@usableapp/cardds 0.7.82 → 0.7.84

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.
Files changed (47) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +72 -12
  3. package/bin/cardds.mjs +21 -10
  4. package/css/actions.css +2 -0
  5. package/css/card.css +4 -4
  6. package/css/layover.css +5 -0
  7. package/css/people.css +3 -2
  8. package/css/post.css +16 -4
  9. package/css/stamp.css +23 -21
  10. package/css/sticky.css +138 -28
  11. package/dist/actions/Chip.d.ts +4 -2
  12. package/dist/actions/Chip.js +3 -3
  13. package/dist/cardds.css +191 -59
  14. package/dist/catalog.json +1 -1
  15. package/dist/index.d.ts +2 -0
  16. package/dist/index.js +2 -0
  17. package/dist/post/PostBox.d.ts +1 -1
  18. package/dist/post/PostDesk.d.ts +2 -2
  19. package/dist/post/PostLetter.d.ts +1 -1
  20. package/dist/post/Postmark.d.ts +1 -1
  21. package/dist/post/Postmark.js +1 -1
  22. package/dist/post/postCorner.js +1 -1
  23. package/dist/scaffold/LayerHistory.d.ts +2 -0
  24. package/dist/scaffold/LayerHistory.js +2 -0
  25. package/dist/sheets/Modal.d.ts +4 -1
  26. package/dist/sheets/Modal.js +4 -1
  27. package/dist/stamp/Stamp.d.ts +4 -3
  28. package/dist/stamp/Stamp.js +3 -2
  29. package/dist/stamp/StampBook.d.ts +3 -3
  30. package/dist/stamp/StampBook.js +1 -1
  31. package/dist/stamp/StampOn.d.ts +1 -0
  32. package/dist/stamp/StampOn.js +1 -0
  33. package/dist/sticky/Sticky.d.ts +8 -6
  34. package/dist/sticky/Sticky.js +6 -3
  35. package/dist/sticky/StickyAnswer.d.ts +23 -0
  36. package/dist/sticky/StickyAnswer.js +35 -0
  37. package/dist/sticky/StickyBoard.d.ts +12 -0
  38. package/dist/sticky/StickyBoard.js +13 -0
  39. package/dist/sticky/StickyThread.d.ts +12 -6
  40. package/dist/sticky/StickyThread.js +8 -4
  41. package/dist/words.d.ts +1 -1
  42. package/dist/words.js +2 -2
  43. package/lib/catalog.mjs +1 -0
  44. package/lib/mcp.mjs +1 -1
  45. package/lib/role.mjs +11 -3
  46. package/lint/rules.mjs +7 -2
  47. package/package.json +1 -1
package/css/sticky.css CHANGED
@@ -1,12 +1,14 @@
1
- /* cardds — sticky notes: the paper note, the field written on it, the tone chooser, a thread of posts.
1
+ /* cardds — sticky notes: the paper note, the field written on it, the tone chooser, notes stuck on a page, a thread of posts, the answer note.
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
- /* ---- sticky: a paper note — a flat pastel block, straight, the bottom-right corner folded over ----
5
- Straight unless asked (--tilt-left / --tilt-right: the one note of a start screen), never patterned. The corner is CUT on the diagonal and a flap of a darker shade lies over it.
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.
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.
6
8
  <div class="sticky sticky--custard">
7
9
  <div class="sticky__paper">
8
10
  <div class="sticky__head">12 Oct</div><div class="sticky__body">…</div><div class="sticky__foot">From Noi</div>
9
- <span class="sticky__fold"></span>
11
+ <span class="sticky__fold"></span><span class="sticky__state t-sr">Unread</span>
10
12
  </div>
11
13
  </div>
12
14
  Two boxes on purpose: the OUTER one holds the tone and the shadow, the PAPER is the one that is cut — a clip-path
@@ -18,16 +20,13 @@
18
20
  --_muted: color-mix(in srgb, currentColor 62%, transparent); /* muted words: the ink sunk into the paper (currentColor is read where it is used: there it is still the note's ink) */
19
21
  padding-inline-end: var(--sticky-fold, var(--sp-6)); /* the knob, read by a real property first (the knob test probes that one); --_fold hands the same value to the paper and the flap */
