@usableapp/cardds 0.7.89 → 0.7.92

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,45 @@
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.92 — 2026-10-15
6
+
7
+ This is the first version after 0.7.90 that npm serves: 0.7.91 was tagged but never published, because the class-count check of tests/lint.spec.js (a literal, 517) had not been raised for the two classes 0.7.91 added, nor for the two added here, so the proof `npm run release` asks for failed on both tags. The count is 520 now and everything 0.7.91 listed ships in this version.
8
+
9
+ One behaviour CHANGES: a `CardFeed` no longer cuts its queue to the first two cards. Before → after: a feed of more than two `FeedCard` children drew the first two (one big, one small) and nothing of the rest → it draws every one, the upper big and all the others small; a feed of two or fewer is drawn as before. Nothing else breaks: a prop pair (`onMore` + `ahead`) and two slots (`CardFeed end`, `FeedCard lead`) are added and none removed, and a feed that passes none of them is the 0.7.90 feed with the whole queue in sight.
10
+
11
+ ### Added
12
+ - **`CardFeed onMore` + `ahead`: the feed asks the app for more when the member nears the end** (Lh 2026-10-15: "there are always 5 items; when the scroll reaches item 3, load 5 more"). Before → after: the app could not tell that the member had come near the end of the queue → `onMore()` is called when the card with `ahead` cards under it (default 2: with 5 cards the 3rd, with 10 the 8th) comes into view, watched with an `IntersectionObserver` on the viewport, so a scrolling ancestor counts. It asks ONCE per queue and again only after the queue changed (the app appended); never while `end` is given and never without `onMore`. A card already in view when watched asks at once, so a short queue asks straight away.
13
+ - **`CardFeed end`: the words drawn after the last card when the app says the queue is complete.** Before → after: a feed just stopped → `end` is a `role="note"` caption centred after the last card, and with it the feed asks for no more. With no cards the feed still draws nothing, `end` or not.
14
+ - **`FeedCard lead`: anything at the head's start in place of the icon** (a `Stamp`, an `Avatar`). Before → after: the head's start was an icon in the outlined square, by name → `lead` takes any node, drawn in both sizes; `icon` stays as it was; both given: `lead` wins and one warning says so. A `Stamp` there is the stamp's small size, 48 × 60 (`--stamp-sm-size`), and the head grows to hold it (80 tall at the gallery's size). Stories `CardFeed · More` and `StampLead`.
15
+
16
+ ### Changed
17
+ - **The feed's `'queue'` ask is for a card that LEFT or an order that changed, not for an append.** Before → after: `onBigChange(upperId, 'queue')` fired whenever the ids changed → it fires only when the previous ids are not a prefix of the new ones (or the queue was empty), so cards appended at the end ask nothing and a member who scrolled down is not sent back to the top; the appended cards still fade in.
18
+ - **The small stamp's width is one rule.** Before → after: `--stamp-sm-size` was written in two rules (`.stamp-on > .stamp` in stamp.css, `.avatar-stack > .stamp` in people.css) → one rule in stamp.css for the three holders (`.stamp-on`, `.avatar-stack`, `.feed-card__lead`); people.css keeps `flex: none; margin-inline: 0`. Computed style of all 458 stories already in the gallery identical before and after (`AvatarStack · Stamps` and every `StampOn` story included), but for `CardFeed · Default`, which now draws the whole queue (`Home · Feed` keeps two cards: the base's scroll rule is measured on it, tests/gallery-stack.spec.js).
19
+
20
+ ## 0.7.91 — 2026-10-15 (tagged, never published: see 0.7.92)
21
+
22
+ Nothing here breaks an existing screen: one knob is added and none removed, no prop changed. The style snapshot of the 458 stories already in the gallery moved only where a note held words it had been cutting without a sign: `Sticky · LongWords` and `StampOn · Removable` (those notes now wear `sticky--small`, the words one size smaller), and `StickyScreens · Chat`, `StickyHeading · InARoom` and `StickyHeading · Ends` (the sample chat gained one long note, nine elements); every other story is identical.
23
+
24
+ ### Added
25
+ - **`--sticky-small-font`** (default `--type-label`): the type a note's words are written in when they do not fit at the note's size (Lh 2026-10-15, ruled from a sketch of four ways side by side). A knob like the others: set on `:root` it retunes every note, on an element only the notes inside it. The only sizes a note's words take are the steps of the scale, so there is no second knob and no scale between.
26
+ - **A `StickyField` stops at its note's room.** The `Sticky` it is on refuses the input that would not fit even one size smaller (six lines at the default size), and the app's `onChange` is not called for it: a typed character, a paste, a composition that overruns is not taken, the words and the caret stay where they were. An input that does not add letters (a deletion) is never refused, so a note that came over-full can still be emptied. `maxLength` is still the app's to set on top. Stories: `Sticky · Small`, `StickyField · Full`.
27
+
28
+ ### Changed
29
+ - **A note's words that do not fit shrink before they are cut.** Before → after: every `Sticky` was one square and cut the words it could not hold at 22px (4.76 lines) with no sign → the note measures its own words, and when they do not fit writes them one size smaller (17px, six lines; it wears `sticky--small`); only words longer than that are cut where their room ends, as before. The note stays its one size, never grows, no ellipsis. Words that fit at 22px are as they were. A note under a `StickyHeading` on a `StickyBoard` (its words show two lines) never shrinks. There is nothing to set: the fit is measured, not a prop. A sent note that is cut after this is cut at six small lines, not four and a half.
30
+
31
+ ## 0.7.90 — 2026-10-13
32
+
33
+ Nothing here breaks an existing screen: one component (`StickyHeading`) and four knobs are added and none removed, and a `StickyThread lapped` without a heading draws, leans and spaces every note as in 0.7.89 (computed style of all 455 stories already in the gallery identical before and after).
34
+
35
+ ### Added
36
+ - **`StickyHeading`: the heading of a chat told in notes, what it is about for as long as that lasts** (Lh 2026-10-13, ruled from two sketches side by side: the plain dotted heading kept, the strip of note paper lying askew rejected — no paper, no tone, no tilt, no variant). Before → after: a chat in notes had no way to say "this chat is about a job agreed, and when", short of a note of its own among the notes → `<StickyHeading title caption />`, a subject and a short caption written on whatever it stands on with a dotted rule under it, one line where there is room (the subject, then the caption), two where there is not, a title too long for its line cut with an ellipsis. It stands in two places that already existed and no other component has a new prop: in a `Sticky`'s `stuck` (the list: across the top of that chat's note on its own paper; the note's words start under it and show two lines at most, so they never run under the foot, and a stamp stuck on the note stays) and as the FIRST child of a `StickyThread` (the room: across the thread's top on the screen's ground, staying there while the notes run under it, with 16 before the first note). The app renders it for as long as the subject lasts and stops when it ends; the notes are as they were before it. Information only, nothing to press.
37
+ - **`StickyHeading as`: `span` (default) or `h2`.** In the room pass `as="h2"`: it is the heading of that thread, a sub-heading under the screen's h1 (the `TopBar`'s title, who the chat is with), heard as "subject caption, heading level 2". On a note keep the default: a note may be a button, and a button holds no heading, so there the words are part of the note's name (read after the note's words and its foot, because the layer that holds things stuck on a note comes after the paper).
38
+ - **Four knobs:** `--sticky-heading-title-font` (`--type-label`), `--sticky-heading-caption-font` (`--type-caption`), `--sticky-heading-gap` (between the subject and its caption, `--sp-2`) and `--sticky-heading-room` (the room a note keeps at the top of its paper for the heading, `--sp-12` + `--sp-4`: 64). Gallery: the page `StickyHeading` after `StickyThread` in `sticky chat`, stories `InARoom`, `OnANote`, `Ends`.
39
+
40
+ ### Changed
41
+ - **A thread's lean and its first post count posts only.** Before → after: `.sticky-thread--lapped > .sticky-post:nth-child(even)` turned the even children the other way and `.sticky-post:first-child` had no gap above it, so a heading as the first child of a lapped thread made every note lean the other way round and gave the first post a gap → `:nth-child(even of .sticky-post)` and `:nth-child(1 of .sticky-post)`: a thread with a heading leans exactly as the same thread without one (−2° · 2° in turn from its first note) and its first post stands 16 under the heading. A thread of posts alone is read the same as before.
42
+ - **Taking the heading out of a room lifts the notes by its room.** Before → after (stated, not a regression): none, a heading is new → the notes keep their size, their lean and their place against the first note, and the whole run rises by the heading's height and its 16 (they stand where they stood before the subject began). In the list nothing moves: the heading is laid over the note's paper and holds no room of the board's.
43
+
5
44
  ## 0.7.89 — 2026-10-13
