@usableapp/cardds 0.7.16 → 0.7.18
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 +32 -0
- package/README.md +23 -20
- package/cardds.css +3 -3
- package/css/actions.css +60 -38
- package/css/base.css +43 -17
- package/css/card.css +18 -13
- package/css/choice.css +21 -33
- package/css/forms.css +0 -3
- package/css/journey.css +0 -2
- package/css/layover.css +14 -24
- package/css/lists.css +1 -6
- package/css/media.css +7 -15
- package/css/numbers.css +5 -21
- package/css/people.css +1 -8
- package/css/sheet.css +12 -14
- package/css/stack.css +17 -21
- package/css/step.css +7 -8
- package/css/tokens.css +19 -4
- package/dist/actions/Btn.d.ts +1 -1
- package/dist/actions/Dropdown.js +1 -6
- package/dist/actions/Fab.d.ts +1 -1
- package/dist/actions/Fab.js +3 -2
- package/dist/cardds.css +230 -232
- package/dist/cards/Card.d.ts +23 -7
- package/dist/cards/Card.js +23 -8
- package/dist/cards/CardHead.d.ts +0 -1
- package/dist/cards/CardHead.js +1 -1
- package/dist/choice/Calendar.js +1 -2
- package/dist/choice/Mood.js +2 -2
- package/dist/forms/Field.js +2 -3
- package/dist/forms/FileBtn.js +1 -1
- package/dist/forms/Pin.js +6 -10
- package/dist/index.js +2 -2
- package/dist/layover/Banner.d.ts +10 -3
- package/dist/layover/Banner.js +6 -3
- package/dist/lists/Row.js +3 -3
- package/dist/media/Postcard.js +2 -2
- package/dist/numbers/Band.d.ts +3 -1
- package/dist/numbers/Picker.js +1 -7
- package/dist/scaffold/ActionBar.d.ts +3 -3
- package/dist/scaffold/ActionBar.js +2 -2
- package/dist/scaffold/Centre.d.ts +1 -1
- package/dist/scaffold/Centre.js +1 -1
- package/dist/scaffold/PagerAt.d.ts +1 -1
- package/dist/scaffold/PagerAt.js +1 -1
- package/dist/scaffold/Screen.js +2 -4
- package/dist/sheets/Drawer.js +3 -4
- package/dist/sheets/Sheet.js +8 -19
- package/dist/sheets/SheetStack.d.ts +2 -2
- package/dist/sheets/SheetStack.js +18 -10
- package/dist/sheets/SheetSteps.js +5 -7
- package/dist/sheets/StackBack.js +2 -1
- package/dist/sheets/StackSheet.d.ts +3 -2
- package/dist/sheets/StackSheet.js +6 -9
- package/dist/state.d.ts +19 -2
- package/dist/state.js +47 -7
- package/dist/templates/CallSheet.js +1 -2
- package/dist/templates/Home.d.ts +3 -2
- package/dist/templates/Home.js +1 -2
- package/dist/templates/Splash.js +2 -3
- package/dist/templates/Walkthrough.js +2 -2
- package/dist/type/Text.d.ts +1 -1
- package/package.json +7 -4
package/dist/cardds.css
CHANGED
|
@@ -134,11 +134,26 @@
|
|
|
134
134
|
|
|
135
135
|
/* ---- motion ---- */
|
|
136
136
|
--motion-sheet: 0.35s;
|
|
137
|
+
--motion-quick: 0.2s; /* a small state flip that isn't a press: a chevron turning, a popover/banner opening */
|
|
137
138
|
--motion-slide: 0.22s; /* a centred card sliding out and the next in (card.css) — quick, a page turn (Lh, 2026-09-12) */
|
|
138
139
|
--motion-flip: 0.45s; /* a postcard turning over (media.css) — slow enough to read as a card in the hand */
|
|
139
140
|
--dim: 0.7; /* secondary words dimmed on a ground whose muted ink does not fit (the dark card --3, a drawer, a tile's sub line): ONE share for all */
|
|
141
|
+
--rest: 0.6; /* an unpicked choice at rest: .day, .segment__item, .float-bar__item */
|
|
142
|
+
--faint: 0.3; /* further still: rails, far dots, unselected picker items */
|
|
140
143
|
--motion-press: 0.12s; /* a control giving under the finger and springing back (actions.css PRESS) */
|
|
141
|
-
--press-scale: 0.96; /* how far
|
|
144
|
+
--press-scale: 0.96; /* how far a finger-sized control gives — a share of its size */
|
|
145
|
+
--press-dim: 0.6; /* how far a card-wide one (a row, a summary line, a card) fades under the finger */
|
|
146
|
+
--press-shade: 0.93; /* how far something with a thing behind it (a peeked sheet, a postcard) darkens under the finger */
|
|
147
|
+
--off: 0.4; /* a disabled control's fade */
|
|
148
|
+
|
|
149
|
+
/* ---- z-index: the few layers that stack ABOVE ordinary content, low to high ---- */
|
|
150
|
+
--z-bar: 30; /* .action-bar, .float-bar — above .sheet-stack sheets (--level 1-5) */
|
|
151
|
+
--z-fab: 31; /* .fab — above the bars it floats near */
|
|
152
|
+
--z-top: 40; /* .appbar, a stage laid OVER its screen (.sheet-stage--over) — topmost, card animations never cover it */
|
|
153
|
+
|
|
154
|
+
/* ---- shared private sizes (not a knob: two components share the exact same
|
|
155
|
+
literal by design and must not drift apart) ---- */
|
|
156
|
+
--_cover-min-h: 22.5rem; /* .card--cover's min-height; --postcard-min-h falls back to it (media.css) */
|
|
142
157
|
|
|
143
158
|
/* ---- typography: one scale, rem so it follows the root ---- */
|
|
144
159
|
--font-ui: "PK Nonthaburi", -apple-system, system-ui, sans-serif; /* fonts.css ships it; the rest is the fallback while it loads */
|
|
@@ -253,7 +268,7 @@
|
|
|
253
268
|
|
|
254
269
|
/* ============================================================
|
|
255
270
|
REGISTERED TOKENS — the two sheet lengths are typed, so that
|
|
256
|
-
|
|
271
|
+
the Sheet component can read them back RESOLVED: getComputedStyle returns a
|
|
257
272
|
registered <length> in px and a <number> as a number, where an
|
|
258
273
|
unregistered custom property only ever reads back as its text
|
|
259
274
|
("calc(1.25rem + …)"). Drag-to-snap needs to know where the states
|
|
@@ -265,8 +280,8 @@
|
|
|
265
280
|
falls back to when its own declaration cannot resolve.
|
|
266
281
|
============================================================ */
|
|
267
282
|
@property --sheet-peek { syntax: "<length>"; inherits: true; initial-value: 0px; }
|
|
268
|
-
@property --sheet-head-h { syntax: "<length>"; inherits: true; initial-value: 48px; }
|
|
269
|
-
@property --_lead { syntax: "<length>"; inherits: true; initial-value: 0px; } /* the room above a step's words: registered so a cqh share (a no-bar step, 15% of the SCREEN) resolves where it is declared — on the step, against the screen — not on the words, against the step */
|
|
283
|
+
@property --sheet-head-h { syntax: "<length>"; inherits: true; initial-value: 48px; } /* the sheet head's floor (sheet.css) — registered so the Sheet reads it resolved */
|
|
284
|
+
@property --_lead { syntax: "<length>"; inherits: true; initial-value: 0px; } /* the room above a step's words: registered so a cqh share (a no-bar step, 15% of the SCREEN) resolves where it is declared — on the step, against the screen — not on the words, against the step */
|
|
270
285
|
@property --sheet-half { syntax: "<number>"; inherits: true; initial-value: 0.5; }
|
|
271
286
|
@property --sheet-3q { syntax: "<number>"; inherits: true; initial-value: 0.25; }
|
|
272
287
|
|
|
@@ -430,9 +445,9 @@
|
|
|
430
445
|
/* …but controls keep a faint edge so they still read as controls */
|
|
431
446
|
[data-palette] { --outline: color-mix(in srgb, currentColor 25%, transparent); }
|
|
432
447
|
|
|
433
|
-
/* ---- css/base.css — reset, surface, type levels,
|
|
448
|
+
/* ---- css/base.css — reset, surface, type levels, page ---- */
|
|
434
449
|
/* ============================================================
|
|
435
|
-
cardds/base.css — reset, surface, typography levels,
|
|
450
|
+
cardds/base.css — reset, surface, typography levels, page
|
|
436
451
|
============================================================ */
|
|
437
452
|
|
|
438
453
|
*, *::before, *::after { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
|
|
@@ -454,9 +469,8 @@ button, [role="button"], [role="tab"], [role="option"], [role="menuitem"], a.btn
|
|
|
454
469
|
An app's OWN text-size setting is NOT here (Lh 2026-09-22: --text-scale, which
|
|
455
470
|
multiplied the root, zoomed the UI too — retired): it is --fs-base on <html>,
|
|
456
471
|
the type base, so the words grow and the things keep their size (tokens.css). */
|
|
457
|
-
html { font-size: 100%;
|
|
472
|
+
html { font-size: 100%; }
|
|
458
473
|
@media (max-width: 22.5em) { html { font-size: 93.75%; } }
|
|
459
|
-
html::-webkit-scrollbar { display: none; } /* Chromium/Safari */
|
|
460
474
|
|
|
461
475
|
body {
|
|
462
476
|
margin: 0;
|
|
@@ -472,7 +486,30 @@ body {
|
|
|
472
486
|
[hidden] { display: none !important; }
|
|
473
487
|
|
|
474
488
|
img, svg { display: block; max-width: 100%; }
|
|
475
|
-
button { font: inherit;
|
|
489
|
+
button { font: inherit; }
|
|
490
|
+
button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
491
|
+
|
|
492
|
+
/* zero-specificity reset for controls that draw nothing of their own (2026-09-27 review):
|
|
493
|
+
a component's own rule always wins, at any specificity, so this never fights it — it only
|
|
494
|
+
fills in for a selector that never says otherwise. Not everything below shares every
|
|
495
|
+
property: two exceptions read as they always have — .calendar__day has never set its own
|
|
496
|
+
padding (the UA button default), and .sheet-step__go has never set its own color (the UA
|
|
497
|
+
button default too) — giving either one now would be a look change, so neither is here. */
|
|
498
|
+
:where(.appbar__who, .segment__item, .day, .calendar__day, .menu__item, .float-bar__item, .link,
|
|
499
|
+
.add-row, .avatar-pick__item, .picker__item, .postcard__turn, .sheet-step__go) {
|
|
500
|
+
border: 0;
|
|
501
|
+
background: transparent;
|
|
502
|
+
font: inherit;
|
|
503
|
+
}
|
|
504
|
+
:where(.appbar__who, .segment__item, .day, .calendar__day, .menu__item, .float-bar__item, .link,
|
|
505
|
+
.add-row, .avatar-pick__item, .picker__item, .postcard__turn) { color: inherit; }
|
|
506
|
+
:where(.appbar__who, .segment__item, .day, .menu__item, .float-bar__item, .link, .add-row,
|
|
507
|
+
.avatar-pick__item, .picker__item, .postcard__turn, .sheet-step__go) { padding: 0; }
|
|
508
|
+
|
|
509
|
+
/* scrollbar hidden, scroll kept: the track scrolls by touch/drag, never by a visible bar —
|
|
510
|
+
.bleed (card.css) is the one exception, its bar is designed, not hidden */
|
|
511
|
+
:where(html, .sheet-stack--tap > .card, .step > .pages, .picker__rail, .avatar-pick) { scrollbar-width: none; }
|
|
512
|
+
:where(html, .sheet-stack--tap > .card, .step > .pages, .picker__rail, .avatar-pick)::-webkit-scrollbar { display: none; }
|
|
476
513
|
|
|
477
514
|
/* icon from icons.svg sprite: <svg class="icon"><use href="icons.svg#bell"/></svg> */
|
|
478
515
|
.icon { width: var(--icon-size, var(--icon)); height: var(--icon-size, var(--icon)); flex: none; }
|
|
@@ -481,10 +518,16 @@ button { font: inherit; cursor: pointer; }
|
|
|
481
518
|
.icon--lg { width: var(--icon-lg-size, var(--icon-lg)); height: var(--icon-lg-size, var(--icon-lg)); } /* empty states */
|
|
482
519
|
|
|
483
520
|
/* mobile screen column — the standard horizontal inset, and nothing vertical.
|
|
484
|
-
Top
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
521
|
+
Top is zero on purpose: room there is only ever a RESERVE for something
|
|
522
|
+
FIXED parked over the screen — .appbar reserves it (actions.css). A bottom
|
|
523
|
+
bar is not fixed (0.7.1): it is the Screen's own last child, in flow on a
|
|
524
|
+
scrolling screen (a sticky row, actions.css) or, in a fill screen, a layer
|
|
525
|
+
over the content's foot (step.css) — nothing here reserves room for it.
|
|
526
|
+
It also reads like a native app (Lh 2026-09-25): words are not highlighted
|
|
527
|
+
by a long press or a drag, no copy / web-search callout — only what is
|
|
528
|
+
typed into, and words marked selectable (`Text selectable` → .t-select: a
|
|
529
|
+
code, an address). Knob --screen-select: `text` = the web page's old way
|
|
530
|
+
everywhere. */
|
|
488
531
|
.screen {
|
|
489
532
|
--screen-gap: var(--sp-5); /* published: a .step cancels it to touch the bar it follows (step.css) */
|
|
490
533
|
max-width: var(--screen-w);
|
|
@@ -493,6 +536,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
493
536
|
display: grid;
|
|
494
537
|
gap: var(--screen-gap);
|
|
495
538
|
align-content: start; /* a screen taller than its content (a fixed phone) never spreads its rows — the top bar stays at the top, a row keeps its height */
|
|
539
|
+
-webkit-user-select: var(--screen-select, none); user-select: var(--screen-select, none); -webkit-touch-callout: none;
|
|
496
540
|
}
|
|
497
541
|
.screen > * { min-width: 0; } /* content never widens the column past the viewport */
|
|
498
542
|
/* a screen that carries its own palette (data-palette on the element, not on <html>)
|
|
@@ -535,21 +579,18 @@ button { font: inherit; cursor: pointer; }
|
|
|
535
579
|
|
|
536
580
|
.t-muted { color: var(--on-surface-muted); }
|
|
537
581
|
.card .t-muted { color: var(--card-muted); } /* muted follows the card's ink, not the surface's */
|
|
538
|
-
.card--3 .t-muted
|
|
582
|
+
/* .card--3 .t-muted (dim instead of --card-muted on the dark slot) is declared once in card.css, with the rest of that family */
|
|
539
583
|
.t-caps { text-transform: uppercase; }
|
|
540
584
|
.grow { flex: 1; min-width: 0; } /* the child that takes the remaining row space */
|
|
541
585
|
/* read, not shown (Lh 2026-09-23): words a screen reader needs — a page's h1 the design leaves out — kept in the page, taking no room.
|
|
542
586
|
The 1px is the one the platform needs to keep it in the accessibility tree, not a length of the design. */
|
|
543
|
-
/* a screen reads like a native app (Lh 2026-09-25): words are not highlighted by a long press or a drag, no copy / web-search
|
|
544
|
-
callout — only what is typed into, and words marked selectable (`Text selectable` → .t-select: a code, an address).
|
|
545
|
-
Knob --screen-select: `text` = the web page's old way everywhere. */
|
|
546
|
-
.screen { -webkit-user-select: var(--screen-select, none); user-select: var(--screen-select, none); -webkit-touch-callout: none; }
|
|
547
587
|
.screen :is(input, textarea, [contenteditable="true"]), .screen .t-select { -webkit-user-select: text; user-select: text; -webkit-touch-callout: default; }
|
|
548
588
|
.t-sr { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
|
|
549
589
|
|
|
550
|
-
/* ----
|
|
551
|
-
|
|
552
|
-
|
|
590
|
+
/* ---- page: words straight on the base, no card around them — a
|
|
591
|
+
heading, a paragraph, maybe a .cta-pack. Was .section until 2026-09-16
|
|
592
|
+
(Lh: the name said nothing about WHERE it lives), then .base-content
|
|
593
|
+
until 2026-09-23 (Lh: the content is a BOX; what's in it is .page).
|
|
553
594
|
In a .step it is the reading block and step.css adds the flex/scroll rule ---- */
|
|
554
595
|
.page {
|
|
555
596
|
display: grid;
|
|
@@ -583,14 +624,15 @@ button { font: inherit; cursor: pointer; }
|
|
|
583
624
|
color: var(--empty-state-ink, var(--on-surface)); /* full ink, icon and words alike: muted read as "disabled" at the middle of an empty screen (Lh 2026-09-16) — muted is for meta, not a sentence */
|
|
584
625
|
}
|
|
585
626
|
.empty-state__action { margin-top: var(--sp-2); }
|
|
586
|
-
/* inside a card (a
|
|
627
|
+
/* inside a card (a card's own "nothing yet"): the card's ink */
|
|
587
628
|
.card .empty-state { color: var(--card-ink); padding-block: var(--sp-6); }
|
|
588
629
|
.card--3 .empty-state { color: inherit; }
|
|
589
630
|
|
|
590
631
|
/* ---- css/card.css — card boundary + list variants ---- */
|
|
591
632
|
/* ============================================================
|
|
592
633
|
cardds/card.css — the card boundary + list variants
|
|
593
|
-
One card = one boundary.
|
|
634
|
+
One card = one boundary. The base .card is tone 1; further tones
|
|
635
|
+
via .card--2/3/4/5 (role slots).
|
|
594
636
|
============================================================ */
|
|
595
637
|
|
|
596
638
|
.card {
|
|
@@ -615,14 +657,18 @@ button { font: inherit; cursor: pointer; }
|
|
|
615
657
|
.card > * { min-width: 0; }
|
|
616
658
|
|
|
617
659
|
.card--2 { --card-bg: var(--card-2-bg); --card-ink: var(--card-2-ink); --card-muted: var(--card-2-muted); }
|
|
618
|
-
.card--3 { --card-bg: var(--card-3-bg); --card-ink: var(--card-3-ink); }
|
|
619
|
-
.card--4 { --card-bg: var(--card-4-bg); --card-ink: var(--card-4-ink); --card-muted: var(--card-4-muted); }
|
|
620
|
-
.card--5 { --card-bg: var(--card-5-bg); --card-ink: var(--card-5-ink); --card-muted: var(--card-5-muted); }
|
|
621
|
-
|
|
622
660
|
/* dark/accent slot: accent-toned controls would vanish — the slot re-points
|
|
623
661
|
the accent tokens, so every accent consumer (btn--primary, chip--on,
|
|
624
662
|
check, toggle, ring, day…) inverts without per-component rules */
|
|
625
|
-
.card--3 { --accent: var(--card-3-ink); --on-accent: var(--card-3-bg); }
|
|
663
|
+
.card--3 { --card-bg: var(--card-3-bg); --card-ink: var(--card-3-ink); --accent: var(--card-3-ink); --on-accent: var(--card-3-bg); }
|
|
664
|
+
.card--4 { --card-bg: var(--card-4-bg); --card-ink: var(--card-4-ink); --card-muted: var(--card-4-muted); }
|
|
665
|
+
.card--5 { --card-bg: var(--card-5-bg); --card-ink: var(--card-5-ink); --card-muted: var(--card-5-muted); }
|
|
666
|
+
/* the dark slot's captions/hints/footnotes: --card-muted is tuned for a light card, so these dim the full ink
|
|
667
|
+
instead (one rule, matching specificity to what it replaces — .card--3 <space> <one class>) — merged from
|
|
668
|
+
base.css/choice.css/media.css/numbers.css/journey.css/people.css (2026-09-27 review). lists.css's .legend
|
|
669
|
+
differs (opacity 0.8, not --dim) and stays there. */
|
|
670
|
+
.card--3 :is(.t-muted, .calendar__wd, .composer__hint, .quote__by, .ring__label, .band__foot, .picker__unit,
|
|
671
|
+
.route__place, .note-row, .avatar-pick__role) { color: inherit; opacity: var(--dim); }
|
|
626
672
|
|
|
627
673
|
/* header row: title left, floating action right — centered, since the
|
|
628
674
|
trailing control (chip/badge/icon-btn/link) is almost always taller than
|
|
@@ -725,7 +771,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
725
771
|
linear-gradient(to top left, transparent calc(50% - 1px), currentColor calc(50% - 1px), currentColor calc(50% + 1px), transparent calc(50% + 1px));
|
|
726
772
|
border: var(--border-w) solid currentColor;
|
|
727
773
|
border-radius: var(--r-media);
|
|
728
|
-
opacity:
|
|
774
|
+
opacity: var(--off);
|
|
729
775
|
min-height: 10rem;
|
|
730
776
|
}
|
|
731
777
|
.ph--sm { min-height: 7.5rem; }
|
|
@@ -781,9 +827,9 @@ button { font: inherit; cursor: pointer; }
|
|
|
781
827
|
likes when not (Lh, 2026-09-12) ----
|
|
782
828
|
The member sees one content area — whatever is between the top bar and the
|
|
783
829
|
bar — and reads it as a whole page: a short card sits at its middle, a long
|
|
784
|
-
one simply makes the page longer, runs under the bar (
|
|
785
|
-
|
|
786
|
-
|
|
830
|
+
one simply makes the page longer, runs under the bar (a sticky last row,
|
|
831
|
+
actions.css) and scrolls up like any page — nothing is capped, nothing
|
|
832
|
+
shrinks, no scroll box inside a box.
|
|
787
833
|
So on a .screen that is NOT --fill the group is in flow: the column is at
|
|
788
834
|
least one phone tall, the group takes the room after the top bar and
|
|
789
835
|
centres its children in it. The screen's own inset stays the inset.
|
|
@@ -801,9 +847,9 @@ button { font: inherit; cursor: pointer; }
|
|
|
801
847
|
the React adapter's slideTo() sets data-slide on :root, starts the transition
|
|
802
848
|
and updates inside it; a browser without them (the floor: Chrome 105 / iOS 16)
|
|
803
849
|
simply switches. Only the card is named; the rest of the page must not
|
|
804
|
-
cross-fade.
|
|
805
|
-
|
|
806
|
-
|
|
850
|
+
cross-fade. The name is set by slideTo() on the moving element for the move only (2026-09-22): a static name on
|
|
851
|
+
every walk's current page made two walks on one page — the gallery — share it, and the browser aborts a
|
|
852
|
+
transition with a duplicate name. */
|
|
807
853
|
|
|
808
854
|
/* ---- pages: the cards of a walk, all one height ----
|
|
809
855
|
A sequence the member walks (an introduction) reads steadier when every card
|
|
@@ -935,7 +981,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
935
981
|
bottom, the headroom above is the base's (block below).
|
|
936
982
|
Direct children only, five at most (slots are :nth-child below):
|
|
937
983
|
first = the back of the stack, last = the front. README "Sheet stack
|
|
938
|
-
markup".
|
|
984
|
+
markup". The SheetStack component opens and closes a --tap stack; without it the
|
|
939
985
|
stack still renders, it just won't open.
|
|
940
986
|
============================================================ */
|
|
941
987
|
|
|
@@ -948,14 +994,14 @@ button { font: inherit; cursor: pointer; }
|
|
|
948
994
|
track is being sized), so a long sheet grew the track past the screen and
|
|
949
995
|
every sheet's 100% then resolved against that — the bottom of every open
|
|
950
996
|
sheet sat below the screen, unreachable (timebank home, 2026-09-13). */
|
|
951
|
-
.sheet-stack { display: grid; grid-template-rows: minmax(0, 1fr); --stack-step: 5.5rem; }
|
|
952
|
-
|
|
953
997
|
/* the stack breaks out of the screen inset, edge to edge, always: the peek
|
|
954
998
|
strips and the opened sheet span the screen's full width whatever the
|
|
955
999
|
viewport (it used to bleed only under a 27em media query — a stack in a
|
|
956
1000
|
phone frame on a desktop kept the inset, and read as a narrower card;
|
|
957
|
-
Lh, 2026-09-11)
|
|
958
|
-
|
|
1001
|
+
Lh, 2026-09-11). --_n: how many sheets the stack holds, read from the DOM
|
|
1002
|
+
by :has below so the closed geometry needs no script and no per-stack
|
|
1003
|
+
declaration. */
|
|
1004
|
+
.sheet-stack { display: grid; grid-template-rows: minmax(0, 1fr); --stack-step: 5.5rem; margin-inline: calc(-1 * var(--screen-pad)); --_n: 1; }
|
|
959
1005
|
.sheet-stack > .card {
|
|
960
1006
|
grid-area: 1 / 1; /* every card shares the stack's cell; later ones offset down */
|
|
961
1007
|
border-bottom-left-radius: 0;
|
|
@@ -972,9 +1018,6 @@ button { font: inherit; cursor: pointer; }
|
|
|
972
1018
|
.sheet-stack > .card:not(:has(~ .card)) {
|
|
973
1019
|
align-self: stretch; /* the front card fills the remaining stack height */
|
|
974
1020
|
}
|
|
975
|
-
/* --_n: how many sheets the stack holds, read from the DOM by :has so the
|
|
976
|
-
closed geometry needs no script and no per-stack declaration */
|
|
977
|
-
.sheet-stack { --_n: 1; }
|
|
978
1021
|
.sheet-stack:has(> .card:nth-child(2)) { --_n: 2; }
|
|
979
1022
|
.sheet-stack:has(> .card:nth-child(3)) { --_n: 3; }
|
|
980
1023
|
.sheet-stack:has(> .card:nth-child(4)) { --_n: 4; }
|
|
@@ -1021,7 +1064,6 @@ button { font: inherit; cursor: pointer; }
|
|
|
1021
1064
|
/* fanned stack: a toggle opens the stack wider so every peek shows more */
|
|
1022
1065
|
.sheet-stack--fanned { --stack-step: 9.5rem; }
|
|
1023
1066
|
|
|
1024
|
-
.sheet-stack > .card:has(~ .card) { cursor: pointer; }
|
|
1025
1067
|
/* a peeked card in a plain stack hides its own body. A --tap stack does
|
|
1026
1068
|
NOT: there, every card is full height and full content always, and a
|
|
1027
1069
|
peek is simply the strip of it the next card doesn't cover — the way a
|
|
@@ -1067,9 +1109,10 @@ button { font: inherit; cursor: pointer; }
|
|
|
1067
1109
|
by n steps (clipped by .screen--fill), so translating it up by exactly
|
|
1068
1110
|
that much lands it filling the stack precisely. */
|
|
1069
1111
|
overflow: clip;
|
|
1070
|
-
transition: translate
|
|
1112
|
+
transition: translate var(--motion-sheet) ease; /* the only animated property here */
|
|
1071
1113
|
transition-delay: calc(var(--stagger, 0) * 70ms); /* 0 for everything
|
|
1072
1114
|
except a staggered is-before card, set in JS as it tucks or un-tucks */
|
|
1115
|
+
--_back-size: var(--stack-back-size, calc(var(--tap-base) * 2 / 3)); /* read once, shared by the back button and the head's padding below */
|
|
1073
1116
|
}
|
|
1074
1117
|
.sheet-stack--tap > .card.is-open {
|
|
1075
1118
|
/* up by its own resting offset — pure movement, nothing resizes */
|
|
@@ -1091,9 +1134,8 @@ button { font: inherit; cursor: pointer; }
|
|
|
1091
1134
|
overscroll-behavior: contain;
|
|
1092
1135
|
}
|
|
1093
1136
|
/* a scrolling sheet shows no scrollbar (a desktop with a mouse would paint one
|
|
1094
|
-
down the card's edge — Lh 2026-09-15); it still scrolls, like the page itself
|
|
1095
|
-
|
|
1096
|
-
.sheet-stack--tap > .card::-webkit-scrollbar { display: none; }
|
|
1137
|
+
down the card's edge — Lh 2026-09-15); it still scrolls, like the page itself
|
|
1138
|
+
(the hiding rule is base.css's, shared with every other scrolling track) */
|
|
1097
1139
|
.sheet-stack--tap > .card.is-before { translate: 0 var(--stack-step); }
|
|
1098
1140
|
.sheet-stack--tap > .card.is-after { translate: 0 100vh; }
|
|
1099
1141
|
|
|
@@ -1112,7 +1154,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
1112
1154
|
lands flush at the top. is-before tucks one step below its resting place.
|
|
1113
1155
|
============================================================ */
|
|
1114
1156
|
.sheet-stack--closed { --_headroom: calc(100% - var(--_n) * var(--stack-step)); }
|
|
1115
|
-
.sheet-stack--closed > .card { translate: 0 var(--_headroom);
|
|
1157
|
+
.sheet-stack--closed > .card { translate: 0 var(--_headroom); }
|
|
1116
1158
|
.sheet-stack--closed > .card.is-before { translate: 0 calc(var(--_headroom) + var(--stack-step)); }
|
|
1117
1159
|
.sheet-stack--closed > .card.is-open { translate: 0 calc(-1 * var(--slot, 0) * var(--stack-step)); }
|
|
1118
1160
|
.sheet-stack--closed > .card.is-after { translate: 0 100vh; }
|
|
@@ -1144,7 +1186,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
1144
1186
|
.sheet-stack--tap .card__head {
|
|
1145
1187
|
position: relative;
|
|
1146
1188
|
padding-left: 0;
|
|
1147
|
-
transition: padding-left
|
|
1189
|
+
transition: padding-left var(--motion-sheet) ease;
|
|
1148
1190
|
}
|
|
1149
1191
|
.sheet-stack--tap .sheet-stack__back {
|
|
1150
1192
|
position: absolute;
|
|
@@ -1153,10 +1195,10 @@ button { font: inherit; cursor: pointer; }
|
|
|
1153
1195
|
translate: 0 -50%;
|
|
1154
1196
|
opacity: 0;
|
|
1155
1197
|
visibility: hidden;
|
|
1156
|
-
transition: opacity
|
|
1198
|
+
transition: opacity var(--motion-sheet) ease, visibility var(--motion-sheet);
|
|
1157
1199
|
/* SMALL (see "the back button, SMALL" below) */
|
|
1158
|
-
width: var(--
|
|
1159
|
-
height: var(--
|
|
1200
|
+
width: var(--_back-size);
|
|
1201
|
+
height: var(--_back-size);
|
|
1160
1202
|
min-width: 0;
|
|
1161
1203
|
min-height: 0;
|
|
1162
1204
|
padding: 0;
|
|
@@ -1218,21 +1260,21 @@ button { font: inherit; cursor: pointer; }
|
|
|
1218
1260
|
/* ---- the back button, SMALL (Lh 2026-09-23: "give the sheet head more room" — and then "a glyph alone does not read as
|
|
1219
1261
|
tappable"): a SMALL DISC — its ring kept, 32px instead of 44, the chevron in it at 20 — so the head gives it 32px + a gap
|
|
1220
1262
|
(was 56px). Its tap target stays 44px: an invisible ::after around the disc. ---- */
|
|
1221
|
-
.sheet-stack--tap > .card.is-open > .card__head { padding-left: var(--stack-back-room, calc(var(--
|
|
1263
|
+
.sheet-stack--tap > .card.is-open > .card__head { padding-left: var(--stack-back-room, calc(var(--_back-size) + var(--sp-2))); }
|
|
1222
1264
|
.sheet-stack--tap .sheet-stack__back > .icon { width: var(--stack-back-icon, calc(var(--tap-base) * 13 / 24)); height: var(--stack-back-icon, calc(var(--tap-base) * 13 / 24)); flex: none; } /* 26: the chevron fills the small disc (Lh 2026-09-23) */
|
|
1223
|
-
.sheet-stack--tap .sheet-stack__back::after { content: ""; position: absolute; inset: calc((var(--
|
|
1265
|
+
.sheet-stack--tap .sheet-stack__back::after { content: ""; position: absolute; inset: calc((var(--_back-size) - var(--tap-sm)) / 2); }
|
|
1224
1266
|
|
|
1225
1267
|
/* the open sheet's title folds it (SheetStack.tsx): it says so to a pointer */
|
|
1226
1268
|
.sheet-stack--tap > .card.is-open > .card__head > :is(h1, h2, h3) { cursor: pointer; }
|
|
1227
1269
|
/* a stacked sheet is a control until it is open — tapped, never read into: no text selection (a drag, a double tap, a long press),
|
|
1228
1270
|
no grey flash, no iOS callout; its head stays one when open. Only the OPEN sheet's body is text to select (Lh 2026-09-23) */
|
|
1229
|
-
.sheet-stack--tap > .card, .sheet-stack > .card > .card__head { -webkit-user-select: none; user-select: none; -webkit-
|
|
1271
|
+
.sheet-stack--tap > .card, .sheet-stack > .card > .card__head { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; }
|
|
1230
1272
|
.sheet-stack--tap > .card.is-open > :not(.card__head) { -webkit-user-select: var(--screen-select, none); user-select: var(--screen-select, none); } /* the open sheet reads as the rest of the screen: not selectable (Lh 2026-09-25; it was text) */
|
|
1231
1273
|
|
|
1232
1274
|
/* ---- css/sheet.css — sheet single: .sheet — one card as a bottom sheet, peek / half / full ---- */
|
|
1233
1275
|
/* ============================================================
|
|
1234
1276
|
cardds/sheet.css — sheet single: one card as a bottom sheet.
|
|
1235
|
-
|
|
1277
|
+
Five states: .sheet--peek · .sheet--half · .sheet--3q · .sheet--full · .sheet--away
|
|
1236
1278
|
|
|
1237
1279
|
A sheet is rigid material, exactly like a sheet in .sheet-stack--tap
|
|
1238
1280
|
(stack.css): it is ALWAYS the full height of the box that holds it —
|
|
@@ -1273,8 +1315,8 @@ button { font: inherit; cursor: pointer; }
|
|
|
1273
1315
|
the body. The handle and the head stay where they are at every
|
|
1274
1316
|
state; the body is what scrolls — a sheet is a card whose head is
|
|
1275
1317
|
pinned, not a scrolling column with a title somewhere in it.
|
|
1276
|
-
|
|
1277
|
-
raise; without it every state still renders.
|
|
1318
|
+
The Sheet component adds the handle's click / drag / keyboard and the
|
|
1319
|
+
focus raise; without it every state still renders.
|
|
1278
1320
|
============================================================ */
|
|
1279
1321
|
|
|
1280
1322
|
.sheet {
|
|
@@ -1302,7 +1344,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
1302
1344
|
position: absolute;
|
|
1303
1345
|
inset: 0;
|
|
1304
1346
|
top: var(--_top, 0); /* a stage keeps its top bar's line free — see .sheet-stage */
|
|
1305
|
-
translate: 0 calc(var(--_y) + var(--_drag, 0rem)); /* --_drag:
|
|
1347
|
+
translate: 0 calc(var(--_y) + var(--_drag, 0rem)); /* --_drag: the Sheet component, only while the handle is held */
|
|
1306
1348
|
transition: translate var(--motion-sheet) ease;
|
|
1307
1349
|
|
|
1308
1350
|
/* handle · head · body: a column, the body takes what is left and scrolls
|
|
@@ -1313,6 +1355,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
1313
1355
|
border-radius: var(--r-card) var(--r-card) 0 0;
|
|
1314
1356
|
border-bottom: 0;
|
|
1315
1357
|
box-shadow: var(--sheet-shadow, var(--shadow-sheet));
|
|
1358
|
+
--_foot: calc(var(--card-gap) + var(--btn-h) + var(--card-pad)); /* the foot strip's height (below) — merged here, one bare .sheet rule */
|
|
1316
1359
|
}
|
|
1317
1360
|
.sheet--full { --_y: 0%; --_cover: 0rem; }
|
|
1318
1361
|
.sheet--half { --_y: calc(var(--sheet-half) * 100%); --_cover: calc(var(--sheet-half) * var(--_box)); }
|
|
@@ -1378,9 +1421,8 @@ button { font: inherit; cursor: pointer; }
|
|
|
1378
1421
|
takes no taps, its buttons do; the strip behind them is the sheet's ground, --_foot tall, and the body
|
|
1379
1422
|
stops that far above the sheet's edge. At peek there is only the handle and the title: the foot is gone.
|
|
1380
1423
|
One row, ≤ 2 buttons. <div class="card__foot"><button class="btn btn--primary btn--block">…</button></div> */
|
|
1381
|
-
.sheet { --_foot: calc(var(--card-gap) + var(--btn-h) + var(--card-pad)); }
|
|
1382
1424
|
/* …and the link row under the button, when there is one (its line + the gap over it) */
|
|
1383
|
-
.sheet:has(> .card__foot--link) { --_foot: calc(var(--card-gap) + var(--btn-h) + var(--sp-1) + calc(var(--tap-base) * 7 / 12) + var(--card-pad)); }
|
|
1425
|
+
.sheet:has(> .card__foot--link) { --_foot: calc(var(--card-gap) + var(--btn-h) + var(--sp-1) + var(--link-sm-min-h, calc(var(--tap-base) * 7 / 12)) + var(--card-pad)); }
|
|
1384
1426
|
.sheet > .card__foot {
|
|
1385
1427
|
position: absolute; inset: calc(-1 * var(--border-w)) 0 0; z-index: 1; /* the sheet's border box: the same height the sheet's own % reads */
|
|
1386
1428
|
align-items: end;
|
|
@@ -1406,7 +1448,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
1406
1448
|
/* grab handle — first child of the sheet. Drawn as a small bar, but it is
|
|
1407
1449
|
the thumb's grab area for the whole strip above the title: a pseudo widens
|
|
1408
1450
|
its hit box to --tap tall and wider than the bar without changing the
|
|
1409
|
-
picture. touch-action: none hands a touch drag to
|
|
1451
|
+
picture. touch-action: none hands a touch drag to the Sheet component instead of to
|
|
1410
1452
|
the scroller behind it. */
|
|
1411
1453
|
.sheet__handle {
|
|
1412
1454
|
position: relative;
|
|
@@ -1480,7 +1522,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
1480
1522
|
.screen > .sheet-stage { margin-inline: calc(-1 * var(--screen-pad)); width: auto; } /* auto: 100% would keep the inset width */
|
|
1481
1523
|
/* a stage laid OVER its screen (a Modal a link opened, Lh 2026-09-23): the page shows through, the stage covers it and its bar */
|
|
1482
1524
|
.sheet-stage--over, .screen > .sheet-stage--over {
|
|
1483
|
-
position: absolute; inset: 0; z-index:
|
|
1525
|
+
position: absolute; inset: 0; z-index: var(--z-top);
|
|
1484
1526
|
width: auto; height: auto; max-width: none; margin: 0;
|
|
1485
1527
|
background: transparent;
|
|
1486
1528
|
--_top: 0rem;
|
|
@@ -1537,7 +1579,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
1537
1579
|
translate: -50% -50%;
|
|
1538
1580
|
border-radius: 50%;
|
|
1539
1581
|
border: var(--border-w) solid var(--map-ring-border, var(--accent));
|
|
1540
|
-
background: var(--map-ring-bg,
|
|
1582
|
+
background: var(--map-ring-bg, var(--accent-soft));
|
|
1541
1583
|
box-shadow: var(--map-ring-shadow, var(--shadow-float));
|
|
1542
1584
|
pointer-events: none;
|
|
1543
1585
|
z-index: 1; /* above the engine's panes (Leaflet's tiles sit at 200, but inside their own stacking context) */
|
|
@@ -1571,9 +1613,6 @@ button { font: inherit; cursor: pointer; }
|
|
|
1571
1613
|
z-index: 1;
|
|
1572
1614
|
}
|
|
1573
1615
|
|
|
1574
|
-
/* raised: the sheet floats above a footnote strip on the scrim. Its bottom
|
|
1575
|
-
edge shows, so it gets its corners and border back; full still stops at
|
|
1576
|
-
the top bar's line, because the top edge never moved. */
|
|
1577
1616
|
/* EVERY sheet has one shape (Lh, 2026-09-21): the handle and the head stay at the top and never scroll · the
|
|
1578
1617
|
foot's button stands at the bottom, and ABOVE the keyboard when there is one · everything between them
|
|
1579
1618
|
scrolls, as far as it needs to. In a .step the keyboard's room is the step's (--kb, step.css); on a stage
|
|
@@ -1621,6 +1660,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
1621
1660
|
display: grid; grid-template-rows: auto 0fr; align-content: start; /* mid-fold the rows sum to less than the box: the spare room stays BELOW, never spread into the title row (Lh: it looked like a spring) */
|
|
1622
1661
|
flex: 0 0 auto;
|
|
1623
1662
|
transition: grid-template-rows var(--motion-sheet) ease, flex-grow var(--motion-sheet) ease;
|
|
1663
|
+
--_row-h: var(--sheet-steps-row-h, calc(var(--tap-base) * 2 / 3)); /* the bar's own floor, read once (__head and __go below share it) */
|
|
1624
1664
|
}
|
|
1625
1665
|
/* the open step GROWS into the room left, so the steps not done yet wait in a pile at the bottom — and a move is an ACCORDION:
|
|
1626
1666
|
in one beat the step that was open folds while the new one unfolds (flex-grow and the 1fr row both ease), the piles slide
|
|
@@ -1628,7 +1668,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
1628
1668
|
.sheet-step--now { grid-template-rows: auto 1fr; flex-grow: 1; }
|
|
1629
1669
|
.sheet-step + .sheet-step { margin-top: var(--sheet-steps-gap, var(--sp-1)); } /* the folded bars read as a pile, a little ground between them (Lh 2026-09-25) */
|
|
1630
1670
|
.sheet-step__head { /* every step's title line is a quiet BAR (a tint of the ink, not the brand — a brand bar read as a button, Lh 2026-09-25), the same height open or folded — opening shows the white room under it, never a taller bar (Lh 2026-09-25; was a thin rule on top) */
|
|
1631
|
-
display: flex; align-items: center; gap: var(--sp-3); min-height: var(--
|
|
1671
|
+
display: flex; align-items: center; gap: var(--sp-3); min-height: var(--_row-h); /* THIN: 32px, a summary line, not a button (Lh 2026-09-25) — the tap stays 44, see __go */
|
|
1632
1672
|
padding-inline: var(--sheet-step-bar-pad-x, var(--sp-3)); border-radius: var(--sheet-step-bar-radius, var(--r-chip));
|
|
1633
1673
|
background: var(--sheet-step-bar-bg, var(--ink-tint)); color: var(--sheet-step-bar-ink, inherit);
|
|
1634
1674
|
border-top: var(--sheet-step-border-w, 0rem) solid var(--sheet-step-border, currentColor); /* a rule instead of the bar: set this, the bar's bg transparent, radius 0 */
|
|
@@ -1646,7 +1686,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
1646
1686
|
.sheet-step__inner > * { min-width: 0; }
|
|
1647
1687
|
.sheet-step__inner > .link { justify-self: center; } /* a link of its own row in a step (skip, "change number", "set PIN again"): centred under the step, as the skip (Lh 2026-09-25) */
|
|
1648
1688
|
.sheet-step:not(.sheet-step--now) > .sheet-step__body { visibility: hidden; transition: visibility 0s linear var(--motion-sheet); }
|
|
1649
|
-
.sheet-step__go { position: absolute; inset: min(0rem, (var(--
|
|
1689
|
+
.sheet-step__go { position: absolute; inset: min(0rem, (var(--_row-h) - var(--tap-sm)) / 2) 0 auto; min-height: max(100%, var(--tap-sm)); /* a thin bar still takes a 44px tap */ margin: 0; cursor: pointer; }
|
|
1650
1690
|
/* the last step's finish, when the app gives one: under the steps */
|
|
1651
1691
|
.sheet-steps__finish { display: grid; padding-top: var(--card-gap); }
|
|
1652
1692
|
@media (prefers-reduced-motion: reduce) { .sheet-step { transition: none; } }
|
|
@@ -1715,9 +1755,6 @@ button { font: inherit; cursor: pointer; }
|
|
|
1715
1755
|
more air and a short one less; a screen with a bar keeps the bar's own room */
|
|
1716
1756
|
.screen > .step:first-child, .screen--lead > .step { --_lead: var(--screen-lead, 15cqh); } /* top="lead" says it; a Step first says it too */
|
|
1717
1757
|
|
|
1718
|
-
/* a walk (the dots float on its foot): the reading block runs right to the bar's top edge — the reserve's breathing gap (--sp-6)
|
|
1719
|
-
is given back, the dots keep the words clear of the bar (Lh 2026-09-23) */
|
|
1720
|
-
|
|
1721
1758
|
/* LAYERS (Lh, 2026-09-20): the base, then the sheet over it, then the top bar. A bar is the BASE's — the
|
|
1722
1759
|
move of a page of words — so a step with a sheet has NO bar: the sheet finishes on its own .card__foot
|
|
1723
1760
|
(sheet.css), and a form lives whole in ONE sheet, never item by item on the base. That is how cardds's
|
|
@@ -1737,8 +1774,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
1737
1774
|
--card-gap: var(--sp-4);
|
|
1738
1775
|
gap: var(--card-gap);
|
|
1739
1776
|
padding-block: 0; /* --_lead carries the top; the bar's room is the step's */
|
|
1740
|
-
align-content: start
|
|
1741
|
-
color: var(--on-surface);
|
|
1777
|
+
color: var(--on-surface); /* align-content: start is .page's own (base.css) — not restated here */
|
|
1742
1778
|
}
|
|
1743
1779
|
/* the reading block is the .page — or the .pages that holds several of them, a walk of words
|
|
1744
1780
|
(card.css). Behind a PEEKED sheet the words keep the strip's room under them, so whatever is down there
|
|
@@ -1747,22 +1783,25 @@ button { font: inherit; cursor: pointer; }
|
|
|
1747
1783
|
.step > :is(.page, .pages) {
|
|
1748
1784
|
flex: 0 1 auto;
|
|
1749
1785
|
min-height: 0;
|
|
1750
|
-
margin: var(--_lead, 0) var(--screen-pad) 0; /* the screen's gap, moved in here from above the step; the inset, taken back from the bleed */
|
|
1751
1786
|
overflow-y: auto;
|
|
1752
1787
|
overscroll-behavior: contain;
|
|
1753
1788
|
}
|
|
1789
|
+
/* a lone .page carries the margin a walk's .pages does not (its own .page children do, below) —
|
|
1790
|
+
the screen's gap, moved in here from above the step; the inset, taken back from the bleed.
|
|
1791
|
+
It ends with the bottom layer's room, as a walk's page does: it scrolls only when the layer would cover its words
|
|
1792
|
+
(timebank onboard-04, 2026-09-27 — its last Link lay under the bar; Lh: same rule as the walk) */
|
|
1793
|
+
.step > .page { margin: var(--_lead, 0) var(--screen-pad) 0; padding-bottom: var(--bottom-h, 0rem); }
|
|
1754
1794
|
|
|
1755
1795
|
/* a WALK (Pages of Page) — Lh 2026-09-23, third call: no top; the picture and the words are ONE run that scrolls up together
|
|
1756
1796
|
(the picture is never pinned). Each page starts at the screen's lead and ends with the same room under it — the lead as
|
|
1757
1797
|
padding above AND below — so the words begin near the middle and, when a big text size runs them long, scroll up clear.
|
|
1758
1798
|
The walk shows no scrollbar. */
|
|
1759
|
-
.step > .pages {
|
|
1799
|
+
.step > .pages { touch-action: pan-y; } /* pan-y: the walk scrolls itself up and down; a sideways swipe is the Walkthrough's (it turns the page) */ /* the scrolling box is the whole content: from the step's top edge, edge to edge — no margin, the default (Lh 2026-09-23) */
|
|
1760
1800
|
.step > .pages > .page {
|
|
1761
1801
|
padding-inline: var(--screen-pad); /* the words keep the inset INSIDE it */
|
|
1762
1802
|
margin-top: var(--_lead, 0rem); /* the lead is each PAGE's margin (15% with no top, the screen's gap under a bar): the words start there and scroll up to the box's edge */
|
|
1763
1803
|
padding-bottom: var(--bottom-h, 0rem); /* exactly the bottom layer (measured by Screen): a page scrolls ONLY when the layer would cover its words, and then just far enough to clear them — at 100% nothing scrolls (Lh 2026-09-23) */
|
|
1764
1804
|
}
|
|
1765
|
-
.step > .pages::-webkit-scrollbar { display: none; }
|
|
1766
1805
|
|
|
1767
1806
|
/* ============================================================
|
|
1768
1807
|
TOP + CONTENT in one flex column; BOTTOM is a LAYER over them (Lh 2026-09-23, second call — replaces "the bar is a row"):
|
|
@@ -1865,9 +1904,6 @@ button { font: inherit; cursor: pointer; }
|
|
|
1865
1904
|
display: inline-flex;
|
|
1866
1905
|
align-items: center;
|
|
1867
1906
|
gap: var(--add-row-gap, var(--sp-2));
|
|
1868
|
-
border: 0;
|
|
1869
|
-
background: none;
|
|
1870
|
-
color: inherit;
|
|
1871
1907
|
font: var(--add-row-font, var(--type-label));
|
|
1872
1908
|
font-size: var(--add-row-font-size, var(--fs-body));
|
|
1873
1909
|
padding: var(--add-row-pad-y, var(--sp-2)) var(--add-row-pad-x, 0);
|
|
@@ -1933,7 +1969,6 @@ button { font: inherit; cursor: pointer; }
|
|
|
1933
1969
|
.route__point--end .route__code::after { content: none; }
|
|
1934
1970
|
.route__time { font: var(--route-time-font, var(--type-body)); font-variant-numeric: tabular-nums; }
|
|
1935
1971
|
.route__place { font: var(--route-place-font, var(--type-caption)); color: var(--route-place-ink, var(--card-muted)); }
|
|
1936
|
-
.card--3 .route__place { color: inherit; opacity: var(--dim); }
|
|
1937
1972
|
|
|
1938
1973
|
.route__via { grid-column: 2; grid-row: 2; align-self: center; display: flex; align-items: center; opacity: 0.65; font-size: var(--route-via-font-size, var(--fs-body)); }
|
|
1939
1974
|
|
|
@@ -1965,7 +2000,6 @@ button { font: inherit; cursor: pointer; }
|
|
|
1965
2000
|
font: var(--note-row-font, var(--type-caption));
|
|
1966
2001
|
color: var(--note-row-ink, var(--card-muted));
|
|
1967
2002
|
}
|
|
1968
|
-
.card--3 .note-row { color: inherit; opacity: var(--dim); }
|
|
1969
2003
|
|
|
1970
2004
|
/* ---- css/actions.css — topbar, chips, buttons, CTA pack ---- */
|
|
1971
2005
|
/* ============================================================
|
|
@@ -2025,12 +2059,12 @@ button { font: inherit; cursor: pointer; }
|
|
|
2025
2059
|
max-width: var(--screen-w);
|
|
2026
2060
|
margin-inline: auto;
|
|
2027
2061
|
padding-inline: var(--appbar-pad-x, var(--screen-pad)); /* fixed + full-bleed: the screen's own inset, so the bar's contents line up with the title bar under it (Lh 2026-09-15) */
|
|
2028
|
-
z-index:
|
|
2062
|
+
z-index: var(--z-top); /* topmost — card animations never cover it */
|
|
2029
2063
|
background: var(--appbar-bg, var(--surface)); /* solid: sliding cards never show through */
|
|
2030
2064
|
}
|
|
2031
2065
|
/* fixed, so it covers the top of the screen it sits in — reserve its height
|
|
2032
|
-
there, from the same token
|
|
2033
|
-
|
|
2066
|
+
there, from the same header-h token. The breathing under it is published:
|
|
2067
|
+
a .step cancels it to touch the bar. */
|
|
2034
2068
|
.screen:has(> .appbar) { --header-gap: var(--sp-2); padding-top: calc(var(--header-h) + var(--header-gap)); }
|
|
2035
2069
|
.appbar__brand { flex: 1; display: flex; align-items: center; gap: var(--sp-2); min-width: 0; }
|
|
2036
2070
|
.appbar__text { display: grid; min-width: 0; flex: 1; } /* pushes trailing button to the right edge */
|
|
@@ -2038,11 +2072,9 @@ button { font: inherit; cursor: pointer; }
|
|
|
2038
2072
|
.appbar__who {
|
|
2039
2073
|
flex: 1; min-width: 0; min-height: var(--tap);
|
|
2040
2074
|
display: flex; align-items: center; gap: var(--sp-2);
|
|
2041
|
-
|
|
2075
|
+
margin: 0; text-align: start;
|
|
2042
2076
|
border-radius: var(--appbar-who-radius, var(--r-chip));
|
|
2043
|
-
-webkit-tap-highlight-color: transparent;
|
|
2044
2077
|
}
|
|
2045
|
-
.appbar__who:focus-visible { outline: var(--focus); outline-offset: var(--sp-1); }
|
|
2046
2078
|
.appbar__title, .appbar__sub { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; line-height: 1.35; } /* clipped lines: room for Thai marks */
|
|
2047
2079
|
.appbar__title { font: var(--type-label); }
|
|
2048
2080
|
.appbar__sub { font: var(--type-caption); color: var(--on-surface-muted); }
|
|
@@ -2066,7 +2098,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
2066
2098
|
.icon-btn--invert { background: var(--icon-btn-invert-bg, var(--accent)); color: var(--icon-btn-invert-ink, var(--on-accent)); border-color: transparent; }
|
|
2067
2099
|
.icon-btn--sm { width: var(--icon-btn-sm-size, var(--tap-sm)); height: var(--icon-btn-sm-size, var(--tap-sm)); font-size: var(--fs-title); }
|
|
2068
2100
|
/* fab: the square floating action, bottom-right of a stage or screen */
|
|
2069
|
-
.fab { width: var(--fab); height: var(--fab); border-radius: var(--r-card); position: absolute; right: var(--sp-4); bottom: calc(var(--sp-5) + var(--bar-h) + var(--sp-3)); box-shadow: var(--shadow-float); z-index:
|
|
2101
|
+
.fab { width: var(--fab); height: var(--fab); border-radius: var(--r-card); position: absolute; right: var(--sp-4); bottom: calc(var(--sp-5) + var(--bar-h) + var(--sp-3)); box-shadow: var(--shadow-float); z-index: var(--z-fab); }
|
|
2070
2102
|
|
|
2071
2103
|
/* ---- buttons ---- */
|
|
2072
2104
|
.btn {
|
|
@@ -2099,7 +2131,6 @@ button { font: inherit; cursor: pointer; }
|
|
|
2099
2131
|
/* disabled keeps the slot — a control that vanishes moves everything else,
|
|
2100
2132
|
and a pager whose "previous" disappeared on page one would shift the bar
|
|
2101
2133
|
under the thumb on every page */
|
|
2102
|
-
:is(.btn, .icon-btn):disabled { opacity: 0.4; cursor: default; }
|
|
2103
2134
|
/* a row of buttons sharing the width (twin CTAs inside a card) */
|
|
2104
2135
|
.btn-row { display: flex; gap: var(--sp-2); }
|
|
2105
2136
|
.btn-row > .btn { flex: 1; min-width: 0; }
|
|
@@ -2139,7 +2170,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
2139
2170
|
/* ---- dropdown: the standard for every select — a pill trigger + .menu popover ----
|
|
2140
2171
|
<div class="dropdown"><button class="dropdown__btn" popovertarget="f1"><span class="dropdown__label">this week</span><svg class="icon icon--xs"><use href="icons.svg#chevron-down"/></svg></button>
|
|
2141
2172
|
<div class="menu" id="f1" popover><button class="menu__item" aria-current="true" data-value="week">this week</button>…</div></div>
|
|
2142
|
-
|
|
2173
|
+
The Dropdown component copies the picked item into the label and reports the change.
|
|
2143
2174
|
.menu (layover.css) draws the panel; anchor positioning pins it under the trigger. */
|
|
2144
2175
|
.dropdown { position: relative; min-width: 0; display: inline-grid; }
|
|
2145
2176
|
.dropdown__btn {
|
|
@@ -2158,12 +2189,11 @@ button { font: inherit; cursor: pointer; }
|
|
|
2158
2189
|
font: var(--type-label);
|
|
2159
2190
|
font-size: var(--fs-body);
|
|
2160
2191
|
text-align: left;
|
|
2161
|
-
cursor: pointer;
|
|
2162
2192
|
}
|
|
2163
2193
|
/* muted: the value is still the default (README 13) */
|
|
2164
2194
|
.dropdown--muted .dropdown__btn { color: var(--dropdown-muted-ink, var(--card-muted)); }
|
|
2165
2195
|
.dropdown__label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
2166
|
-
.dropdown__btn > .icon { transition: rotate
|
|
2196
|
+
.dropdown__btn > .icon { transition: rotate var(--motion-quick) ease; }
|
|
2167
2197
|
.dropdown:has(.menu:popover-open) .dropdown__btn > .icon { rotate: 180deg; }
|
|
2168
2198
|
.dropdown:has(.menu.\:popover-open) .dropdown__btn > .icon { rotate: 180deg; } /* the polyfill's open (layover.css) */
|
|
2169
2199
|
.dropdown--end .menu[popover] { position-area: block-end span-inline-start; } /* right-aligned trigger: panel grows leftwards (= .menu--end, layover.css) */
|
|
@@ -2173,10 +2203,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
2173
2203
|
/* ---- segment (day picker / tab strip) — items share width, shrink on small screens ---- */
|
|
2174
2204
|
.segment { display: flex; gap: var(--sp-1); }
|
|
2175
2205
|
.segment__item {
|
|
2176
|
-
|
|
2177
|
-
background: transparent;
|
|
2178
|
-
color: inherit;
|
|
2179
|
-
opacity: 0.6;
|
|
2206
|
+
opacity: var(--rest);
|
|
2180
2207
|
border-radius: var(--r-chip);
|
|
2181
2208
|
padding: var(--sp-2) var(--sp-1);
|
|
2182
2209
|
font: var(--type-label);
|
|
@@ -2200,7 +2227,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
2200
2227
|
:root { --bar-reserve: calc(var(--btn-h) + var(--sp-3) + var(--sp-5) + var(--sp-6)); }
|
|
2201
2228
|
.action-bar {
|
|
2202
2229
|
position: relative;
|
|
2203
|
-
z-index:
|
|
2230
|
+
z-index: var(--z-bar); /* above .sheet-stack sheets, which paint at --level 1-5 */
|
|
2204
2231
|
max-width: var(--screen-w);
|
|
2205
2232
|
margin-inline: auto;
|
|
2206
2233
|
padding: var(--sp-3) var(--sp-4) var(--sp-5);
|
|
@@ -2230,10 +2257,11 @@ button { font: inherit; cursor: pointer; }
|
|
|
2230
2257
|
NEVER carry words (Lh 2026-09-16, for good): an arrow is the whole meaning,
|
|
2231
2258
|
and a word that isn't a direction ("Get started", "Done") is not a pager
|
|
2232
2259
|
button — that step COMMITS and goes back to the plain .action-bar with one
|
|
2233
|
-
wide button. Where you are is said by the .pager__at dots
|
|
2234
|
-
that changes (a .screen__centre group, card.css)
|
|
2235
|
-
|
|
2236
|
-
|
|
2260
|
+
wide button. Where you are is said by the .pager__at dots — under the card
|
|
2261
|
+
that changes (a .screen__centre group, card.css) for a card walk, never in
|
|
2262
|
+
that bar (Lh, 2026-09-12); a walk of words instead floats its dots as the
|
|
2263
|
+
bar's own first row (step.css, Lh 2026-09-23) — either way the dots move
|
|
2264
|
+
with what moves. Between the buttons goes a "skip" link or nothing.
|
|
2237
2265
|
|
|
2238
2266
|
<div class="action-bar action-bar--pager">
|
|
2239
2267
|
<button class="icon-btn" aria-label="ย้อนกลับ" disabled>…</button>
|
|
@@ -2250,14 +2278,14 @@ button { font: inherit; cursor: pointer; }
|
|
|
2250
2278
|
height: var(--dot);
|
|
2251
2279
|
border-radius: 50%;
|
|
2252
2280
|
background: currentColor;
|
|
2253
|
-
opacity:
|
|
2281
|
+
opacity: var(--faint);
|
|
2254
2282
|
}
|
|
2255
2283
|
.pager__at > .is-at { background: var(--accent); opacity: 1; }
|
|
2256
2284
|
/* ---- tiers: a control row above the buttons ----
|
|
2257
2285
|
A screen whose one move comes with a control — the introduction's text-size
|
|
2258
2286
|
slider above its pager. Each tier is a row; the bar stacks them, and any
|
|
2259
2287
|
parent hosting the bar publishes the room for both, the way the plain
|
|
2260
|
-
reserve is published above, so a .
|
|
2288
|
+
reserve is published above, so a .card--centre stops clear of it.
|
|
2261
2289
|
<div class="action-bar action-bar--tiers">
|
|
2262
2290
|
<div class="action-bar__tier"><label class="slider">…</label></div>
|
|
2263
2291
|
<div class="action-bar__tier action-bar__tier--pager">…the round pager…</div>
|
|
@@ -2304,28 +2332,56 @@ button { font: inherit; cursor: pointer; }
|
|
|
2304
2332
|
.action-note__words { display: block; } /* ONE run of words: an icon inside sits in the line, the spaces around it kept */
|
|
2305
2333
|
.action-note__words > .icon { display: inline-block; vertical-align: -0.2em; width: 1.25em; height: 1.25em; margin-inline: 0.15em; color: var(--action-note-icon-ink, inherit); } /* a project may colour the icon apart from the words — e.g. the system blue of the control it points at (Lh 2026-09-24) */
|
|
2306
2334
|
|
|
2307
|
-
/* PRESS (Lh 2026-09-25): every CONTROL answers a tap
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2335
|
+
/* PRESS (Lh 2026-09-25; every tappable thing, 2026-09-27): every CONTROL answers a tap, INFORMATION never does (no pointer, no
|
|
2336
|
+
press). Two answers, by size: GIVE — a control the size of a finger shrinks under it (--press-scale) and springs back; DIM —
|
|
2337
|
+
something as wide as its card (a row, a summary line, a card, a step bar, a peeked sheet) fades under the finger (--press-dim)
|
|
2338
|
+
instead, because a whole row shrinking reads as the card moving. A choice's fill fades in as it is picked. Disabled = no
|
|
2339
|
+
pointer, no press, --off. Zero specificity everywhere: a component's own rule always wins. A NEW control joins a list here. */
|
|
2340
|
+
:where(.btn, .icon-btn, .link, a.chip, button.chip, .chip--pick, a.tile, button.tile, .tile--pick, .day, .segment__item, .calendar__day,
|
|
2341
|
+
.float-bar__item, .avatar-pick__item, .add-row, .picker__item, .dropdown__btn, .appbar__who, .check, .toggle, .mood__opt, .sheet__handle,
|
|
2342
|
+
a.avatar, button.avatar, .menu__item) {
|
|
2311
2343
|
cursor: pointer;
|
|
2312
2344
|
touch-action: manipulation;
|
|
2313
2345
|
transition: scale var(--motion-press) ease-out;
|
|
2314
2346
|
}
|
|
2315
|
-
|
|
2316
|
-
:
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
:
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2347
|
+
:where(a.row, button.row, label.row, .row-expand > summary, .banner__summary, a.card, label.card, .toggle-field) {
|
|
2348
|
+
cursor: pointer;
|
|
2349
|
+
touch-action: manipulation;
|
|
2350
|
+
transition: opacity var(--motion-press) ease-out;
|
|
2351
|
+
}
|
|
2352
|
+
/* a CHOICE's fill fades in as it is picked (a plain button's colour never changes on a tap, so it does not fade) */
|
|
2353
|
+
:where(.chip--pick, button.chip, .tile--pick, button.tile, .day, .segment__item, .calendar__day, .float-bar__item, .picker__item, .check,
|
|
2354
|
+
.icon-btn[role="tab"], .avatar-pick__item > .avatar) {
|
|
2355
|
+
transition-property: scale, background-color, color, border-color, opacity;
|
|
2356
|
+
transition-duration: var(--motion-press);
|
|
2357
|
+
transition-timing-function: ease-out;
|
|
2358
|
+
}
|
|
2359
|
+
:where(.btn, .icon-btn, .link, a.chip, button.chip, .chip--pick, a.tile, button.tile, .tile--pick, .day, .segment__item, .calendar__day,
|
|
2360
|
+
.float-bar__item, .avatar-pick__item, .add-row, .picker__item, .dropdown__btn, .appbar__who, .check, .toggle, .mood__opt, .sheet__handle,
|
|
2361
|
+
a.avatar, button.avatar, .menu__item):active:not(:disabled, [aria-disabled="true"], :has(:disabled)) { scale: var(--press-scale); }
|
|
2362
|
+
:where(a.row, button.row, label.row, .row-expand > summary, .banner__summary, a.card, label.card, .toggle-field):active:not([aria-disabled="true"], :has(:disabled)) { opacity: var(--press-dim); }
|
|
2363
|
+
.sheet-step:has(> .sheet-step__go:active) { opacity: var(--press-dim); } /* the step bar's button is invisible over it: the bar answers */
|
|
2364
|
+
/* SHADE — what has something BEHIND it (a peeked sheet over the pile, a postcard's face over its back) must not turn see-through:
|
|
2365
|
+
it darkens a touch instead (--press-shade) */
|
|
2366
|
+
:where(.sheet-stack--tap > .card:not(.is-open), .postcard__turn) { cursor: pointer; touch-action: manipulation; transition: filter var(--motion-press) ease-out; }
|
|
2367
|
+
:where(.sheet-stack--tap > .card:not(.is-open), .postcard__turn):active { filter: brightness(var(--press-shade)); }
|
|
2368
|
+
/* the focus ring: on the control, or on the label that wraps a hidden input (chip, tile, file button, a row that is a label) */
|
|
2369
|
+
:where(.btn, .icon-btn, .link, a.chip, button.chip, .chip--pick, a.tile, button.tile, .tile--pick, .day, .segment__item, .calendar__day,
|
|
2370
|
+
.float-bar__item, .avatar-pick__item, .add-row, .picker__item, .dropdown__btn, .appbar__who, .check, .toggle, .mood__opt, .sheet__handle,
|
|
2371
|
+
a.avatar, button.avatar, .menu__item):focus-visible,
|
|
2372
|
+
:where(.chip--pick, .tile--pick, label.btn, label.row, .toggle-field):has(:focus-visible) { outline: var(--focus); outline-offset: var(--sp-1); }
|
|
2373
|
+
:where(a.row, button.row, label.row, .row-expand > summary, .banner__summary, a.card, label.card, .toggle-field):not(label, .toggle-field):focus-visible { outline: var(--focus); outline-offset: var(--sp-1); }
|
|
2374
|
+
/* disabled: no pointer anywhere; a control fades (a link and a row keep their own muted look) */
|
|
2375
|
+
:where(.btn, .icon-btn, .link, a.chip, button.chip, .chip--pick, a.tile, button.tile, .tile--pick, .day, .segment__item, .calendar__day,
|
|
2376
|
+
.float-bar__item, .avatar-pick__item, .add-row, .picker__item, .dropdown__btn, .appbar__who, .check, .toggle, .mood__opt, .sheet__handle,
|
|
2377
|
+
a.avatar, button.avatar, .menu__item, a.row, button.row, label.row, .row-expand > summary, .banner__summary, a.card, label.card, .toggle-field):is(:disabled, [aria-disabled="true"]),
|
|
2378
|
+
:where(.chip--pick, .tile--pick, .mood__opt, .toggle-field, label.btn, label.row):has(:disabled) { cursor: default; }
|
|
2379
|
+
:where(.btn, .icon-btn, .link, a.chip, button.chip, .chip--pick, a.tile, button.tile, .tile--pick, .day, .segment__item, .calendar__day,
|
|
2380
|
+
.float-bar__item, .avatar-pick__item, .add-row, .picker__item, .dropdown__btn, .appbar__who, .check, .toggle, .mood__opt, .sheet__handle,
|
|
2381
|
+
a.avatar, button.avatar, .menu__item):not(.link):is(:disabled, [aria-disabled="true"]),
|
|
2382
|
+
:where(.chip--pick, .tile--pick, .mood__opt, .toggle-field, label.btn):has(:disabled) { opacity: var(--off); }
|
|
2383
|
+
.sheet-stack--tap > .card > .card__head > :is(h1, h2, h3):focus-visible { outline: var(--focus); outline-offset: var(--sp-1); } /* the keyboard's way into a stack sheet */
|
|
2384
|
+
@media (prefers-reduced-motion: reduce) { :root { --press-scale: 1; } } /* the give goes; the dim, the shade and the focus ring stay */
|
|
2329
2385
|
|
|
2330
2386
|
/* ---- css/numbers.css — ring, track, band, dot grid, picker, badge, bars ---- */
|
|
2331
2387
|
/* ============================================================
|
|
@@ -2363,7 +2419,6 @@ button { font: inherit; cursor: pointer; }
|
|
|
2363
2419
|
}
|
|
2364
2420
|
.ring__value { font: var(--ring-value-font, var(--type-stat)); letter-spacing: var(--track-display); }
|
|
2365
2421
|
.ring__label { font: var(--ring-label-font, var(--type-caption)); color: var(--ring-label-ink, var(--card-muted)); }
|
|
2366
|
-
.card--3 .ring__label { color: inherit; opacity: var(--dim); }
|
|
2367
2422
|
.ring--sm { --_size: var(--avatar-xl); --_w: var(--sp-3); }
|
|
2368
2423
|
.ring--sm .ring__value { font: 800 var(--fs-2xl)/1 var(--font-display); }
|
|
2369
2424
|
.ring--xs { --_size: var(--tap-sm); --_w: var(--dot-xs); } /* inline counter (step 3 of 5) */
|
|
@@ -2402,7 +2457,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
2402
2457
|
content: "";
|
|
2403
2458
|
flex: 1;
|
|
2404
2459
|
height: var(--sp-3);
|
|
2405
|
-
background: color-mix(in srgb, var(--_ink) 30%, transparent)
|
|
2460
|
+
background: var(--ink-rail); /* the track's own ink at 30% — identical to color-mix(in srgb, var(--_ink) 30%, transparent): --ink-rail already reads currentColor, and .track sets color to --_ink */
|
|
2406
2461
|
}
|
|
2407
2462
|
.track__step:first-child::before { display: none; }
|
|
2408
2463
|
.track__step.is-done::before { background: var(--accent); }
|
|
@@ -2413,7 +2468,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
2413
2468
|
flex: none;
|
|
2414
2469
|
padding: calc((var(--tap-lg) - var(--icon)) / 2);
|
|
2415
2470
|
border-radius: 50%;
|
|
2416
|
-
background:
|
|
2471
|
+
background: var(--ink-rail);
|
|
2417
2472
|
color: var(--_ink);
|
|
2418
2473
|
}
|
|
2419
2474
|
.track__step > .track__dot { display: grid; place-items: center; padding: 0; font: var(--track-dot-font, var(--type-title)); font-variant-numeric: tabular-nums; } /* a number in the round (TrackStep num) */
|
|
@@ -2438,7 +2493,6 @@ button { font: inherit; cursor: pointer; }
|
|
|
2438
2493
|
}
|
|
2439
2494
|
.band__num small { font-size: 0.5em; font-weight: 700; margin-right: 0.1em; } /* unit or % */
|
|
2440
2495
|
.band__foot { grid-area: foot; font: var(--band-foot-font, var(--type-caption)); color: var(--band-foot-ink, var(--card-muted)); }
|
|
2441
|
-
.card--3 .band__foot { color: inherit; opacity: var(--dim); }
|
|
2442
2496
|
/* band stack: ONE card boundary; bands are frameless inside it, a hairline
|
|
2443
2497
|
between each. Bands may still carry slot tints (card--2 / card--3). */
|
|
2444
2498
|
.band-stack {
|
|
@@ -2485,14 +2539,6 @@ button { font: inherit; cursor: pointer; }
|
|
|
2485
2539
|
@supports not (width: round(up, 1px, 1px)) {
|
|
2486
2540
|
.dotgrid::before { display: none; }
|
|
2487
2541
|
}
|
|
2488
|
-
.dotgrid--stars {
|
|
2489
|
-
background-image: none;
|
|
2490
|
-
}
|
|
2491
|
-
.dotgrid--stars,
|
|
2492
|
-
.dotgrid--stars::before {
|
|
2493
|
-
--_star: polygon(50% 0, 62% 38%, 100% 50%, 62% 62%, 50% 100%, 38% 62%, 0 50%, 38% 38%);
|
|
2494
|
-
}
|
|
2495
|
-
|
|
2496
2542
|
/* ---- picker: horizontal snap picker, centre value big ----
|
|
2497
2543
|
<div class="picker"><div class="picker__rail"><button class="picker__item">100</button>…</div><span class="picker__unit">ml</span></div> */
|
|
2498
2544
|
.picker { display: grid; gap: var(--picker-gap, var(--sp-1)); justify-items: center; position: relative; }
|
|
@@ -2507,20 +2553,15 @@ button { font: inherit; cursor: pointer; }
|
|
|
2507
2553
|
grid-auto-columns: 100%;
|
|
2508
2554
|
overflow-x: auto;
|
|
2509
2555
|
scroll-snap-type: x mandatory;
|
|
2510
|
-
scrollbar-width: none;
|
|
2511
2556
|
position: relative;
|
|
2512
2557
|
}
|
|
2513
|
-
.picker__rail::-webkit-scrollbar { display: none; }
|
|
2514
2558
|
.picker__item {
|
|
2515
2559
|
scroll-snap-align: center;
|
|
2516
|
-
border: 0;
|
|
2517
|
-
background: transparent;
|
|
2518
|
-
color: inherit;
|
|
2519
2560
|
padding: var(--picker-item-pad-y, var(--sp-2)) var(--picker-item-pad-x, 0);
|
|
2520
2561
|
font: var(--picker-item-font, var(--type-stat));
|
|
2521
2562
|
letter-spacing: var(--track-display);
|
|
2522
2563
|
font-variant-numeric: tabular-nums;
|
|
2523
|
-
opacity:
|
|
2564
|
+
opacity: var(--faint);
|
|
2524
2565
|
text-align: center;
|
|
2525
2566
|
}
|
|
2526
2567
|
.picker__item[aria-selected="true"] { opacity: 1; }
|
|
@@ -2537,12 +2578,11 @@ button { font: inherit; cursor: pointer; }
|
|
|
2537
2578
|
}
|
|
2538
2579
|
.picker::after { left: auto; right: calc(100% / 3); }
|
|
2539
2580
|
.picker__unit { font: var(--picker-unit-font, var(--type-caption)); color: var(--picker-unit-ink, var(--card-muted)); }
|
|
2540
|
-
.card--3 .picker__unit { color: inherit; opacity: var(--dim); }
|
|
2541
2581
|
/* the scrolled-to item lights up by position — no JS */
|
|
2542
2582
|
@supports (animation-timeline: view(x)) {
|
|
2543
|
-
.picker__item {
|
|
2583
|
+
.picker__item { animation: picker-focus linear both; animation-timeline: view(x); } /* opacity: var(--faint) already set above — the animation alone is new here */
|
|
2544
2584
|
@keyframes picker-focus {
|
|
2545
|
-
0%, 100% { opacity:
|
|
2585
|
+
0%, 100% { opacity: var(--faint); }
|
|
2546
2586
|
45%, 55% { opacity: 1; }
|
|
2547
2587
|
}
|
|
2548
2588
|
}
|
|
@@ -2664,10 +2704,8 @@ button { font: inherit; cursor: pointer; }
|
|
|
2664
2704
|
display: flex;
|
|
2665
2705
|
gap: var(--avatar-pick-gap, var(--sp-4));
|
|
2666
2706
|
overflow-x: auto;
|
|
2667
|
-
scroll-snap-type: x proximity;
|
|
2668
|
-
scrollbar-width: none; /* starts at the card's padding edge, like every row */
|
|
2707
|
+
scroll-snap-type: x proximity; /* starts at the card's padding edge, like every row */
|
|
2669
2708
|
}
|
|
2670
|
-
.avatar-pick::-webkit-scrollbar { display: none; }
|
|
2671
2709
|
.avatar-pick__item {
|
|
2672
2710
|
scroll-snap-align: start;
|
|
2673
2711
|
flex: none;
|
|
@@ -2675,18 +2713,13 @@ button { font: inherit; cursor: pointer; }
|
|
|
2675
2713
|
display: grid;
|
|
2676
2714
|
justify-items: center;
|
|
2677
2715
|
gap: var(--avatar-pick-item-gap, var(--sp-2));
|
|
2678
|
-
border: 0;
|
|
2679
|
-
background: transparent;
|
|
2680
|
-
color: inherit;
|
|
2681
|
-
padding: 0;
|
|
2682
2716
|
text-align: center;
|
|
2683
2717
|
}
|
|
2684
2718
|
.avatar-pick__name { font: var(--avatar-pick-name-font, var(--type-label)); font-size: var(--avatar-pick-name-font-size, var(--fs-sm)); }
|
|
2685
2719
|
.avatar-pick__role { font: var(--avatar-pick-role-font, var(--type-caption)); color: var(--avatar-pick-role-ink, var(--card-muted)); margin-top: calc(-1 * var(--sp-1)); }
|
|
2686
2720
|
.avatar-pick__item[aria-pressed="true"] > .avatar { background: var(--avatar-pick-on-bg, var(--accent)); color: var(--avatar-pick-on-ink, var(--on-accent)); border-color: transparent; } /* the picked person */
|
|
2687
|
-
.card--3 .avatar-pick__role { color: inherit; opacity: var(--dim); }
|
|
2688
2721
|
|
|
2689
|
-
/* ---- css/choice.css — check, toggle,
|
|
2722
|
+
/* ---- css/choice.css — check, toggle, chip grid, days, calendar, mood, pin, composer ---- */
|
|
2690
2723
|
/* ============================================================
|
|
2691
2724
|
cardds/choice.css — thumb-size choice controls inside cards:
|
|
2692
2725
|
check circle · toggle · toggle chip · chip grid · day strip ·
|
|
@@ -2710,7 +2743,6 @@ button { font: inherit; cursor: pointer; }
|
|
|
2710
2743
|
background: var(--check-bg, var(--action-bg));
|
|
2711
2744
|
display: grid;
|
|
2712
2745
|
place-items: center;
|
|
2713
|
-
cursor: pointer;
|
|
2714
2746
|
}
|
|
2715
2747
|
.check::after {
|
|
2716
2748
|
content: "";
|
|
@@ -2723,15 +2755,14 @@ button { font: inherit; cursor: pointer; }
|
|
|
2723
2755
|
}
|
|
2724
2756
|
.check:checked { background: var(--accent); border-color: transparent; }
|
|
2725
2757
|
.check:checked::after { opacity: 1; }
|
|
2726
|
-
.check:focus-visible { outline: var(--focus); outline-offset: var(--sp-1); }
|
|
2727
2758
|
.check--sm { width: var(--check-sm-size, var(--tap-sm)); height: var(--check-sm-size, var(--tap-sm)); }
|
|
2728
2759
|
|
|
2729
2760
|
/* ---- toggle: big switch ----
|
|
2730
2761
|
<input type="checkbox" role="switch" class="toggle" aria-label="Private"> */
|
|
2731
2762
|
.toggle {
|
|
2732
2763
|
--_w: var(--bar-h); /* 64 × 36, knob = height − 2 × pad */
|
|
2733
|
-
--_h:
|
|
2734
|
-
--_pad:
|
|
2764
|
+
--_h: calc(var(--tap-base) * 3 / 4);
|
|
2765
|
+
--_pad: calc(var(--tap-base) / 16);
|
|
2735
2766
|
appearance: none;
|
|
2736
2767
|
width: var(--_w);
|
|
2737
2768
|
height: var(--_h);
|
|
@@ -2741,8 +2772,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
2741
2772
|
border: var(--border-w) solid var(--toggle-border, var(--outline));
|
|
2742
2773
|
background: var(--toggle-bg, var(--action-bg));
|
|
2743
2774
|
position: relative;
|
|
2744
|
-
|
|
2745
|
-
transition: background 0.2s ease;
|
|
2775
|
+
transition: background-color var(--motion-press) ease-out, border-color var(--motion-press) ease-out;
|
|
2746
2776
|
}
|
|
2747
2777
|
.toggle::after {
|
|
2748
2778
|
content: "";
|
|
@@ -2754,11 +2784,10 @@ button { font: inherit; cursor: pointer; }
|
|
|
2754
2784
|
border-radius: 50%;
|
|
2755
2785
|
background: currentColor;
|
|
2756
2786
|
opacity: 0.45;
|
|
2757
|
-
transition: translate
|
|
2787
|
+
transition: translate var(--motion-quick) ease, opacity var(--motion-quick) ease;
|
|
2758
2788
|
}
|
|
2759
2789
|
.toggle:checked { background: var(--accent); border-color: transparent; }
|
|
2760
2790
|
.toggle:checked::after { translate: calc(var(--_w) - var(--_h)) 0; background: var(--on-accent); opacity: 1; }
|
|
2761
|
-
.toggle:focus-visible { outline: var(--focus); outline-offset: var(--sp-1); }
|
|
2762
2791
|
|
|
2763
2792
|
/* ---- chip pick: a chip that is a choice — a native radio/checkbox inside the label, the picked one filled with accent.
|
|
2764
2793
|
Full-width in a .chip-grid (one slot / many options), or hugging its word on a .chip-row with --sign (an interest
|
|
@@ -2774,11 +2803,12 @@ button { font: inherit; cursor: pointer; }
|
|
|
2774
2803
|
chip grid the whole point is equal, full-width pick targets, so stretch.
|
|
2775
2804
|
Matters most in scripts without word spaces (Thai): min-content is the
|
|
2776
2805
|
whole string, tracks get wide, and hugging chips leave a ragged grid. */
|
|
2777
|
-
.chip-grid > .chip { justify-self: stretch; justify-content: center; min-height: var(--btn-h); padding-inline: var(--sp-2);
|
|
2778
|
-
.chip--pick {
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
.chip--pick
|
|
2806
|
+
.chip-grid > .chip { justify-self: stretch; justify-content: center; min-height: var(--btn-h); padding-inline: var(--sp-2); }
|
|
2807
|
+
.chip--pick { position: relative; } /* the input covers the whole chip, on a row as in a grid */
|
|
2808
|
+
/* the hidden input, the same shape wherever a native radio/checkbox drives a picked look:
|
|
2809
|
+
a chip, a mood dot, a tile (media.css) */
|
|
2810
|
+
:is(.chip--pick, .mood__opt, .tile--pick) > input { appearance: none; position: absolute; inset: 0; margin: 0; opacity: 0; cursor: inherit; }
|
|
2811
|
+
:is(.chip--pick, .tile--pick):has(:checked) { background: var(--accent); color: var(--on-accent); border-color: transparent; }
|
|
2782
2812
|
/* --sign: the trailing + that turns 45° into an × when picked — one mask, one rotate, no swap (Lh 2026-09-16) */
|
|
2783
2813
|
.chip--sign { padding-right: var(--sp-2); }
|
|
2784
2814
|
.chip--sign::after {
|
|
@@ -2811,11 +2841,8 @@ button { font: inherit; cursor: pointer; }
|
|
|
2811
2841
|
padding-block: var(--day-pad-y, var(--sp-3)); /* equal air top and bottom, so the selected pill
|
|
2812
2842
|
is the same shape on every day */
|
|
2813
2843
|
padding-inline: 0;
|
|
2814
|
-
border: 0;
|
|
2815
2844
|
border-radius: var(--day-radius, var(--r-chip));
|
|
2816
|
-
|
|
2817
|
-
color: inherit;
|
|
2818
|
-
opacity: 0.6;
|
|
2845
|
+
opacity: var(--rest);
|
|
2819
2846
|
}
|
|
2820
2847
|
.day__name { font: var(--day-name-font, var(--type-caption)); }
|
|
2821
2848
|
.day__num { font: 700 var(--fs-lg)/1.1 var(--font-display); font-variant-numeric: tabular-nums; }
|
|
@@ -2828,17 +2855,13 @@ button { font: inherit; cursor: pointer; }
|
|
|
2828
2855
|
.calendar__head { display: flex; align-items: center; justify-content: space-between; gap: var(--calendar-head-gap, var(--sp-2)); }
|
|
2829
2856
|
.calendar__grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: var(--calendar-grid-gap, var(--sp-1)); justify-items: center; }
|
|
2830
2857
|
.calendar__wd { font: var(--calendar-wd-font, var(--type-caption)); color: var(--calendar-wd-ink, var(--card-muted)); padding-block: var(--calendar-wd-pad-y, var(--sp-2)); }
|
|
2831
|
-
.card--3 .calendar__wd { color: inherit; opacity: var(--dim); }
|
|
2832
2858
|
.calendar__day {
|
|
2833
2859
|
width: 100%;
|
|
2834
2860
|
max-width: var(--calendar-day-max-w, var(--tap));
|
|
2835
2861
|
aspect-ratio: 1;
|
|
2836
2862
|
display: grid;
|
|
2837
2863
|
place-items: center;
|
|
2838
|
-
border: 0;
|
|
2839
2864
|
border-radius: 50%;
|
|
2840
|
-
background: transparent;
|
|
2841
|
-
color: inherit;
|
|
2842
2865
|
font: 500 var(--fs-title)/1 var(--font-ui);
|
|
2843
2866
|
font-variant-numeric: tabular-nums;
|
|
2844
2867
|
grid-column-start: var(--start, auto); /* --start on the 1st: weekday column 1–7 */
|
|
@@ -2861,8 +2884,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
2861
2884
|
display: flex;
|
|
2862
2885
|
justify-content: space-between;
|
|
2863
2886
|
}
|
|
2864
|
-
.mood__opt { flex: 1 1 0; max-width: var(--mood-opt-max-w, var(--tap)); min-width: 0; height: var(--mood-opt-h, var(--tap)); display: grid; place-items: center; position: relative;
|
|
2865
|
-
.mood__opt > input { appearance: none; position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; }
|
|
2887
|
+
.mood__opt { flex: 1 1 0; max-width: var(--mood-opt-max-w, var(--tap)); min-width: 0; height: var(--mood-opt-h, var(--tap)); display: grid; place-items: center; position: relative; }
|
|
2866
2888
|
.mood__dot {
|
|
2867
2889
|
width: var(--mood-dot-size, var(--icon-xs));
|
|
2868
2890
|
height: var(--mood-dot-size, var(--icon-xs));
|
|
@@ -2893,12 +2915,11 @@ button { font: inherit; cursor: pointer; }
|
|
|
2893
2915
|
text-align: center;
|
|
2894
2916
|
font: var(--pin-cell-font, var(--type-stat));
|
|
2895
2917
|
font-variant-numeric: tabular-nums;
|
|
2918
|
+
caret-color: transparent; /* the cell the cursor waits in (Lh 2026-09-22): NO outline ring — it sat outside the pill and got cropped by the sheet's
|
|
2919
|
+
edge. The sign is INSIDE the pill: a full-ink border and a blinking bar drawn where the digit will land (the native caret
|
|
2920
|
+
is hidden — its height and place are the browser's; the pill's bar is one rule). A filled cell selects its digit instead. */
|
|
2896
2921
|
}
|
|
2897
2922
|
.pin__cell:not(:placeholder-shown) { color: var(--pin-cell-filled-ink, var(--accent)); } /* a typed digit: coloured words on the cell's own ground — no fill (Lh 2026-09-24; was an accent-filled pill) */
|
|
2898
|
-
/* the cell the cursor waits in (Lh 2026-09-22): NO outline ring — it sat outside the pill and got cropped by the sheet's
|
|
2899
|
-
edge. The sign is INSIDE the pill: a full-ink border and a blinking bar drawn where the digit will land (the native caret
|
|
2900
|
-
is hidden — its height and place are the browser's; the pill's bar is one rule). A filled cell selects its digit instead. */
|
|
2901
|
-
.pin__cell { caret-color: transparent; }
|
|
2902
2923
|
.pin--secret .pin__cell { -webkit-text-security: disc; text-security: disc; } /* a PIN: a dot per digit, the number pad kept (Lh 2026-09-25) */
|
|
2903
2924
|
.pin__cell:focus { outline: none; border-color: var(--pin-cell-active-border, currentColor); }
|
|
2904
2925
|
.pin__cell:focus:placeholder-shown {
|
|
@@ -2928,10 +2949,9 @@ button { font: inherit; cursor: pointer; }
|
|
|
2928
2949
|
letter-spacing: var(--track-h2);
|
|
2929
2950
|
outline: none;
|
|
2930
2951
|
}
|
|
2931
|
-
.composer__input::placeholder { color: inherit; opacity:
|
|
2952
|
+
.composer__input::placeholder { color: inherit; opacity: var(--off); }
|
|
2932
2953
|
.composer__bar { display: flex; align-items: center; justify-content: space-between; gap: var(--composer-bar-gap, var(--sp-3)); }
|
|
2933
2954
|
.composer__hint { font: var(--composer-hint-font, var(--type-caption)); color: var(--composer-hint-ink, var(--card-muted)); }
|
|
2934
|
-
.card--3 .composer__hint { color: inherit; opacity: var(--dim); }
|
|
2935
2955
|
|
|
2936
2956
|
/* ---- slider: one value along a track, a thumb-size knob ----
|
|
2937
2957
|
<label class="slider"><span class="slider__label">ขนาดตัวหนังสือ</span><input class="slider__range" type="range" min="100" max="160" step="15" aria-label="ขนาดตัวหนังสือ"></label>
|
|
@@ -2942,8 +2962,8 @@ button { font: inherit; cursor: pointer; }
|
|
|
2942
2962
|
.slider { display: grid; gap: var(--slider-gap, var(--sp-1)); flex: 1; min-width: 0; position: relative; } /* relative: the glyph rides the knob */
|
|
2943
2963
|
/* ---- the sample glyph: one letter IN the knob, on-accent, at the type size
|
|
2944
2964
|
the slider sets — so a text-size slider shows the size before anything is
|
|
2945
|
-
read (Lh, 2026-09-12).
|
|
2946
|
-
as --slider-at (0..1) from the input's value; the glyph sits over the knob
|
|
2965
|
+
read (Lh, 2026-09-12). The Slider component publishes the knob's place on
|
|
2966
|
+
the .slider as --slider-at (0..1) from the input's value; the glyph sits over the knob
|
|
2947
2967
|
and never takes the pointer. <span class="slider__glyph" aria-hidden="true">ก</span>
|
|
2948
2968
|
before the input. */
|
|
2949
2969
|
.slider__glyph {
|
|
@@ -2993,6 +3013,8 @@ button { font: inherit; cursor: pointer; }
|
|
|
2993
3013
|
box-shadow: var(--shadow-float);
|
|
2994
3014
|
}
|
|
2995
3015
|
.slider__range:focus-visible { outline: var(--focus); outline-offset: var(--sp-1); }
|
|
3016
|
+
.slider__range:active::-webkit-slider-thumb { scale: var(--press-scale); } /* the knob gives under the finger like any control */
|
|
3017
|
+
.slider__range:active::-moz-range-thumb { scale: var(--press-scale); }
|
|
2996
3018
|
|
|
2997
3019
|
/* ---- a switch that names itself (Toggle text, Lh 2026-09-23 — "อยู่บ้าน" on the home's top): the words, then the switch,
|
|
2998
3020
|
one label — the whole of it is the tap target ---- */
|
|
@@ -3000,8 +3022,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
3000
3022
|
display: inline-flex; align-items: center; gap: var(--toggle-field-gap, var(--sp-2));
|
|
3001
3023
|
min-height: var(--tap);
|
|
3002
3024
|
font: var(--toggle-field-font, var(--type-label));
|
|
3003
|
-
|
|
3004
|
-
-webkit-user-select: none; user-select: none; -webkit-tap-highlight-color: transparent;
|
|
3025
|
+
-webkit-user-select: none; user-select: none;
|
|
3005
3026
|
flex: none;
|
|
3006
3027
|
}
|
|
3007
3028
|
|
|
@@ -3048,7 +3069,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
3048
3069
|
who liked a postcard, the rest of a list. An end chevron turns when open. The app owns the list.
|
|
3049
3070
|
<details class="row-expand"><summary class="row">…lead · body · end…</summary><div class="row-expand__more">…</div></details> */
|
|
3050
3071
|
.row-expand { display: block; }
|
|
3051
|
-
.row-expand > summary { list-style: none;
|
|
3072
|
+
.row-expand > summary { list-style: none; }
|
|
3052
3073
|
.row-expand > summary::-webkit-details-marker { display: none; }
|
|
3053
3074
|
.row-expand > summary > .icon:last-child { transition: rotate var(--motion-slide); }
|
|
3054
3075
|
.row-expand[open] > summary > .icon:last-child { rotate: 180deg; }
|
|
@@ -3200,14 +3221,9 @@ button { font: inherit; cursor: pointer; }
|
|
|
3200
3221
|
align-items: center;
|
|
3201
3222
|
gap: var(--link-gap, var(--sp-1));
|
|
3202
3223
|
min-height: var(--link-min-h, var(--tap-sm));
|
|
3203
|
-
color: inherit;
|
|
3204
3224
|
text-decoration: none;
|
|
3205
3225
|
font: var(--link-font, var(--type-label));
|
|
3206
3226
|
font-size: var(--link-font-size, var(--fs-body));
|
|
3207
|
-
border: 0;
|
|
3208
|
-
background: transparent;
|
|
3209
|
-
padding: 0;
|
|
3210
|
-
cursor: pointer;
|
|
3211
3227
|
}
|
|
3212
3228
|
/* a SMALL link (Lh 2026-09-23): a thing tapped rarely — "details", a ctaLink — at the minor-link size (15) and 28px of height;
|
|
3213
3229
|
its 44px tap target stays, an invisible ::after around it */
|
|
@@ -3234,7 +3250,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
3234
3250
|
--card-ink: var(--card-3-ink);
|
|
3235
3251
|
--accent: var(--card-3-ink);
|
|
3236
3252
|
--on-accent: var(--card-3-bg);
|
|
3237
|
-
min-height:
|
|
3253
|
+
min-height: var(--_cover-min-h);
|
|
3238
3254
|
align-content: space-between;
|
|
3239
3255
|
overflow: hidden;
|
|
3240
3256
|
isolation: isolate;
|
|
@@ -3274,7 +3290,6 @@ button { font: inherit; cursor: pointer; }
|
|
|
3274
3290
|
.quote__text { margin: 0; font: var(--quote-text-font, var(--type-h1)); letter-spacing: var(--track-h1); text-wrap: balance; }
|
|
3275
3291
|
.quote--sm .quote__text { font: var(--type-h2); letter-spacing: var(--track-h2); }
|
|
3276
3292
|
.quote__by { font: var(--quote-by-font, var(--type-caption)); color: var(--quote-by-ink, var(--card-muted)); }
|
|
3277
|
-
.card--3 .quote__by { color: inherit; opacity: var(--dim); }
|
|
3278
3293
|
|
|
3279
3294
|
/* ---- tile grid: big square tiles — icon top, label bottom ----
|
|
3280
3295
|
<div class="tile-grid"><button class="tile"><svg class="icon"/><span class="tile__label">Light</span></button>
|
|
@@ -3306,13 +3321,10 @@ button { font: inherit; cursor: pointer; }
|
|
|
3306
3321
|
.tile__sub { font: var(--tile-sub-font, var(--type-caption)); opacity: var(--dim); }
|
|
3307
3322
|
.tile__text { display: grid; gap: var(--tile-text-gap, var(--sp-0)); }
|
|
3308
3323
|
.tile--on { background: var(--tile-on-bg, var(--accent)); color: var(--tile-on-ink, var(--on-accent)); border-color: transparent; }
|
|
3309
|
-
.tile--pick
|
|
3310
|
-
.tile--pick:has(:checked) { background: var(--accent); color: var(--on-accent); border-color: transparent; }
|
|
3311
|
-
.tile--pick:has(:focus-visible) { outline: var(--focus); outline-offset: var(--sp-1); }
|
|
3324
|
+
/* .tile--pick's hidden input and :has(:checked) fill are declared once in choice.css, alongside .chip--pick and .mood__opt */
|
|
3312
3325
|
.tile--wide { grid-column: 1 / -1; }
|
|
3313
3326
|
/* stat: READ, not pressed — no frame of its own inside a card (a frame says "tap me"): icon · value · words on the card's ground */
|
|
3314
|
-
.tile--stat { border-color: transparent; background: none; padding-inline: 0; }
|
|
3315
|
-
.tile--stat { min-height: var(--tile-stat-min-h, var(--avatar-xl)); gap: var(--tile-stat-gap, var(--sp-3)); grid-template-columns: auto 1fr; align-items: center; }
|
|
3327
|
+
.tile--stat { border-color: transparent; background: none; padding-inline: 0; min-height: var(--tile-stat-min-h, var(--avatar-xl)); gap: var(--tile-stat-gap, var(--sp-3)); grid-template-columns: auto 1fr; align-items: center; }
|
|
3316
3328
|
/* upload: dashed, centred plus + label */
|
|
3317
3329
|
.tile--upload {
|
|
3318
3330
|
border-style: dashed;
|
|
@@ -3389,8 +3401,8 @@ button { font: inherit; cursor: pointer; }
|
|
|
3389
3401
|
overflow: hidden;
|
|
3390
3402
|
}
|
|
3391
3403
|
.mosaic > :first-child { grid-row: span 2; }
|
|
3392
|
-
.mosaic > * { min-height: 0; border-radius: 0;
|
|
3393
|
-
.mosaic > .ph { opacity:
|
|
3404
|
+
.mosaic > * { min-height: 0; border-radius: 0; } /* opacity/border-width here were no-ops: .ph restates its own below, and nothing else in a mosaic carries either by default */
|
|
3405
|
+
.mosaic > .ph { opacity: var(--off); border-width: var(--border-w); }
|
|
3394
3406
|
.mosaic > img { width: 100%; height: 100%; object-fit: cover; }
|
|
3395
3407
|
|
|
3396
3408
|
/* ---- postcard: a member's introduction in the shape of a cover card — the photo fills it,
|
|
@@ -3419,7 +3431,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
3419
3431
|
liked it, a voice note, a reply live OUTSIDE the postcard, in a Row under it. */
|
|
3420
3432
|
.postcard {
|
|
3421
3433
|
position: relative;
|
|
3422
|
-
min-height: var(--postcard-min-h,
|
|
3434
|
+
min-height: var(--postcard-min-h, var(--_cover-min-h));
|
|
3423
3435
|
perspective: 60rem;
|
|
3424
3436
|
border-radius: var(--postcard-radius, var(--r-card));
|
|
3425
3437
|
isolation: isolate;
|
|
@@ -3445,14 +3457,10 @@ button { font: inherit; cursor: pointer; }
|
|
|
3445
3457
|
display: grid;
|
|
3446
3458
|
align-content: start;
|
|
3447
3459
|
padding: var(--postcard-face-pad, var(--card-pad-rest));
|
|
3448
|
-
border: 0;
|
|
3449
3460
|
border-radius: inherit;
|
|
3450
|
-
background: transparent;
|
|
3451
|
-
color: inherit;
|
|
3452
3461
|
text-align: left;
|
|
3453
|
-
cursor: pointer;
|
|
3454
3462
|
}
|
|
3455
|
-
.postcard__turn:focus-visible { outline
|
|
3463
|
+
.postcard__turn:focus-visible { outline-offset: calc(-1 * var(--sp-1)); } /* the ring inside the card's edge */
|
|
3456
3464
|
/* the front: the photo (or the wireframe box) fills it; the band reads on a scrim */
|
|
3457
3465
|
.postcard__front { background: var(--postcard-front-bg, var(--card-3-bg)); color: var(--postcard-front-ink, var(--card-3-ink)); }
|
|
3458
3466
|
.postcard__front .postcard__turn > img,
|
|
@@ -3508,7 +3516,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
3508
3516
|
.postcard__face { transition: none; }
|
|
3509
3517
|
}
|
|
3510
3518
|
|
|
3511
|
-
/* ---- css/layover.css — ask drawer, popover menu, banner, float bar, callout
|
|
3519
|
+
/* ---- css/layover.css — ask drawer, popover menu, banner, float bar, callout ---- */
|
|
3512
3520
|
/* ============================================================
|
|
3513
3521
|
cardds/layover.css — cards that lay OVER other content:
|
|
3514
3522
|
Modal (ask) · popover menu · expanding banner · float bar ·
|
|
@@ -3535,10 +3543,11 @@ button { font: inherit; cursor: pointer; }
|
|
|
3535
3543
|
2. the drawer comes out of the card's bottom edge while the lift moves
|
|
3536
3544
|
up, both at once: the card grows from its centre, which never moves
|
|
3537
3545
|
(the modal centres itself — absolute, margin auto; a growing card keeps its middle where it is)
|
|
3538
|
-
Closing:
|
|
3539
|
-
reverse (
|
|
3540
|
-
|
|
3541
|
-
|
|
3546
|
+
Closing: <Modal open onClosed> — set open={false} and the Modal component plays the
|
|
3547
|
+
same animations in reverse (Animation.reverse() on each — nothing duplicated), then
|
|
3548
|
+
calls onClosed for the app to unmount the stage. Modal no longer knows its own
|
|
3549
|
+
buttons (data-answer / the "cardds:modal" event are gone; the app's own onClick
|
|
3550
|
+
sets open={false}).
|
|
3542
3551
|
Reduced motion: the finished state, no beats; closing fires at once. */
|
|
3543
3552
|
.modal {
|
|
3544
3553
|
--drawer-h: 14rem;
|
|
@@ -3617,8 +3626,8 @@ button { font: inherit; cursor: pointer; }
|
|
|
3617
3626
|
element the button belongs to (a card, a sheet), opened by an .icon-btn:
|
|
3618
3627
|
<button class="icon-btn icon-btn--sm" popovertarget="m1" aria-label="More">…</button>
|
|
3619
3628
|
<div class="menu menu--actions menu--end" id="m1" popover><button class="menu__item"><svg class="icon"/>Edit</button>…</div>
|
|
3620
|
-
No item stays current there
|
|
3621
|
-
trigger sits at the far right, so the panel grows leftwards. */
|
|
3629
|
+
No item stays current there — an actions menu's items carry no aria-current, unlike
|
|
3630
|
+
the dropdown's. .menu--end = the trigger sits at the far right, so the panel grows leftwards. */
|
|
3622
3631
|
.menu {
|
|
3623
3632
|
display: none; /* explicit closed state — never display on the base rule */
|
|
3624
3633
|
margin: 0;
|
|
@@ -3644,15 +3653,11 @@ button { font: inherit; cursor: pointer; }
|
|
|
3644
3653
|
min-height: var(--menu-item-min-h, var(--tap));
|
|
3645
3654
|
width: 100%;
|
|
3646
3655
|
padding: var(--menu-item-pad-y, 0) var(--menu-item-pad-x, var(--sp-3));
|
|
3647
|
-
border: 0;
|
|
3648
3656
|
/* concentric with the panel: the card radius minus the panel's padding, so the
|
|
3649
3657
|
highlight's curve follows the outer corner (Lh 2026-09-16) */
|
|
3650
3658
|
border-radius: calc(var(--r-card) - var(--menu-pad, var(--sp-2)));
|
|
3651
|
-
background: transparent;
|
|
3652
|
-
color: inherit;
|
|
3653
3659
|
font: var(--menu-item-font, var(--type-title));
|
|
3654
3660
|
text-align: left;
|
|
3655
|
-
cursor: pointer;
|
|
3656
3661
|
}
|
|
3657
3662
|
.menu__item:hover, .menu__item:focus-visible { background: var(--ink-tint); outline: none; }
|
|
3658
3663
|
.menu__item[aria-current] { background: var(--accent); color: var(--on-accent); }
|
|
@@ -3669,7 +3674,7 @@ button { font: inherit; cursor: pointer; }
|
|
|
3669
3674
|
.menu[popover] {
|
|
3670
3675
|
opacity: 0;
|
|
3671
3676
|
translate: 0 calc(-1 * var(--sp-2));
|
|
3672
|
-
transition: display
|
|
3677
|
+
transition: display var(--motion-quick) allow-discrete, overlay var(--motion-quick) allow-discrete, opacity var(--motion-quick), translate var(--motion-quick);
|
|
3673
3678
|
}
|
|
3674
3679
|
.menu:popover-open { opacity: 1; translate: 0 0; }
|
|
3675
3680
|
.menu.\:popover-open { opacity: 1; translate: 0 0; }
|
|
@@ -3690,19 +3695,19 @@ button { font: inherit; cursor: pointer; }
|
|
|
3690
3695
|
padding-inline: var(--card-pad, var(--screen-pad));
|
|
3691
3696
|
}
|
|
3692
3697
|
.banner__summary {
|
|
3693
|
-
list-style: none;
|
|
3698
|
+
list-style: none;
|
|
3694
3699
|
display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2);
|
|
3695
3700
|
padding-block: var(--banner-summary-pad-y, calc(var(--sp-base) * 1.5)); font: var(--banner-summary-font, var(--type-caption)); font-weight: 400;
|
|
3696
3701
|
}
|
|
3697
3702
|
.banner__summary::-webkit-details-marker { display: none; }
|
|
3698
|
-
.banner__summary .icon { transition: rotate
|
|
3703
|
+
.banner__summary .icon { transition: rotate var(--motion-quick) ease; }
|
|
3699
3704
|
.banner[open] .banner__summary .icon { rotate: 180deg; }
|
|
3700
3705
|
.banner__body { display: grid; gap: var(--banner-body-gap, var(--sp-1)); padding-bottom: var(--sp-4); }
|
|
3701
3706
|
.banner::details-content { width: 100%; }
|
|
3702
3707
|
@supports (interpolate-size: allow-keywords) {
|
|
3703
3708
|
@media (prefers-reduced-motion: no-preference) {
|
|
3704
3709
|
.banner { interpolate-size: allow-keywords; }
|
|
3705
|
-
.banner::details-content { block-size: 0; overflow: clip; transition: block-size
|
|
3710
|
+
.banner::details-content { block-size: 0; overflow: clip; transition: block-size var(--motion-quick) ease, content-visibility var(--motion-quick) allow-discrete; }
|
|
3706
3711
|
.banner[open]::details-content { block-size: auto; }
|
|
3707
3712
|
}
|
|
3708
3713
|
}
|
|
@@ -3719,16 +3724,12 @@ button { font: inherit; cursor: pointer; }
|
|
|
3719
3724
|
display: flex;
|
|
3720
3725
|
gap: var(--float-bar-gap, var(--sp-2));
|
|
3721
3726
|
align-items: center;
|
|
3722
|
-
z-index:
|
|
3727
|
+
z-index: var(--z-bar);
|
|
3723
3728
|
}
|
|
3724
3729
|
/* in a fill screen (its `bottom`) the float bar floats over the SCREEN's foot, not the window's — it lies over the content
|
|
3725
3730
|
(a map, a stack), so it is not a row of the column; the screen is its box (Lh 2026-09-23) */
|
|
3726
3731
|
.screen--fill > .float-bar { position: absolute; }
|
|
3727
|
-
.sheet-stage > .float-bar { position: absolute; padding-inline:
|
|
3728
|
-
/* .screen's default bottom padding clears an .action-bar; a .float-bar is
|
|
3729
|
-
taller (--bar-h + its own bottom offset), so reserve the difference. Any
|
|
3730
|
-
parent that hosts the bar counts (see .action-bar's reserve, actions.css) */
|
|
3731
|
-
:has(> .float-bar) .screen:not(.screen--fill) { padding-bottom: calc(var(--bar-h) + var(--sp-5) + var(--sp-6)); }
|
|
3732
|
+
.sheet-stage > .float-bar { position: absolute; } /* padding-inline removed 2026-09-27: it hard-coded --sp-4 over the --float-bar-pad-x knob, so the knob had no effect here */
|
|
3732
3733
|
.float-bar__pill {
|
|
3733
3734
|
flex: 1;
|
|
3734
3735
|
min-width: 0;
|
|
@@ -3753,13 +3754,10 @@ button { font: inherit; cursor: pointer; }
|
|
|
3753
3754
|
gap: var(--float-bar-item-gap, var(--sp-0));
|
|
3754
3755
|
min-height: var(--float-bar-item-min-h, var(--tap-lg));
|
|
3755
3756
|
padding: var(--float-bar-item-pad, var(--sp-1));
|
|
3756
|
-
border: 0;
|
|
3757
3757
|
border-radius: var(--float-bar-item-radius, var(--r-chip));
|
|
3758
|
-
background: transparent;
|
|
3759
|
-
color: inherit;
|
|
3760
3758
|
font: var(--float-bar-item-font, var(--type-caption));
|
|
3761
3759
|
text-decoration: none;
|
|
3762
|
-
opacity:
|
|
3760
|
+
opacity: var(--rest);
|
|
3763
3761
|
overflow: hidden;
|
|
3764
3762
|
white-space: nowrap;
|
|
3765
3763
|
text-overflow: ellipsis;
|