20
22
  --_fold: var(--sticky-fold, var(--sp-6));
21
- --_min: calc(var(--tap) * 3);
22
- --_cap: calc(var(--tap) * 7);
23
- --_tall: calc(var(--tap) * 4.4);
24
23
  position: relative;
25
24
  display: grid;
25
+ grid-template: minmax(0, 1fr) / minmax(0, 1fr); /* the paper is the note's size, never its words' */
26
26
  box-sizing: border-box;
27
- width: fit-content; /* hugs its words… */
28
- min-width: var(--sticky-min-w, var(--_min)); /* …but a word alone is still a note */
29
- max-width: var(--sticky-max-w, 100%);
30
- min-height: var(--sticky-min-h, var(--_tall)); /* every note is one height, the height of a note being written (Lh 2026-10-07); more words still grow it */
27
+ flex: none;
28
+ width: var(--sticky-size, calc(var(--sp-12) * 3.5)); /* 168: two side by side on a phone */
29
+ aspect-ratio: 1;
31
30
  padding: 0 var(--_fold) var(--_fold) 0; /* = the line above, written whole */
32
31
  background-clip: content-box;
33
32
  isolation: isolate; /* the two layers below stay under this note, over whatever is behind it */
@@ -41,7 +40,7 @@
41
40
  position: absolute;
42
41
  z-index: -1;
43
42
  pointer-events: none;
44
- border-radius: var(--sticky-radius, var(--sp-1));
43
+ border-radius: var(--sticky-radius, 0rem);
45
44
  background-image: linear-gradient(-45deg, transparent calc(var(--_fold) * 0.7071), var(--_ink) 0); /* the folded corner, cut */
46
45
  }
47
46
  .sticky::after { --_ink: var(--sticky-edge-bg, rgb(0 0 0 / 0.14)); inset: calc(-1 * var(--border-w)); }
@@ -69,18 +68,20 @@
69
68
  gap: var(--sticky-gap, var(--sp-3));
70
69
  box-sizing: border-box;
71
70
  min-width: 0;
71
+ min-height: 0;
72
72
  margin: 0 calc(-1 * var(--_fold)) calc(-1 * var(--_fold)) 0;
73
73
  padding: var(--sticky-pad-y, var(--sp-4)) var(--sticky-pad-x, var(--sp-4));
74
- border-radius: var(--sticky-radius, var(--sp-1));
74
+ border-radius: var(--sticky-radius, 0rem);
75
75
  background-color: inherit;
76
76
  clip-path: polygon(0 0, 100% 0, 100% calc(100% - var(--_fold)), calc(100% - var(--_fold)) 100%, 0 100%);
77
77
  }