6
45
 
7
46
  Nothing here breaks an existing screen: a prop pair (`Stamp arrive` + `onArrived`) is added and none removed, a `Stamp` without `arrive` renders the html it did, and a `StampOn arrive` is struck and tells the app exactly as before (the one rule that gave it the strike lists a second holder; no keyframe, knob or class is new but `stamp--arrive`).
package/README.md CHANGED
@@ -520,26 +520,33 @@ between at 40% down the screen (see *Anatomy*, "Where the centre is"):
520
520
  the one button in the head or in the content, a disabled button, a sheet, a
521
521
  stack's sheet, a modal. A card that is itself a link or a label was pressed
522
522
  whole already. Story `Card · PressedWhole`.
523
- - **A feed is a queue of cards, two in sight** (Lh 2026-10-09). `CardFeed`
524
- takes the WHOLE queue as `FeedCard` children, in the app's order, and shows
525
- the first two: one BIG (heading, content, its button) and one SMALL (the
526
- same card cut down to its head — no content, no button). A touch anywhere
527
- on the small one makes it the big one and the other the small one; neither
528
- changes place. The small card is itself the button (named by its heading,
529
- Enter and Space) and what it does not show cannot be pressed, focused or
530
- read; the big card is a plain card, pressed whole by the rule above. When
531
- the two cards shown change — a card left the queue, a new one came — what
532
- is left slides up, the next comes in as the lower card, and the UPPER card
533
- is big, whichever was big before. One card alone is big; no card, nothing
534
- is drawn. The queue is the app's: a card leaves when the app stops
535
- rendering it. Which card is big is controlled like an input: `big` (a
536
- card's `id`) + `onBigChange(id, 'tap' | 'queue')`, or `defaultBig`. A
537
- `FeedCard` is `id` · `heading` · `caption` · `icon` (its thumb, in both
538
- sizes) · `tone` · `foot` (one `Btn`: the big card is pressed whole; two
539
- or more: only the buttons act) · children (the content); its size is
540
- the feed's, never a prop. Knobs `--card-feed-gap`, `--feed-card-head-h`.
541
- Reduced motion: nothing slides or fades. Stories `CardFeed · Default`,
542
- `Home · Feed`.
523
+ - **A feed is a queue of cards, all in sight** (Lh 2026-10-09; the whole
524
+ queue 2026-10-15). `CardFeed` takes the WHOLE queue as `FeedCard`
525
+ children, in the app's order, and draws every one: ONE is BIG (heading,
526
+ content, its button) and the rest are SMALL (the same card cut down to its
527
+ head — no content, no button). A touch anywhere on a small one makes it
528
+ the big one and the big one small; none changes place. The small card is
529
+ itself the button (named by its heading, Enter and Space) and what it does
530
+ not show cannot be pressed, focused or read; the big card is a plain card,
531
+ pressed whole by the rule above. The queue is the app's, and so is its
532
+ length (5, then 10…): near the end the feed asks for more with
533
+ `onMore()`, once per queue, when the card with `ahead` (default 2) cards
534
+ under it comes into view; the app appends, and the feed asks again at the
535
+ new queue's card. When the app says the queue is complete it passes `end`
536
+ (the words drawn after the last card) and the feed asks no more. When a
537
+ card LEAVES or the order changes, what is left slides up and the UPPER card
538
+ is big, whichever was big before; cards appended at the end fade in and ask
539
+ nothing, so a member who scrolled down stays there. One card alone is big;
540
+ no card, nothing is drawn. Which card is big is controlled like an input:
541
+ `big` (a card's `id`) + `onBigChange(id, 'tap' | 'queue')`, or
542
+ `defaultBig`. A `FeedCard` is `id` · `heading` · `caption` · `icon` (its
543
+ thumb, in both sizes) or `lead` (anything at the head's start instead: a
544
+ `Stamp` is the small stamp, 48 × 60, an `Avatar`; it wins over `icon`) ·
545
+ `tone` · `foot` (one `Btn`: the big card is pressed whole; two or more:
546
+ only the buttons act) · children (the content); its size is the feed's,
547
+ never a prop. Knobs `--card-feed-gap`, `--feed-card-head-h`. Reduced
548
+ motion: nothing slides or fades. Stories `CardFeed · Default`, `More`,
549
+ `StampLead`, `Home · Feed`.
543
550
  - A page with **one feature and one CTA** (an install page, a single
544
551
  decision) may be contained in one card: head, the pitch, foot.
545
552
  - **A bottom sheet holds what the member acts on** — controls, or small
@@ -586,8 +593,11 @@ Live: every screen in the gallery (`npm run dev`).
586
593
  A sticky note is a square of paper with a few words on it. The family is in `src/sticky/`, its CSS in `css/sticky.css`.
587
594
 
588
595
  - **One note, one size.** Every `Sticky` is one square, 168 by default, with square corners. It never grows with its
589
- words: words it cannot hold are cut where their room ends, with no ellipsis. One knob sets the side,
590
- `--sticky-size`; set it on an element to resize the notes inside that element only.
596
+ words. Words it cannot hold at its size are written one size smaller (`--sticky-small-font`, the label size), and
597
+ the note then holds six lines instead of four and a half. Words longer than that are cut where their room ends, with
598
+ no ellipsis. A `StickyField` stops at that room: the note refuses the input that would not fit, and the app's
599
+ `onChange` is not called for it. One knob sets the side, `--sticky-size`; set it on an element to resize the notes
600
+ inside that element only.
591
601
  - **The fold says unread.** A folded bottom-right corner means the note has not been read. `flat` means it has. A
592
602
  folded note also says so to a screen reader after its words: cardds's own word by the page's language, or
593
603
  `unreadLabel`.
@@ -607,6 +617,12 @@ A sticky note is a square of paper with a few words on it. The family is in `src
607
617
  stand at the left and mine (`side="right"`) at the right, each turn lying over a corner of the one before
608
618
  (`--sticky-thread-lap` down, `--sticky-thread-lap-x` across), the time close under its note, the name only heard.
609
619
  Two turns from one side stand `--sticky-thread-same-gap` apart.
620
+ - **`StickyHeading`** — what a chat told in notes is about, for as long as that lasts: a `title` and a short `caption`
621
+ (when) written on what it stands on, a dotted rule under it; information only. It stands in two places and no other
622
+ component has a prop for it. In a `Sticky`'s `stuck` (the list) it lies across the top of that chat's note, and the
623
+ note's words start under it and show two lines at most. As the first child of a `StickyThread` (the room, with
624
+ `as="h2"`: the sub-heading under the screen's h1) it stays at the top while the notes run under it. The app stops
625
+ rendering it when the subject ends. Knobs: `--sticky-heading-title-font`, `-caption-font`, `-gap`, `-room`.
610
626
  - **`StickyAnswer`** — the note you answer on. Its child is a `Sticky flat` holding a `StickyField`. At rest it waits
611
627
  at the screen's foot near the right, three quarters of it in sight (`--sticky-answer-show`). A touch on it puts the
612
628
  cursor in the field and the holder asks to write: `writing` + `onWritingChange(writing, reason)`, or
package/css/card.css CHANGED
@@ -320,11 +320,12 @@
320
320
  opacity: 0.85;
321
321
  }
322
322
 
323
- /* ---- card feed: a queue of cards, two in sight — one big, one small (Lh 2026-10-09) ----
323
+ /* ---- card feed: a queue of cards, all in sight — one big, the rest small (Lh 2026-10-09, the whole queue 2026-10-15) ----
324
324
  <div class="card-feed">
325
- <article class="card feed-card"><div class="card__head feed-card__head">thumb · h2 · caption</div>
325
+ <article class="card feed-card"><div class="card__head feed-card__head">thumb or lead · h2 · caption</div>
326
326
  <div class="feed-card__more"><div class="feed-card__rest">content · <div class="card__foot">…</div></div></div></article>
327
327
  <article class="card feed-card feed-card--small" role="button" tabindex="0">… <div class="feed-card__more" inert>…</div></article>
328
+ <p class="t-caption card-feed__end" role="note">the end</p>
328
329
  </div>
329
330
  ONE markup in both sizes: the head always shows, the rest is a grid row that goes 1fr ↔ 0fr (never a height: Safari 16.4 has no
330
331
  interpolate-size). The card's own row gap is 0 and the rest carries it as its first thing's margin, so a small card ends on its
@@ -334,8 +335,10 @@
334
335
  .card-feed { display: grid; grid-template-columns: minmax(0, 1fr); align-content: start; gap: var(--card-feed-gap, var(--card-gap-rest)); }
335
336
  .feed-card { row-gap: 0; }
336
337
  .feed-card__head { display: grid; grid-template-columns: minmax(0, 1fr); align-content: center; min-height: var(--feed-card-head-h, var(--tap)); }
337
- .feed-card__head:has(> .card__thumb) { grid-template-columns: auto minmax(0, 1fr); }
338
- .feed-card__head:has(> .t-caption) > .card__thumb { grid-row: span 2; }
338
+ .feed-card__head:has(> :is(.card__thumb, .feed-card__lead)) { grid-template-columns: auto minmax(0, 1fr); }
339
+ .feed-card__head:has(> .t-caption) > :is(.card__thumb, .feed-card__lead) { grid-row: span 2; }
340
+ .feed-card__lead { display: grid; align-self: center; } /* a Stamp here is the small one, 48 × 60 (stamp.css) */
341
+ .card-feed__end { text-align: center; }
339
342
  .feed-card__more { display: grid; grid-template-columns: minmax(0, 1fr); grid-template-rows: 1fr; }
340
343
  .feed-card--small > .feed-card__more { grid-template-rows: 0fr; }
341
344
  .feed-card__rest {
package/css/people.css CHANGED
@@ -50,10 +50,10 @@
50
50
  .avatar-stack { display: inline-flex; align-items: center; }
51
51
  .avatar-stack > .avatar { box-shadow: 0 0 0 var(--sp-1) var(--card-bg, var(--surface)); }
52
52
  .avatar-stack > .avatar + .avatar { margin-left: calc(-1 * var(--sp-3)); }
53
- /* a stamp in the stack is the stamp's small size, the one a stuck stamp is (--stamp-sm-size, stamp.css: 48 × 60), lapped like the heads. Measured 2026-10-11 in a Row's
53
+ /* a stamp in the stack is the stamp's small size (--stamp-sm-size: one rule in stamp.css, 48 × 60), lapped like the heads. Measured 2026-10-11 in a Row's
54
54
  lead with a title, a meta line and an end button: at 375 the meta line breaks in two, accepted by Lh (a lap of 16 would hold it; the lap stays 8)
55
55
  <span class="avatar-stack"><span class="stamp stamp--bare">…</span><span class="stamp stamp--bare">…</span><span class="avatar-stack__more">+9</span></span> */
56
- .avatar-stack > .stamp { flex: none; width: var(--stamp-sm-size, var(--tap)); margin-inline: 0; }
56
+ .avatar-stack > .stamp { flex: none; margin-inline: 0; }
57
57
  .avatar-stack > .stamp + .stamp { margin-left: calc(-1 * var(--sp-2)); }
58
58
  /* more: the count that trails the stack — plain words on the ground, title size so it reads at arm's length (the accent "+7" avatar is the small, framed way) */
59
59
  .avatar-stack__more { margin-left: var(--avatar-stack-more-gap, var(--sp-3)); font: var(--avatar-stack-more-font, var(--type-title)); color: var(--avatar-stack-more-ink, inherit); white-space: nowrap; }
package/css/stamp.css CHANGED
@@ -17,7 +17,8 @@
17
17
  set of tone knobs.
18
18
  ONE SIZE (Lh 2026-10-11): a stamp, and the empty place for one, is --stamp-size wide wherever it stands — in a book, a tray,
19
19
  a card's head, a centre, a sheet, a page. What holds it only PLACES it. There is ONE other size, the small one, --stamp-sm-size
20
- (one tap: half the stamp), for a stamp stuck ON something (`.stamp-on`, below) and a stamp in an avatar stack (people.css): one knob for both.
20
+ (one tap: half the stamp), for a stamp stuck ON something (`.stamp-on`, below), a stamp in an avatar stack (people.css) and a stamp
21
+ at the head of a feed card (`.feed-card__lead`, card.css): one knob for the three.
21
22
  ONE exception, by the owner's decision (Lh 2026-10-11): the stamp at a postcard's corner (post.css) is the postcard's own size, 72 × 90,
22
23
  --post-corner-stamp-w, in neither. The desk's tray (post.css) holds stamps of the standard size. */
23
24
  .stamp, .stamp-place {
@@ -211,8 +212,10 @@
211
212
  rotate: calc(var(--stamp-on-turn, 0) * 1deg);
212
213
  pointer-events: auto; /* the layer it lies in lets a tap through to the surface; the stamp itself takes it */
213
214
  }
214
- /* the SMALL stamp: stuck on a surface (a note). 48 × 60 whatever the surface's size. The stamp carries the size and the `.stamp-on` box is as wide as its stamp */
215
- .stamp-on > .stamp { width: var(--stamp-sm-size, var(--tap)); max-width: none; }
215
+ /* the SMALL stamp, 48 × 60 whatever its holder's size, in the three places that hold it: stuck on a surface (a note), lapped in an avatar stack,
216
+ leading a feed card's head. The stamp carries the size and the `.stamp-on` box is as wide as its stamp */
217
+ .stamp-on > .stamp, .avatar-stack > .stamp, .feed-card__lead > .stamp { width: var(--stamp-sm-size, var(--tap)); }
218
+ .stamp-on > .stamp { max-width: none; }
216
219
  /* --main: the same stuck stamp at the stamp's MAIN size, 96 × 120 (--stamp-size), to cover what it lies on (the owner's call, 2026-10-11: the home's note).
217
220
  After the rule above, at the same weight: `main` is the one place a stuck stamp reads --stamp-size. */
218
221
  .stamp-on--main > .stamp { width: var(--stamp-size, calc(var(--tap) * 2)); }
package/css/sticky.css CHANGED
@@ -2,8 +2,8 @@
2
2
  A note is its own paper: it brings its colour and its ink, whatever card or palette it lies on. */
3
3
 
4
4
  /* ---- sticky: a paper note — a flat pastel SQUARE of one size, the bottom-right corner folded over ----
5
- ONE SIZE (Lh 2026-10-10): every note is --sticky-size square (168), whatever it says; words it cannot hold are cut where the words' room ends,
6
- no ellipsis. Square corners. THE FOLD SAYS UNREAD: a folded corner = not read yet, --flat = read.
5
+ ONE SIZE (Lh 2026-10-10): every note is --sticky-size square (168), whatever it says; words it cannot hold are written one size smaller
6
+ (--sticky-small-font, the label size; Lh 2026-10-15), and past that are cut where the words' room ends, no ellipsis. Square corners. THE FOLD SAYS UNREAD: a folded corner = not read yet, --flat = read.
7
7
  Straight unless asked (--tilt-left / --tilt-right, or a holder that turns its notes), never patterned. The corner is CUT on the diagonal and a flap of a darker shade lies over it.
8
8
  <div class="sticky sticky--custard">
9
9
  <div class="sticky__paper">
@@ -80,6 +80,7 @@
80
80
  .sticky__head:has(> .sticky-date) { align-items: start; }
81
81
  .sticky__head:has(> .sticky-date) > .ph { flex: none; width: var(--sticky-pic-size, calc(var(--tap) * 1.5)); min-height: 0; aspect-ratio: 1; } /* beside a date the picture is a small square at the start */
82
82
  .sticky__body { flex: 1 1 auto; min-width: 0; min-height: 0; overflow: clip; /* words past the note's room are cut, the foot keeps its place */ font: var(--sticky-font, var(--type-title)); overflow-wrap: anywhere; }
83
+ .sticky--small .sticky__body { font: var(--sticky-small-font, var(--type-label)); } /* the note measured its words and they did not fit at its size (src/sticky/fit.ts) */
83
84
  .sticky__paper > :not(.sticky__body) > .chip { border-color: transparent; background-color: var(--sticky-chip-bg, rgb(255 255 255 / 0.85)); color: var(--sticky-ink, #141414); } /* a chip on a note is a label ON the paper (whose note it is): a pale ground nearly opaque (it may lie over words), no edge, the note's ink */
84
85
  .sticky__foot { flex: none; font: var(--sticky-foot-font, var(--type-caption)); color: var(--sticky-muted-ink, var(--_muted)); padding-inline-end: var(--_fold); } /* the line stops before the fold */
85
86
  /* the flap: a square in the corner — the paper's cut takes its outer half, what is left is the triangle lying on the note */
@@ -234,7 +235,7 @@
234
235
  padding-block-start: var(--sp-4); /* the first note's turned corner */
235
236
  }
236
237
  .sticky-thread--lapped > .sticky-post { position: relative; display: grid; grid-template-columns: auto; justify-self: start; justify-items: start; row-gap: var(--sticky-thread-time-gap, var(--sp-0)); margin-block-start: var(--sticky-thread-same-gap, var(--sp-2)); }
237
- .sticky-thread--lapped > .sticky-post:first-child { margin-block-start: 0; }
238
+ .sticky-thread--lapped > .sticky-post:nth-child(1 of .sticky-post) { margin-block-start: 0; }
238
239
  .sticky-thread--lapped > .sticky-post--right { justify-self: end; justify-items: end; }
239
240
  .sticky-thread--lapped > .sticky-post:not(.sticky-post--right) + .sticky-post--right,
240
241
  .sticky-thread--lapped > .sticky-post--right + .sticky-post:not(.sticky-post--right) { margin-block-start: calc(-1 * var(--sticky-thread-lap, calc(var(--sp-1) * 19))); } /* 76 */
@@ -244,9 +245,9 @@
244
245
  .sticky-thread--lapped > .sticky-post > .sticky-post__head > .sticky-post__name { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; } /* as .t-sr: heard, not seen */
245
246
  .sticky-thread--lapped > .sticky-post > .sticky-post__head > .sticky-post__time { margin-inline: 0; }
246
247
  .sticky-thread--lapped > .sticky-post > .sticky-post__head:not(:has(> .sticky-post__time)) { display: none; }
247
- /* each note leans a little, one way then the other */
248
+ /* each note leans a little, one way then the other; a post counts among posts only, so a heading before the first changes no note's lean */
248
249
  .sticky-thread--lapped > .sticky-post > .sticky-post__notes > .sticky { rotate: calc(-1 * var(--sticky-thread-tilt, 2deg)); }
249
- .sticky-thread--lapped > .sticky-post:nth-child(even) > .sticky-post__notes > .sticky { rotate: var(--sticky-thread-tilt, 2deg); }
250
+ .sticky-thread--lapped > .sticky-post:nth-child(even of .sticky-post) > .sticky-post__notes > .sticky { rotate: var(--sticky-thread-tilt, 2deg); }
250
251
  /* under an answer note the thread ends with the room that note stands in: the last turn scrolls up clear of it */
251
252
  .screen:has(> .sticky-answer) .sticky-thread--lapped { padding-block-end: calc(var(--sticky-size, calc(var(--sp-12) * 3.5)) * var(--sticky-answer-show, 0.75) + var(--sp-6)); }
252
253
 
@@ -374,3 +375,31 @@
374
375
  .sticky-answer__tools > .sticky-pick { justify-content: start; }
375
376
  .sticky-answer__tray { display: grid; gap: var(--sticky-answer-tray-gap, var(--sp-2)); }
376
377
  @media (prefers-reduced-motion: reduce) { .sticky-answer__scrim, .sticky-answer__note, .sticky-answer__under { transition: none; } }
378
+
379
+ /* ---- sticky heading: what the notes are about — the subject and a short caption written on whatever it stands on, a dotted rule under it ----
380
+ Two places, both existing ones:
381
+ <div class="sticky__stuck"><span class="sticky-heading"><span class="sticky-heading__title">…</span><span class="sticky-heading__caption">…</span></span></div>
382
+ <div class="sticky-thread sticky-thread--lapped"><h2 class="sticky-heading">…</h2><div class="sticky-post">…</div>…</div> */
383
+ .sticky-heading {
384
+ display: flex;
385
+ flex-wrap: wrap;
386
+ align-items: baseline;
387
+ column-gap: var(--sticky-heading-gap, var(--sp-2));
388
+ box-sizing: border-box;
389
+ min-width: 0;
390
+ margin: 0;
391
+ padding-block-end: var(--sp-0);
392
+ border-block-end: var(--rule-w) dotted currentColor;
393
+ font: inherit;
394
+ }
395
+ .sticky-heading__title { min-width: 0; max-width: 100%; font: var(--sticky-heading-title-font, var(--type-label)); line-height: 1.35; /* Thai marks stack taller than the label's 1.2, and overflow clips them */ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
396
+ .sticky-heading__caption { font: var(--sticky-heading-caption-font, var(--type-caption)); color: color-mix(in srgb, currentColor 62%, transparent); white-space: nowrap; }
397
+ /* on a note: across the top of its paper, and the note's words start under it */
398
+ .sticky__stuck > .sticky-heading { position: absolute; inset: var(--sp-2) var(--sp-4) auto; }
399
+ .sticky:has(> .sticky__stuck > .sticky-heading) > .sticky__paper { padding-block-start: var(--sticky-heading-room, calc(var(--sp-12) + var(--sp-4))); }
400
+ /* on a board the foot lies over the note's last line: under a heading the words are two lines, clear of it */
401
+ .sticky-board .sticky:has(> .sticky__stuck > .sticky-heading) .sticky__body { flex: none; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
402
+ /* in a thread: across its top on the screen's ground, staying there while the notes run under it; the room before the first note is the turned corner's, as the thread's own top */
403
+ /* the ground is drawn a little wider than the heading each way: a turned note's corner and its shadow stand outside the thread's width */
404
+ .sticky-thread > .sticky-heading { position: sticky; inset-block-start: 0; z-index: 2; padding-block-start: var(--sp-2); background: var(--surface); color: var(--on-surface); box-shadow: calc(-1 * var(--sp-3)) 0 0 var(--surface), var(--sp-3) 0 0 var(--surface); }
405
+ .sticky-thread--lapped > .sticky-heading { margin-block-end: var(--sp-4); }
package/dist/cardds.css CHANGED
@@ -1012,11 +1012,12 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
1012
1012
  opacity: 0.85;
1013
1013
  }
1014
1014
 
1015
- /* ---- card feed: a queue of cards, two in sight — one big, one small (Lh 2026-10-09) ----
1015
+ /* ---- card feed: a queue of cards, all in sight — one big, the rest small (Lh 2026-10-09, the whole queue 2026-10-15) ----
1016
1016
  <div class="card-feed">
1017
- <article class="card feed-card"><div class="card__head feed-card__head">thumb · h2 · caption</div>
1017
+ <article class="card feed-card"><div class="card__head feed-card__head">thumb or lead · h2 · caption</div>
1018
1018
  <div class="feed-card__more"><div class="feed-card__rest">content · <div class="card__foot">…</div></div></div></article>
1019
1019
  <article class="card feed-card feed-card--small" role="button" tabindex="0">… <div class="feed-card__more" inert>…</div></article>
1020
+ <p class="t-caption card-feed__end" role="note">the end</p>
1020
1021
  </div>
1021
1022
  ONE markup in both sizes: the head always shows, the rest is a grid row that goes 1fr ↔ 0fr (never a height: Safari 16.4 has no
1022
1023
  interpolate-size). The card's own row gap is 0 and the rest carries it as its first thing's margin, so a small card ends on its
@@ -1026,8 +1027,10 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
1026
1027
  .card-feed { display: grid; grid-template-columns: minmax(0, 1fr); align-content: start; gap: var(--card-feed-gap, var(--card-gap-rest)); }
1027
1028
  .feed-card { row-gap: 0; }
1028
1029
  .feed-card__head { display: grid; grid-template-columns: minmax(0, 1fr); align-content: center; min-height: var(--feed-card-head-h, var(--tap)); }
1029
- .feed-card__head:has(> .card__thumb) { grid-template-columns: auto minmax(0, 1fr); }
1030
- .feed-card__head:has(> .t-caption) > .card__thumb { grid-row: span 2; }
1030
+ .feed-card__head:has(> :is(.card__thumb, .feed-card__lead)) { grid-template-columns: auto minmax(0, 1fr); }
1031
+ .feed-card__head:has(> .t-caption) > :is(.card__thumb, .feed-card__lead) { grid-row: span 2; }
1032
+ .feed-card__lead { display: grid; align-self: center; } /* a Stamp here is the small one, 48 × 60 (stamp.css) */
1033
+ .card-feed__end { text-align: center; }
1031
1034
  .feed-card__more { display: grid; grid-template-columns: minmax(0, 1fr); grid-template-rows: 1fr; }
1032
1035
  .feed-card--small > .feed-card__more { grid-template-rows: 0fr; }
1033
1036
  .feed-card__rest {
@@ -3215,10 +3218,10 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
3215
3218
  .avatar-stack { display: inline-flex; align-items: center; }
3216
3219
  .avatar-stack > .avatar { box-shadow: 0 0 0 var(--sp-1) var(--card-bg, var(--surface)); }
3217
3220
  .avatar-stack > .avatar + .avatar { margin-left: calc(-1 * var(--sp-3)); }
3218
- /* a stamp in the stack is the stamp's small size, the one a stuck stamp is (--stamp-sm-size, stamp.css: 48 × 60), lapped like the heads. Measured 2026-10-11 in a Row's
3221
+ /* a stamp in the stack is the stamp's small size (--stamp-sm-size: one rule in stamp.css, 48 × 60), lapped like the heads. Measured 2026-10-11 in a Row's
3219
3222
  lead with a title, a meta line and an end button: at 375 the meta line breaks in two, accepted by Lh (a lap of 16 would hold it; the lap stays 8)
3220
3223
  <span class="avatar-stack"><span class="stamp stamp--bare">…</span><span class="stamp stamp--bare">…</span><span class="avatar-stack__more">+9</span></span> */
3221
- .avatar-stack > .stamp { flex: none; width: var(--stamp-sm-size, var(--tap)); margin-inline: 0; }
3224
+ .avatar-stack > .stamp { flex: none; margin-inline: 0; }
3222
3225
  .avatar-stack > .stamp + .stamp { margin-left: calc(-1 * var(--sp-2)); }
3223
3226
  /* more: the count that trails the stack — plain words on the ground, title size so it reads at arm's length (the accent "+7" avatar is the small, framed way) */
3224
3227
  .avatar-stack__more { margin-left: var(--avatar-stack-more-gap, var(--sp-3)); font: var(--avatar-stack-more-font, var(--type-title)); color: var(--avatar-stack-more-ink, inherit); white-space: nowrap; }
@@ -3798,8 +3801,8 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
3798
3801
  A note is its own paper: it brings its colour and its ink, whatever card or palette it lies on. */
3799
3802
 
3800
3803
  /* ---- sticky: a paper note — a flat pastel SQUARE of one size, the bottom-right corner folded over ----
3801
- ONE SIZE (Lh 2026-10-10): every note is --sticky-size square (168), whatever it says; words it cannot hold are cut where the words' room ends,
3802
- no ellipsis. Square corners. THE FOLD SAYS UNREAD: a folded corner = not read yet, --flat = read.
3804
+ ONE SIZE (Lh 2026-10-10): every note is --sticky-size square (168), whatever it says; words it cannot hold are written one size smaller
3805
+ (--sticky-small-font, the label size; Lh 2026-10-15), and past that are cut where the words' room ends, no ellipsis. Square corners. THE FOLD SAYS UNREAD: a folded corner = not read yet, --flat = read.
3803
3806
  Straight unless asked (--tilt-left / --tilt-right, or a holder that turns its notes), never patterned. The corner is CUT on the diagonal and a flap of a darker shade lies over it.
3804
3807
  <div class="sticky sticky--custard">
3805
3808
  <div class="sticky__paper">
@@ -3876,6 +3879,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
3876
3879
  .sticky__head:has(> .sticky-date) { align-items: start; }
3877
3880
  .sticky__head:has(> .sticky-date) > .ph { flex: none; width: var(--sticky-pic-size, calc(var(--tap) * 1.5)); min-height: 0; aspect-ratio: 1; } /* beside a date the picture is a small square at the start */
3878
3881
  .sticky__body { flex: 1 1 auto; min-width: 0; min-height: 0; overflow: clip; /* words past the note's room are cut, the foot keeps its place */ font: var(--sticky-font, var(--type-title)); overflow-wrap: anywhere; }
3882
+ .sticky--small .sticky__body { font: var(--sticky-small-font, var(--type-label)); } /* the note measured its words and they did not fit at its size (src/sticky/fit.ts) */
3879
3883
  .sticky__paper > :not(.sticky__body) > .chip { border-color: transparent; background-color: var(--sticky-chip-bg, rgb(255 255 255 / 0.85)); color: var(--sticky-ink, #141414); } /* a chip on a note is a label ON the paper (whose note it is): a pale ground nearly opaque (it may lie over words), no edge, the note's ink */
3880
3884
  .sticky__foot { flex: none; font: var(--sticky-foot-font, var(--type-caption)); color: var(--sticky-muted-ink, var(--_muted)); padding-inline-end: var(--_fold); } /* the line stops before the fold */
3881
3885
  /* the flap: a square in the corner — the paper's cut takes its outer half, what is left is the triangle lying on the note */
@@ -4030,7 +4034,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
4030
4034
  padding-block-start: var(--sp-4); /* the first note's turned corner */
4031
4035
  }
4032
4036
  .sticky-thread--lapped > .sticky-post { position: relative; display: grid; grid-template-columns: auto; justify-self: start; justify-items: start; row-gap: var(--sticky-thread-time-gap, var(--sp-0)); margin-block-start: var(--sticky-thread-same-gap, var(--sp-2)); }
4033
- .sticky-thread--lapped > .sticky-post:first-child { margin-block-start: 0; }
4037
+ .sticky-thread--lapped > .sticky-post:nth-child(1 of .sticky-post) { margin-block-start: 0; }
4034
4038
  .sticky-thread--lapped > .sticky-post--right { justify-self: end; justify-items: end; }
4035
4039
  .sticky-thread--lapped > .sticky-post:not(.sticky-post--right) + .sticky-post--right,
4036
4040
  .sticky-thread--lapped > .sticky-post--right + .sticky-post:not(.sticky-post--right) { margin-block-start: calc(-1 * var(--sticky-thread-lap, calc(var(--sp-1) * 19))); } /* 76 */
@@ -4040,9 +4044,9 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
4040
4044
  .sticky-thread--lapped > .sticky-post > .sticky-post__head > .sticky-post__name { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; } /* as .t-sr: heard, not seen */
4041
4045
  .sticky-thread--lapped > .sticky-post > .sticky-post__head > .sticky-post__time { margin-inline: 0; }
4042
4046
  .sticky-thread--lapped > .sticky-post > .sticky-post__head:not(:has(> .sticky-post__time)) { display: none; }
4043
- /* each note leans a little, one way then the other */
4047
+ /* each note leans a little, one way then the other; a post counts among posts only, so a heading before the first changes no note's lean */
4044
4048
  .sticky-thread--lapped > .sticky-post > .sticky-post__notes > .sticky { rotate: calc(-1 * var(--sticky-thread-tilt, 2deg)); }
4045
- .sticky-thread--lapped > .sticky-post:nth-child(even) > .sticky-post__notes > .sticky { rotate: var(--sticky-thread-tilt, 2deg); }
4049
+ .sticky-thread--lapped > .sticky-post:nth-child(even of .sticky-post) > .sticky-post__notes > .sticky { rotate: var(--sticky-thread-tilt, 2deg); }
4046
4050
  /* under an answer note the thread ends with the room that note stands in: the last turn scrolls up clear of it */
4047
4051
  .screen:has(> .sticky-answer) .sticky-thread--lapped { padding-block-end: calc(var(--sticky-size, calc(var(--sp-12) * 3.5)) * var(--sticky-answer-show, 0.75) + var(--sp-6)); }
4048
4052
 
@@ -4171,6 +4175,34 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
4171
4175
  .sticky-answer__tray { display: grid; gap: var(--sticky-answer-tray-gap, var(--sp-2)); }
4172
4176
  @media (prefers-reduced-motion: reduce) { .sticky-answer__scrim, .sticky-answer__note, .sticky-answer__under { transition: none; } }
4173
4177
 
4178
+ /* ---- sticky heading: what the notes are about — the subject and a short caption written on whatever it stands on, a dotted rule under it ----
4179
+ Two places, both existing ones:
4180
+ <div class="sticky__stuck"><span class="sticky-heading"><span class="sticky-heading__title">…</span><span class="sticky-heading__caption">…</span></span></div>
4181
+ <div class="sticky-thread sticky-thread--lapped"><h2 class="sticky-heading">…</h2><div class="sticky-post">…</div>…</div> */
4182
+ .sticky-heading {
4183
+ display: flex;
4184
+ flex-wrap: wrap;
4185
+ align-items: baseline;
4186
+ column-gap: var(--sticky-heading-gap, var(--sp-2));
4187
+ box-sizing: border-box;
4188
+ min-width: 0;
4189
+ margin: 0;
4190
+ padding-block-end: var(--sp-0);
4191
+ border-block-end: var(--rule-w) dotted currentColor;
4192
+ font: inherit;
4193
+ }
4194
+ .sticky-heading__title { min-width: 0; max-width: 100%; font: var(--sticky-heading-title-font, var(--type-label)); line-height: 1.35; /* Thai marks stack taller than the label's 1.2, and overflow clips them */ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
4195
+ .sticky-heading__caption { font: var(--sticky-heading-caption-font, var(--type-caption)); color: color-mix(in srgb, currentColor 62%, transparent); white-space: nowrap; }
4196
+ /* on a note: across the top of its paper, and the note's words start under it */
4197
+ .sticky__stuck > .sticky-heading { position: absolute; inset: var(--sp-2) var(--sp-4) auto; }
4198
+ .sticky:has(> .sticky__stuck > .sticky-heading) > .sticky__paper { padding-block-start: var(--sticky-heading-room, calc(var(--sp-12) + var(--sp-4))); }
4199
+ /* on a board the foot lies over the note's last line: under a heading the words are two lines, clear of it */
4200
+ .sticky-board .sticky:has(> .sticky__stuck > .sticky-heading) .sticky__body { flex: none; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; }
4201
+ /* in a thread: across its top on the screen's ground, staying there while the notes run under it; the room before the first note is the turned corner's, as the thread's own top */
4202
+ /* the ground is drawn a little wider than the heading each way: a turned note's corner and its shadow stand outside the thread's width */
4203
+ .sticky-thread > .sticky-heading { position: sticky; inset-block-start: 0; z-index: 2; padding-block-start: var(--sp-2); background: var(--surface); color: var(--on-surface); box-shadow: calc(-1 * var(--sp-3)) 0 0 var(--surface), var(--sp-3) 0 0 var(--surface); }
4204
+ .sticky-thread--lapped > .sticky-heading { margin-block-end: var(--sp-4); }
4205
+
4174
4206
  /* ---- css/stamp.css — stamp, empty place, album page, a stamp stuck on a surface ---- */
4175
4207
  /* cardds — stamps: the paper stamp, an empty place for one, the album page, a stamp stuck on a surface.
4176
4208
  A stamp is its own paper, like a sticky note: it brings its ground and its ink, whatever card or palette it lies on.
@@ -4191,7 +4223,8 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
4191
4223
  set of tone knobs.
4192
4224
  ONE SIZE (Lh 2026-10-11): a stamp, and the empty place for one, is --stamp-size wide wherever it stands — in a book, a tray,
4193
4225
  a card's head, a centre, a sheet, a page. What holds it only PLACES it. There is ONE other size, the small one, --stamp-sm-size
4194
- (one tap: half the stamp), for a stamp stuck ON something (`.stamp-on`, below) and a stamp in an avatar stack (people.css): one knob for both.
4226
+ (one tap: half the stamp), for a stamp stuck ON something (`.stamp-on`, below), a stamp in an avatar stack (people.css) and a stamp
4227
+ at the head of a feed card (`.feed-card__lead`, card.css): one knob for the three.
4195
4228
  ONE exception, by the owner's decision (Lh 2026-10-11): the stamp at a postcard's corner (post.css) is the postcard's own size, 72 × 90,
4196
4229
  --post-corner-stamp-w, in neither. The desk's tray (post.css) holds stamps of the standard size. */
4197
4230
  .stamp, .stamp-place {
@@ -4385,8 +4418,10 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
4385
4418
  rotate: calc(var(--stamp-on-turn, 0) * 1deg);
4386
4419
  pointer-events: auto; /* the layer it lies in lets a tap through to the surface; the stamp itself takes it */
4387
4420
  }
4388
- /* the SMALL stamp: stuck on a surface (a note). 48 × 60 whatever the surface's size. The stamp carries the size and the `.stamp-on` box is as wide as its stamp */
4389
- .stamp-on > .stamp { width: var(--stamp-sm-size, var(--tap)); max-width: none; }
4421
+ /* the SMALL stamp, 48 × 60 whatever its holder's size, in the three places that hold it: stuck on a surface (a note), lapped in an avatar stack,
4422
+ leading a feed card's head. The stamp carries the size and the `.stamp-on` box is as wide as its stamp */
4423
+ .stamp-on > .stamp, .avatar-stack > .stamp, .feed-card__lead > .stamp { width: var(--stamp-sm-size, var(--tap)); }
4424
+ .stamp-on > .stamp { max-width: none; }
4390
4425
  /* --main: the same stuck stamp at the stamp's MAIN size, 96 × 120 (--stamp-size), to cover what it lies on (the owner's call, 2026-10-11: the home's note).
4391
4426
  After the rule above, at the same weight: `main` is the one place a stuck stamp reads --stamp-size. */
4392
4427
  .stamp-on--main > .stamp { width: var(--stamp-size, calc(var(--tap) * 2)); }
@@ -4,23 +4,31 @@ import { type IconName } from '../type/Icon.js';
4
4
  /** why the feed asks for another big card: the member touched the small one · the two cards shown changed */
5
5
  export type CardFeedReason = 'tap' | 'queue';
6
6
  export interface CardFeedProps extends ComponentProps<'div'> {
7
- /** CONTROLLED: the `id` of the card shown big — with `onBigChange`; or `defaultBig`. An id that is not one of the two cards shown reads as the upper one */
7
+ /** CONTROLLED: the `id` of the card shown big — with `onBigChange`; or `defaultBig`. An id that names no card of the queue reads as the upper one */
8
8
  big?: string;
9
9
  /** UNCONTROLLED: the card big on arrival (default: the upper one); the feed then keeps its own. Pair of `big` */
10
10
  defaultBig?: string;
11
- /** the feed asks for another big card: its `id`, and why — `tap` (the member touched the small card) · `queue` (the two cards shown changed: the upper one is asked for) */
11
+ /** the feed asks for another big card: its `id`, and why — `tap` (the member touched a small card) · `queue` (a card left the queue, or the order changed: the upper card is asked for; cards appended at the end ask nothing) */
12
12
  onBigChange?: (id: string, reason: CardFeedReason) => void;
13
- /** the WHOLE queue, in the app's order: one `FeedCard` each, as direct children. The feed draws the first two */
13
+ /** the member is near the end of the queue: the app appends more cards (and passes `end` when there are none). Asked ONCE per queue, when the card with `ahead` cards under it comes into view; asked again only after the queue changed. Never asked while `end` is given */
14
+ onMore?: () => void;
15
+ /** how many cards are still under the one that asks (default 2): with 5 cards the 3rd asks, with 10 the 8th */
16
+ ahead?: number;
17
+ /** the words drawn after the last card, when the app says the queue is complete — they stop `onMore`. No card, nothing is drawn, these words too */
18
+ end?: ReactNode;
19
+ /** the WHOLE queue, in the app's order: one `FeedCard` each, as direct children. The feed draws every one of them */
14
20
  children?: ReactNode;
15
21
  }
16
22
  /**
17
- * CardFeed — a QUEUE of cards the system hands the member one after another; at most two are on the screen: one BIG (heading, content,
18
- * its button) and one SMALL (the same card cut down to its head). A touch anywhere on the small one makes it the big one and the other
19
- * the small one; neither changes place. The queue is the app's: when it changes (a card left, a new one came) what is left slides up,
20
- * the next comes in as the lower card, and the feed asks for the UPPER card to be big, whichever was big before. One card alone is big;
21
- * no card, nothing is drawn. Which card is big is controlled like an input (`big` + `onBigChange`, or `defaultBig`).
23
+ * CardFeed — a QUEUE of cards the system hands the member one after another, all of them on the screen: one BIG (heading, content, its
24
+ * button) and every other SMALL (the same card cut down to its head). A touch anywhere on a small one makes it the big one and the big
25
+ * one small; none changes place. The queue is the app's, and so is its length (5, then 10…): near its end the feed asks for more
26
+ * (`onMore`, at the card with `ahead` cards under it) and the app appends; the appended cards fade in and the member stays where they
27
+ * were. When a card LEAVES or the order changes, what is left slides up and the feed asks for the UPPER card to be big. When the
28
+ * app says the queue is complete (`end`) the feed draws those words after the last card and stops asking. One card alone is big; no
29
+ * card, nothing is drawn. Which card is big is controlled like an input (`big` + `onBigChange`, or `defaultBig`).
22
30
  */
23
- export declare function CardFeed({ big: bigProp, defaultBig, onBigChange, className, children, ref, ...rest }: CardFeedProps): import("react").JSX.Element | null;
31
+ export declare function CardFeed({ big: bigProp, defaultBig, onBigChange, onMore, ahead, end, className, children, ref, ...rest }: CardFeedProps): import("react").JSX.Element | null;
24
32
  export interface FeedCardProps extends Omit<ComponentProps<'article'>, 'id' | 'role' | 'tabIndex'> {
25
33
  /** which card of the queue this is — the app's own id for it, unique in the feed. `CardFeed big` / `onBigChange` speak in these */
26
34
  id: string;
@@ -28,8 +36,10 @@ export interface FeedCardProps extends Omit<ComponentProps<'article'>, 'id' | 'r
28
36
  heading: ReactNode;
29
37
  /** the muted line under the heading, shown in both sizes */
30
38
  caption?: ReactNode;
31
- /** the card's thumb at the head's start: an icon by name, drawn in both sizes */
39
+ /** the card's thumb at the head's start: an icon by name in the outlined square, drawn in both sizes. Ignored when `lead` is given */
32
40
  icon?: IconName;
41
+ /** anything at the head's start in place of the icon, drawn in both sizes: a `Stamp` (the small one, 48 × 60, the icon's square is not drawn), an `Avatar`. Wins over `icon` */
42
+ lead?: ReactNode;
33
43
  /** the tone slot — see `Tone` */
34
44
  tone?: Tone;
35
45
  /** the card's button, or its buttons — shown only while the card is big. ONE `Btn`: the big card is pressed whole, a touch anywhere on it runs that button. Two or more (a question: yes · no): only the buttons act, a touch elsewhere on the card does nothing */
@@ -38,9 +48,9 @@ export interface FeedCardProps extends Omit<ComponentProps<'article'>, 'id' | 'r
38
48
  children?: ReactNode;
39
49
  }
40
50
  /**
41
- * FeedCard — ONE card of a `CardFeed`'s queue, the same markup in both sizes: its head (thumb · heading · caption) always shows, its
51
+ * FeedCard — ONE card of a `CardFeed`'s queue, the same markup in both sizes: its head (thumb or lead · heading · caption) always shows, its
42
52
  * content and its button are one part that opens while the card is big. The SIZE IS THE FEED'S, never a prop. Small, the card itself
43
53
  * is the control (a button named by its heading; Enter and Space) and its closed part cannot be pressed, focused or read. Big, it is a
44
54
  * plain card: with one button in its foot it is pressed whole; with two or more only the buttons act.
45
55
  */
46
- export declare function FeedCard({ id, heading, caption, icon, tone, foot, className, children, onClick, onKeyDown, ref, ...rest }: FeedCardProps): import("react").JSX.Element;
56
+ export declare function FeedCard({ id, heading, caption, icon, lead, tone, foot, className, children, onClick, onKeyDown, ref, ...rest }: FeedCardProps): import("react").JSX.Element;