78
- .sticky__head { display: flex; align-items: center; gap: var(--sticky-head-gap, var(--sp-2)); font: var(--sticky-head-font, var(--type-caption)); color: var(--sticky-muted-ink, var(--_muted)); }
78
+ .sticky__head { flex: none; display: flex; align-items: center; gap: var(--sticky-head-gap, var(--sp-2)); font: var(--sticky-head-font, var(--type-caption)); color: var(--sticky-muted-ink, var(--_muted)); }
79
79
  .sticky__head > .ph { flex: 1 1 0; min-width: 0; } /* a picture in the head takes the note's width */
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
- .sticky__body { flex: 1 1 auto; min-width: 0; font: var(--sticky-font, var(--type-title)); overflow-wrap: anywhere; }
83
- .sticky__foot { 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 */
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__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
+ .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 */
84
85
  /* 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 */
85
86
  .sticky__fold {
86
87
  position: absolute;
@@ -92,12 +93,15 @@
92
93
  background-color: var(--sticky-fold-bg, color-mix(in srgb, currentColor 14%, transparent)); /* a darker shade of whatever the paper is */
93
94
  box-shadow: calc(-1 * var(--sp-0)) calc(-1 * var(--sp-0)) var(--sp-1) rgb(0 0 0 / 0.08);
94
95
  }
95
- /* things stuck ON the note (a stamp, placed by hand): a layer over the whole paper. A tap between them still reaches the words under it. */
96
+ /* things stuck ON the note (a stamp, placed by hand): a layer over the whole paper. A tap between them still reaches the words under it.
97
+ A stamp on a note is the stamp's small size whatever the note's (48 × 60, --stamp-sm-size: stamp.css), and may lie over the words. */
96
98
  .sticky__stuck { position: absolute; inset: 0; pointer-events: none; }
97
99
  .sticky--flat > .sticky__paper { clip-path: none; }
98
100
  .sticky--flat > .sticky__paper > .sticky__foot { padding-inline-end: 0; }
99
101
 
100
- /* --tilt: the note turned a little on its middle. Straight is the rule; this is for the one note a start screen hands over. */
102
+ /* --tilt: the note turned a little on its middle — the one note a start screen hands over. Notes on a board or in a lapped thread are turned by their holder. */
103
+ /* a note that stands alone in a page's column or in a centre stands in its middle */
104
+ .page > .sticky, .screen__centre > .sticky { margin-inline: auto; }
101
105
  .sticky--tilt-left, .sticky--tilt-right { margin-block: var(--sp-4); } /* the turned corners and the shadow need room: a box that scrolls (a centre) would cut them */
102
106
  .sticky--tilt-left { rotate: calc(-1 * var(--sticky-tilt, 5deg)); }
103
107
  .sticky--tilt-right { rotate: var(--sticky-tilt, 5deg); }
@@ -108,9 +112,6 @@
108
112
  .sticky-date__day { font: var(--sticky-date-day-font, var(--type-stat)); }
109
113
  .sticky-date__month { font: var(--sticky-date-month-font, var(--type-caption)); color: var(--sticky-muted-ink, var(--_muted)); }
110
114
 
111
- /* --square: the note to write on — 1:1, as wide as its column up to a cap, in the middle */
112
- .sticky--square { width: 100%; max-width: var(--sticky-square-max-w, var(--_cap)); aspect-ratio: 1; margin-inline: auto; }
113
-
114
115
  /* --corner: a control stands ON the bottom-right corner, half outside. The paper is notched round it (a mask, so no
115
116
  ground colour is needed; the edge and the shadow are notched the same way); the note has no fold. */
116
117
  .sticky__corner {
@@ -126,7 +127,6 @@
126
127
  /* the half of the control that stands outside is the note's own room: it never runs past the column, nor onto what follows */
127
128
  .sticky--corner {
128
129
  --_corner: var(--sticky-corner-size, var(--tap-sm)); /* the same default as .sticky__corner above: the paper's notch needs it too */
129
- max-width: calc(var(--sticky-max-w, 100%) - var(--_corner) / 2);
130
130
  margin: 0 calc(var(--_corner) / 2) calc(var(--_corner) / 2) 0;
131
131
  }
132
132
  .sticky--corner::before, .sticky--corner::after { background-image: radial-gradient(circle at 100% 100%, transparent calc(var(--_corner) / 2 + var(--sp-1)), var(--_ink) 0); }
@@ -137,16 +137,17 @@
137
137
  mask: radial-gradient(circle at 100% 100%, transparent calc(var(--_notch) - var(--border-w)), #000 var(--_notch));
138
138
  }
139
139
 
140
- /* a tilted square is sized by the SCREEN, not by a length: three quarters of its width inside the inset, about two thirds of the glass (Lh 2026-10-06) — the turned corners
141
- keep clear of the edges on any phone. cqw reads the fill screen (the size container). After --square, so it wins. */
142
- .sticky--square.sticky--tilt-left, .sticky--square.sticky--tilt-right { max-width: var(--sticky-tilt-max-w, 75cqw); }
143
-
144
140
  /* --tap: the whole note is the button (`onClick`). The fold stays; the ring follows the note's turn. */
145
141
  .sticky--tap { appearance: none; border: 0; font: inherit; text-align: start; cursor: pointer; -webkit-tap-highlight-color: transparent; }
146
142
  .sticky--tap .sticky__body, .sticky--tap .sticky__foot { display: block; }
147
143
  .sticky--tap:focus-visible { outline: var(--focus); outline-offset: var(--sp-1); }
148
144
  .sticky--tap:active > .sticky__paper { translate: 0 var(--sp-0); }
149
145
 
146
+ /* a name label in the foot (a Chip, `sm`) stands at the START, what follows it (the time) at the end, on one line: a long name is cut
147
+ with an ellipsis past its share of the row and never wraps; the time is never cut (a long one may run a little onto the fold) */
148
+ .sticky__paper > .sticky__foot:has(> .chip) { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); white-space: nowrap; text-align: end; }
149
+ .sticky__foot > .chip { display: block; flex: none; min-width: 0; max-width: var(--sticky-foot-chip-max-w, 62%); overflow: hidden; text-overflow: ellipsis; text-align: start; }
150
+
150
151
  /* ---- sticky field: the words written ON a note — a bare textarea in the note's type and ink, filling it ----
151
152
  <div class="sticky__body"><textarea class="sticky-field" aria-label="Note" placeholder="Add text to this note"></textarea></div> */
152
153
  .sticky__body:has(> .sticky-field) { display: grid; }
@@ -154,7 +155,7 @@
154
155
  display: block;
155
156
  box-sizing: border-box;
156
157
  width: 100%;
157
- min-height: var(--sticky-field-min-h, var(--_min));
158
+ height: 100%; /* the note's whole room: a touch anywhere on the paper writes */
158
159
  resize: none;
159
160
  border: 0;
160
161
  margin: 0;
@@ -210,7 +211,6 @@
210
211
  .sticky-post__name { min-width: 0; font: var(--sticky-post-name-font, var(--type-label)); overflow-wrap: anywhere; }
211
212
  .sticky-post__time { margin-inline-start: auto; font: var(--sticky-post-time-font, var(--type-caption)); color: var(--sticky-post-time-ink, var(--on-surface-muted)); white-space: nowrap; }
212
213
  .sticky-post__notes { grid-area: 2 / 2; display: grid; justify-items: start; gap: var(--sticky-post-notes-gap, var(--sp-3)); min-width: 0; }
213
- .sticky-post__notes > .sticky { max-width: var(--sticky-post-max-w, 85%); } /* a note leaves the far side open, as a bubble does */
214
214
  .sticky-post__rail { display: none; grid-area: 2 / 1; justify-self: center; align-self: stretch; color: var(--sticky-thread-rail-ink, var(--on-surface-muted)); border-inline-start: var(--rule-w) dotted currentColor; margin-block-end: calc(-1 * var(--_thread-gap)); } /* runs on through the gap to the next avatar */
215
215
  .sticky-thread--rail > .sticky-post:not(:last-child) > .sticky-post__rail { display: block; }
216
216
  /* a chat (no rail): a note leans on its writer's own edge of the thread — it starts under the avatar, not under the name, so theirs and mine stand apart (Lh 2026-10-07: both sat in the middle). With a rail the notes keep clear of it, under the name. */
@@ -221,3 +221,113 @@
221
221
  .sticky-post--right > .sticky-post__head > .sticky-post__time { margin-inline: 0 auto; }
222
222
  .sticky-post--right > .sticky-post__notes { grid-column: 1; justify-items: end; }
223
223
  .sticky-post--right > .sticky-post__rail { grid-column: 2; }
224
+
225
+ /* --lapped: a chat with NO head rows (Lh 2026-10-10) — theirs at the left, mine at the right, each turn lying over a corner of the one before
226
+ on the other side; two turns from one side have nothing to lie over and stand a small gap apart. The time stands close under its note, on
227
+ its own side; the avatar is not drawn and the name is only heard (it stands once, in the screen's top bar). The thread is two notes wide
228
+ less what they share, in the middle of its column, so the corners cross on any phone. Later = on top: the order of the turns. */
229
+ .sticky-thread--lapped {
230
+ gap: 0;
231
+ width: 100%;
232
+ max-width: calc(2 * var(--sticky-size, calc(var(--sp-12) * 3.5)) - var(--sticky-thread-lap-x, var(--sp-8))); /* 304: the two sides cross 32 in the middle (38 as they lie turned — the prototype's 39); on a 375 phone each stands 19.5 in from the screen's inset */
233
+ margin-inline: auto;
234
+ padding-block-start: var(--sp-4); /* the first note's turned corner */
235
+ }
236
+ .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--right { justify-self: end; justify-items: end; }
239
+ .sticky-thread--lapped > .sticky-post:not(.sticky-post--right) + .sticky-post--right,
240
+ .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 */
241
+ .sticky-thread--lapped > .sticky-post > .sticky-post__who { display: none; }
242
+ .sticky-thread--lapped > .sticky-post > .sticky-post__notes { grid-area: 1 / 1; }
243
+ .sticky-thread--lapped > .sticky-post > .sticky-post__head { grid-area: 2 / 1; }
244
+ .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
+ .sticky-thread--lapped > .sticky-post > .sticky-post__head > .sticky-post__time { margin-inline: 0; }
246
+ .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
+ .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
+ /* under an answer note the thread ends with the room that note stands in: the last turn scrolls up clear of it */
251
+ .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
+ /* ---- sticky board: notes stuck on a page — rows that wrap by the width, each note turned a little ----
254
+ <div class="sticky-board"><button class="sticky sticky--tap …">…</button>…</div>
255
+ As many notes in a row as the width holds (two on a phone, three from 504 of room, a screen about 540 wide), the rows in the middle. The notes stand edge to
256
+ edge both ways (--sticky-board-lap 0): only their turned corners cross. LATER = ON TOP, always: the right one over the left, the lower over
257
+ the upper — the order of the notes, no z-index. Where two notes do not fit side by side, each lies over the one before by what is
258
+ missing, so a phone still shows two — but never by more than half the fold, which would hide the corner that says unread: past that, one in a row.
259
+ The columns are tracks of the note's size less the lap; a note is wider than its track by the lap, and the room at the end is what the last
260
+ of a row runs into — none is cut. The tracks are 50% while the lap is the missing room (padding % and track % read the same width).
261
+ A note here is the SAME note as inside its chat (Lh 2026-10-11): on the paper only what is the note's own — its words, its time at the END
262
+ of the foot, the stamps really stuck on it. Whose it is, is a label of the page: a small Chip at the start of the note's foot.
263
+ The foot takes no room from the words (below). */
264
+ .sticky-board {
265
+ --_size: var(--sticky-size, calc(var(--sp-12) * 3.5));
266
+ --_most: calc(var(--sticky-fold, var(--sp-6)) / 2);
267
+ padding-inline-end: clamp(var(--sticky-board-lap, 0rem), 2 * var(--_size) - 100%, var(--_most));
268
+ --_cell: max(var(--_size) - var(--_most), min(var(--_size) - var(--sticky-board-lap, 0rem), 50%));
269
+ display: grid;
270
+ grid-template-columns: repeat(auto-fill, var(--_cell));
271
+ grid-auto-rows: calc(var(--_size) - var(--sticky-board-lap, 0rem)); /* down the page the lap is the set one: a page is as long as it needs */
272
+ justify-content: center;
273
+ padding-block: var(--sp-2) calc(var(--sticky-board-lap, 0rem) + var(--sticky-board-drop, var(--sp-2)) + var(--sp-2)); /* the turned corners above; below, the last row's overhang and its drop */
274
+ }
275
+ .sticky-board > * { position: relative; }
276
+ .sticky-board > :nth-child(even) { translate: 0 var(--sticky-board-drop, var(--sp-2)); } /* tops of a row NEAR one line, not on it */
277
+ /* the foot is laid OVER the note's foot, out of its flow: the words keep the whole paper, so they break exactly as they do where the note is
278
+ opened (Lh 2026-10-11 — a clamp showed three lines and "…" on the page and four inside). A long note's last line runs under the label and the time. */
279
+ .sticky-board .sticky__paper > .sticky__foot { position: absolute; inset: auto var(--sticky-board-foot-inset, var(--sp-3)) var(--sticky-board-foot-inset, var(--sp-3)); text-align: end; }
280
+ /* how far each is turned: FOUR unlike angles in turn (Lh 2026-10-11). At most three notes meet, so four keep the lean from ever looking the
281
+ same: two in a row, a note lies over the one two before it and those lean opposite ways; three in a row, over the one three before it,
282
+ never the same angle. (A run of seven left one column at 0.75° and −0.5°: its edges read as one straight line and the lean was lost.) */
283
+ .sticky-board > :nth-child(4n + 1) { rotate: var(--sticky-board-turn-1, -3deg); }
284
+ .sticky-board > :nth-child(4n + 2) { rotate: var(--sticky-board-turn-2, 1.5deg); }
285
+ .sticky-board > :nth-child(4n + 3) { rotate: var(--sticky-board-turn-3, 2.75deg); }
286
+ .sticky-board > :nth-child(4n) { rotate: var(--sticky-board-turn-4, -1.75deg); }
287
+
288
+ /* ---- sticky answer: the note you answer on — it waits at the screen's foot and comes to the top to be written on ----
289
+ <div class="sticky-answer sticky-answer--writing">
290
+ <div class="sticky-answer__scrim"></div>
291
+ <div class="sticky-answer__note"><div class="sticky sticky--flat">…<textarea class="sticky-field"></textarea>…</div></div>
292
+ <div class="sticky-answer__under" inert>
293
+ <div class="sticky-answer__tools">…the paper chooser · the send button…</div>
294
+ <div class="sticky-answer__tray">…what else stands under them (the writer's stamp)…</div>
295
+ </div>
296
+ </div>
297
+ A LAYER of the screen (its child), as the snackbar is. AT REST the note stands near the right, three quarters of it above the screen's
298
+ foot (--sticky-answer-show), turned a little; what stands under it is out of sight and inert. WRITING it stands straight in the middle under
299
+ the top bar, the screen behind is washed pale and takes no touch, tools and tray stand under it. The note keeps its one size in both
300
+ places. One `top`, one `right` — they slide between the two places; nothing else moves. */
301
+ .sticky-answer {
302
+ --_size: var(--sticky-size, calc(var(--sp-12) * 3.5));
303
+ position: fixed; inset: 0;
304
+ max-width: var(--screen-w); margin-inline: auto;
305
+ overflow: clip;
306
+ z-index: var(--z-fab);
307
+ pointer-events: none;
308
+ }
309
+ .screen--fill > .sticky-answer { position: absolute; }
310
+ .sticky-answer--writing { z-index: calc(var(--z-top) + 1); }
311
+ .sticky-answer__scrim { position: absolute; inset: 0; background: var(--sticky-answer-scrim-bg, color-mix(in srgb, var(--surface) 82%, transparent)); opacity: 0; visibility: hidden; transition: opacity var(--motion-sheet) ease, visibility 0s var(--motion-sheet); }
312
+ .sticky-answer--writing > .sticky-answer__scrim { opacity: 1; visibility: visible; pointer-events: auto; transition-delay: 0s; }
313
+ .sticky-answer__note {
314
+ position: absolute;
315
+ top: calc(100% - var(--_size) * var(--sticky-answer-show, 0.75) - max(var(--safe-bottom), var(--kb, 0px)));
316
+ right: var(--sticky-answer-end, var(--screen-pad));
317
+ rotate: var(--sticky-answer-tilt, -3deg);
318
+ pointer-events: auto;
319
+ transition: top var(--motion-sheet) ease, right var(--motion-sheet) ease, rotate var(--motion-sheet) ease;
320
+ }
321
+ .sticky-answer--writing > .sticky-answer__note { top: calc(var(--safe-top) + var(--header-h) + var(--sticky-answer-top, var(--sp-5))); right: calc(50% - var(--_size) / 2); rotate: 0deg; }
322
+ .sticky-answer__under {
323
+ position: absolute; inset-inline: var(--screen-pad);
324
+ top: calc(var(--safe-top) + var(--header-h) + var(--sticky-answer-top, var(--sp-5)) + var(--_size) + var(--sticky-answer-gap, var(--sp-4)));
325
+ display: grid; gap: var(--sticky-answer-gap, var(--sp-4));
326
+ opacity: 0; visibility: hidden;
327
+ transition: opacity var(--motion-sheet-rest) ease, visibility 0s var(--motion-sheet-rest);
328
+ }
329
+ .sticky-answer--writing > .sticky-answer__under { opacity: 1; visibility: visible; pointer-events: auto; transition-delay: var(--motion-sheet-rest), 0s; }
330
+ .sticky-answer__tools { display: flex; align-items: center; justify-content: space-between; gap: var(--sticky-answer-tools-gap, var(--sp-3)); }
331
+ .sticky-answer__tools > .sticky-pick { justify-content: start; }
332
+ .sticky-answer__tray { display: grid; gap: var(--sticky-answer-tray-gap, var(--sp-2)); }
333
+ @media (prefers-reduced-motion: reduce) { .sticky-answer__scrim, .sticky-answer__note, .sticky-answer__under { transition: none; } }
@@ -7,6 +7,8 @@ export interface ChipProps extends ComponentProps<'span'> {
7
7
  muted?: boolean;
8
8
  /** a leading signal dot: `a` (the accent signal), `b` (the secondary), or one of the signal set — ok · warn · error · info */
9
9
  dot?: 'a' | 'b' | DotTone;
10
+ /** a small label: low and tight (a name on a note). Never with `as="button"` or `href` — it is under tap size */
11
+ sm?: boolean;
10
12
  /** the element: span (default), a link, or a button */
11
13
  as?: 'span' | 'a' | 'button';
12
14
  /** makes the chip a link (an `a`) */
@@ -14,8 +16,8 @@ export interface ChipProps extends ComponentProps<'span'> {
14
16
  /** the chip's words */
15
17
  children?: ReactNode;
16
18
  }
17
- /** Chip — a status or attribute tag (with an optional signal Dot), or a small tap target; `on` when it is the live one. For a choice use ChipPick (`sign` for the +/× interest picker). */
18
- export declare function Chip({ on, muted, dot, as, href, className, children, ...rest }: ChipProps): import("react").DetailedReactHTMLElement<{
19
+ /** Chip — a status or attribute tag (with an optional signal Dot), or a small tap target; `sm` = a small label only; `on` when it is the live one. For a choice use ChipPick (`sign` for the +/× interest picker). */
20
+ export declare function Chip({ on, muted, dot, sm, as, href, className, children, ...rest }: ChipProps): import("react").DetailedReactHTMLElement<{
19
21
  ref?: import("react").Ref<HTMLSpanElement> | undefined;
20
22
  key?: import("react").Key | null | undefined;
21
23
  defaultChecked?: boolean | undefined;
@@ -2,10 +2,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { createElement } from 'react';
3
3
  import { cx } from '../cx.js';
4
4
  import { Dot } from './Dot.js';
5
- /** Chip — a status or attribute tag (with an optional signal Dot), or a small tap target; `on` when it is the live one. For a choice use ChipPick (`sign` for the +/× interest picker). */
6
- export function Chip({ on, muted, dot, as, href, className, children, ...rest }) {
5
+ /** Chip — a status or attribute tag (with an optional signal Dot), or a small tap target; `sm` = a small label only; `on` when it is the live one. For a choice use ChipPick (`sign` for the +/× interest picker). */
6
+ export function Chip({ on, muted, dot, sm, as, href, className, children, ...rest }) {
7
7
  const el = as ?? (href ? 'a' : 'span');
8
- return createElement(el, { href, type: el === 'button' ? 'button' : undefined, className: cx('chip', on && 'chip--on', muted && 'chip--muted', className), ...rest }, dot && _jsx(Dot, { b: dot === 'b', tone: dot === 'a' || dot === 'b' ? undefined : dot }), children);
8
+ return createElement(el, { href, type: el === 'button' ? 'button' : undefined, className: cx('chip', on && 'chip--on', muted && 'chip--muted', sm && 'chip--sm', className), ...rest }, dot && _jsx(Dot, { b: dot === 'b', tone: dot === 'a' || dot === 'b' ? undefined : dot }), children);
9
9
  }
10
10
  /** ChipPick — a chip that is a choice: full-width in a ChipGrid (9:00 · 9:30 · …), or `sign` on a ChipRow (+ off, × on — the
11
11
  * interest picker); the picked one fills with accent. A native radio/checkbox inside the label carries the state. */