@usableapp/cardds 0.7.66 → 0.7.68
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 +24 -0
- package/README.md +60 -0
- package/cardds.css +2 -0
- package/css/actions.css +11 -11
- package/css/base.css +22 -0
- package/css/post.css +326 -0
- package/css/stamp.css +183 -0
- package/css/step.css +3 -0
- package/css/sticky.css +30 -4
- package/dist/cardds.css +579 -15
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/post/PostBox.d.ts +34 -0
- package/dist/post/PostBox.js +34 -0
- package/dist/post/PostDesk.d.ts +68 -0
- package/dist/post/PostDesk.js +81 -0
- package/dist/post/PostLetter.d.ts +52 -0
- package/dist/post/PostLetter.js +53 -0
- package/dist/post/Postmark.d.ts +12 -0
- package/dist/post/Postmark.js +9 -0
- package/dist/post/postCorner.d.ts +5 -0
- package/dist/post/postCorner.js +8 -0
- package/dist/scaffold/Page.d.ts +3 -1
- package/dist/scaffold/Page.js +2 -2
- package/dist/stamp/Stamp.d.ts +32 -0
- package/dist/stamp/Stamp.js +21 -0
- package/dist/stamp/StampBook.d.ts +19 -0
- package/dist/stamp/StampBook.js +17 -0
- package/dist/stamp/StampOn.d.ts +25 -0
- package/dist/stamp/StampOn.js +69 -0
- package/dist/sticky/Sticky.d.ts +3 -1
- package/dist/sticky/Sticky.js +2 -2
- package/dist/type/Mark.d.ts +11 -0
- package/dist/type/Mark.js +9 -0
- package/dist/type/icons.d.ts +1 -1
- package/dist/type/icons.js +2 -2
- package/dist/words.d.ts +1 -1
- package/dist/words.js +4 -2
- package/icons.svg +1 -0
- package/package.json +1 -1
package/dist/cardds.css
CHANGED
|
@@ -614,6 +614,28 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
614
614
|
.card .t-muted { color: var(--card-muted); } /* muted follows the card's ink, not the surface's */
|
|
615
615
|
/* .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 */
|
|
616
616
|
.t-caps { text-transform: uppercase; }
|
|
617
|
+
/* ---- mark: a highlighter stroke over a word INSIDE running text (a sender's name in a greeting) ----
|
|
618
|
+
<p class="t-body"><mark class="mark">ธนาคารเวลา</mark> ส่งแสตมป์ดวงแรกให้คุณ</p>
|
|
619
|
+
The words keep their ink and their size; the stroke is the signal washed thin, drawn as a pen would: unlike corners, the ink
|
|
620
|
+
darker at its ends and along its middle. It follows the words across a line break (each piece is its own stroke). Information, never a control. Green (ok) unless a tone says otherwise. */
|
|
621
|
+
.mark {
|
|
622
|
+
--_bg: color-mix(in srgb, var(--signal-ok) 30%, transparent);
|
|
623
|
+
--_pool: color-mix(in srgb, var(--mark-bg, var(--_bg)) 75%, transparent); /* the same ink again, thinner: where it lies twice it reads darker, as wet ink does */
|
|
624
|
+
--_shape: 0.75em 0.3em 0.9em 0.35em / 0.4em 0.95em 0.35em 0.8em; /* four unlike corners: a stroke drawn by hand has no two ends alike */
|
|
625
|
+
background-color: var(--mark-bg, var(--_bg));
|
|
626
|
+
background-image:
|
|
627
|
+
linear-gradient(100deg, var(--_pool), transparent 0.55em), /* ink pools where the pen lands */
|
|
628
|
+
linear-gradient(280deg, var(--_pool), transparent 0.4em), /* and a little where it lifts */
|
|
629
|
+
linear-gradient(181deg, transparent 18%, var(--_pool) 52%, transparent 88%); /* the nib presses harder along its middle */
|
|
630
|
+
color: var(--mark-ink, inherit);
|
|
631
|
+
padding-inline: var(--mark-pad-x, 0.3em);
|
|
632
|
+
border-radius: var(--mark-radius, var(--_shape));
|
|
633
|
+
-webkit-box-decoration-break: clone;
|
|
634
|
+
box-decoration-break: clone;
|
|
635
|
+
}
|
|
636
|
+
.mark--warn { --_bg: color-mix(in srgb, var(--signal-warn) 30%, transparent); }
|
|
637
|
+
.mark--error { --_bg: color-mix(in srgb, var(--signal-error) 30%, transparent); }
|
|
638
|
+
.mark--info { --_bg: color-mix(in srgb, var(--signal-info) 30%, transparent); }
|
|
617
639
|
.grow { flex: 1; min-width: 0; } /* the child that takes the remaining row space */
|
|
618
640
|
/* 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.
|
|
619
641
|
The 1px is the one the platform needs to keep it in the accessibility tree, not a length of the design. */
|
|
@@ -2056,6 +2078,9 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2056
2078
|
It ends with the bottom layer's room, as a walk's page does: it scrolls only when the layer would cover its words
|
|
2057
2079
|
(timebank onboard-04, 2026-09-27 — its last Link lay under the bar; Lh: same rule as the walk) */
|
|
2058
2080
|
.step > .page { margin: var(--_lead, 0) var(--screen-pad) 0; padding-bottom: var(--bottom-h, 0rem); }
|
|
2081
|
+
/* --bleed: the page's box runs to the screen's edges, the inset is its own padding — what leans, slides or casts a shadow past
|
|
2082
|
+
the inset (a postcard) is cut at the glass, not at the inset; a little room on top for a turned corner. Nothing pans sideways. */
|
|
2083
|
+
.step > .page--bleed { margin-inline: 0; padding-inline: var(--screen-pad); padding-block-start: var(--sp-2); overflow-x: hidden; }
|
|
2059
2084
|
|
|
2060
2085
|
/* 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
|
|
2061
2086
|
(the picture is never pinned). Each page starts at the screen's lead and ends with the same room under it — the lead as
|
|
@@ -2654,12 +2679,12 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2654
2679
|
pointer, no press, --off. Zero specificity everywhere: a component's own rule always wins. A NEW control joins a list here. */
|
|
2655
2680
|
:where(.btn, .icon-btn, .link, a.chip, button.chip, .chip--pick, a.tile, button.tile, .tile--pick, .day, .segment__item, .calendar__day,
|
|
2656
2681
|
.float-bar__item, .avatar-pick__item, .add-row, .picker__item, .dropdown__btn, .appbar__who, .check, .toggle, .mood__opt, .sheet__handle,
|
|
2657
|
-
a.avatar, button.avatar, .menu__item, button.snackbar__body) {
|
|
2682
|
+
a.avatar, button.avatar, .menu__item, button.snackbar__body, .stamp--tap, button.stamp-cell, button.stamp-place, .post-letter__turn, .post-desk__slot, .post-desk__pick, .post-desk__reach) {
|
|
2658
2683
|
cursor: pointer;
|
|
2659
2684
|
touch-action: manipulation;
|
|
2660
2685
|
transition: scale var(--motion-press) ease-out;
|
|
2661
2686
|
}
|
|
2662
|
-
:where(a.row, button.row, label.row, .row-expand > summary, .banner__summary, a.card, label.card, .toggle-field) {
|
|
2687
|
+
:where(a.row, button.row, label.row, .row-expand > summary, .banner__summary, a.card, label.card, .toggle-field, .post-box__edge) {
|
|
2663
2688
|
cursor: pointer;
|
|
2664
2689
|
touch-action: manipulation;
|
|
2665
2690
|
transition: opacity var(--motion-press) ease-out;
|
|
@@ -2673,27 +2698,27 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
2673
2698
|
}
|
|
2674
2699
|
:where(.btn, .icon-btn, .link, a.chip, button.chip, .chip--pick, a.tile, button.tile, .tile--pick, .day, .segment__item, .calendar__day,
|
|
2675
2700
|
.float-bar__item, .avatar-pick__item, .add-row, .picker__item, .dropdown__btn, .appbar__who, .check, .toggle, .mood__opt, .sheet__handle,
|
|
2676
|
-
a.avatar, button.avatar, .menu__item, button.snackbar__body):active:not(:disabled, [aria-disabled="true"], :has(:disabled)) { scale: var(--press-scale); }
|
|
2677
|
-
: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); }
|
|
2701
|
+
a.avatar, button.avatar, .menu__item, button.snackbar__body, .stamp--tap, button.stamp-cell, button.stamp-place, .post-letter__turn, .post-desk__slot, .post-desk__pick, .post-desk__reach):active:not(:disabled, [aria-disabled="true"], :has(:disabled)) { scale: var(--press-scale); }
|
|
2702
|
+
:where(a.row, button.row, label.row, .row-expand > summary, .banner__summary, a.card, label.card, .toggle-field, .post-box__edge):active:not([aria-disabled="true"], :has(:disabled)) { opacity: var(--press-dim); }
|
|
2678
2703
|
.sheet-step:has(> .sheet-step__go:active) { opacity: var(--press-dim); } /* the step bar's button is invisible over it: the bar answers */
|
|
2679
|
-
/* SHADE — what has something BEHIND it (a peeked sheet over the pile, a postcard's face over its back) must not turn see-through:
|
|
2704
|
+
/* SHADE — what has something BEHIND it (a peeked sheet over the pile, a postcard's face over its back, a post letter's sheet over the other) must not turn see-through:
|
|
2680
2705
|
it darkens a touch instead (--press-shade) */
|
|
2681
|
-
:where(.sheet-stack--tap > .card:not(.is-open), .postcard__turn) { cursor: pointer; touch-action: manipulation; transition: filter var(--motion-press) ease-out; }
|
|
2682
|
-
:where(.sheet-stack--tap > .card:not(.is-open), .postcard__turn):active { filter: brightness(var(--press-shade)); }
|
|
2706
|
+
:where(.sheet-stack--tap > .card:not(.is-open), .postcard__turn, .post-letter__sheet:not(:has(.icon-btn:active))) { cursor: pointer; touch-action: manipulation; transition: filter var(--motion-press) ease-out; }
|
|
2707
|
+
:where(.sheet-stack--tap > .card:not(.is-open), .postcard__turn, .post-letter__sheet:not(:has(.icon-btn:active))):active { filter: brightness(var(--press-shade)); }
|
|
2683
2708
|
/* 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) */
|
|
2684
2709
|
:where(.btn, .icon-btn, .link, a.chip, button.chip, .chip--pick, a.tile, button.tile, .tile--pick, .day, .segment__item, .calendar__day,
|
|
2685
2710
|
.float-bar__item, .avatar-pick__item, .add-row, .picker__item, .dropdown__btn, .appbar__who, .check, .toggle, .mood__opt, .sheet__handle,
|
|
2686
|
-
a.avatar, button.avatar, .menu__item, button.snackbar__body):focus-visible,
|
|
2711
|
+
a.avatar, button.avatar, .menu__item, button.snackbar__body, .stamp--tap, button.stamp-cell, button.stamp-place, .post-letter__turn, .post-desk__slot, .post-desk__pick, .post-desk__reach):focus-visible,
|
|
2687
2712
|
:where(.chip--pick, .tile--pick, label.btn, label.row, .toggle-field):has(:focus-visible) { outline: var(--focus); outline-offset: var(--sp-1); }
|
|
2688
|
-
: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); }
|
|
2713
|
+
:where(a.row, button.row, label.row, .row-expand > summary, .banner__summary, a.card, label.card, .toggle-field, .post-box__edge):not(label, .toggle-field):focus-visible { outline: var(--focus); outline-offset: var(--sp-1); }
|
|
2689
2714
|
/* disabled: no pointer anywhere; a control fades (a link and a row keep their own muted look) */
|
|
2690
2715
|
:where(.btn, .icon-btn, .link, a.chip, button.chip, .chip--pick, a.tile, button.tile, .tile--pick, .day, .segment__item, .calendar__day,
|
|
2691
2716
|
.float-bar__item, .avatar-pick__item, .add-row, .picker__item, .dropdown__btn, .appbar__who, .check, .toggle, .mood__opt, .sheet__handle,
|
|
2692
|
-
a.avatar, button.avatar, .menu__item, button.snackbar__body, a.row, button.row, label.row, .row-expand > summary, .banner__summary, a.card, label.card, .toggle-field):is(:disabled, [aria-disabled="true"]),
|
|
2717
|
+
a.avatar, button.avatar, .menu__item, button.snackbar__body, .stamp--tap, button.stamp-cell, button.stamp-place, .post-letter__turn, .post-desk__slot, .post-desk__pick, .post-desk__reach, a.row, button.row, label.row, .row-expand > summary, .banner__summary, a.card, label.card, .toggle-field, .post-box__edge):is(:disabled, [aria-disabled="true"]),
|
|
2693
2718
|
:where(.chip--pick, .tile--pick, .mood__opt, .toggle-field, label.btn, label.row):has(:disabled) { cursor: default; }
|
|
2694
2719
|
:where(.btn, .icon-btn, .link, a.chip, button.chip, .chip--pick, a.tile, button.tile, .tile--pick, .day, .segment__item, .calendar__day,
|
|
2695
2720
|
.float-bar__item, .avatar-pick__item, .add-row, .picker__item, .dropdown__btn, .appbar__who, .check, .toggle, .mood__opt, .sheet__handle,
|
|
2696
|
-
a.avatar, button.avatar, .menu__item, button.snackbar__body):not(.link):is(:disabled, [aria-disabled="true"]),
|
|
2721
|
+
a.avatar, button.avatar, .menu__item, button.snackbar__body, .stamp--tap, button.stamp-cell, button.stamp-place, .post-letter__turn, .post-desk__slot, .post-desk__pick, .post-desk__reach):not(.link):is(:disabled, [aria-disabled="true"]),
|
|
2697
2722
|
:where(.chip--pick, .tile--pick, .mood__opt, .toggle-field, label.btn):has(:disabled) { opacity: var(--off); }
|
|
2698
2723
|
.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 */
|
|
2699
2724
|
@media (prefers-reduced-motion: reduce) { :root { --press-scale: 1; } } /* the give goes; the dim, the shade and the focus ring stay */
|
|
@@ -3624,16 +3649,37 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
3624
3649
|
--_fold: var(--sticky-fold, var(--sp-6));
|
|
3625
3650
|
--_min: calc(var(--tap) * 3);
|
|
3626
3651
|
--_cap: calc(var(--tap) * 7);
|
|
3652
|
+
--_tall: calc(var(--tap) * 4.4);
|
|
3627
3653
|
position: relative;
|
|
3628
3654
|
display: grid;
|
|
3629
3655
|
box-sizing: border-box;
|
|
3630
3656
|
width: fit-content; /* hugs its words… */
|
|
3631
3657
|
min-width: var(--sticky-min-w, var(--_min)); /* …but a word alone is still a note */
|
|
3632
3658
|
max-width: var(--sticky-max-w, 100%);
|
|
3659
|
+
min-height: var(--sticky-min-h, var(--_tall)); /* every note is one height, the height of a note being written (Lh 2026-10-07); more words still grow it */
|
|
3633
3660
|
padding: 0 var(--_fold) var(--_fold) 0; /* = the line above, written whole */
|
|
3634
3661
|
background-clip: content-box;
|
|
3635
|
-
|
|
3662
|
+
isolation: isolate; /* the two layers below stay under this note, over whatever is behind it */
|
|
3636
3663
|
}
|
|
3664
|
+
/* the edge and the shadow: two layers UNDER the paper, each drawn in the note's own shape by a gradient — never a filter on the
|
|
3665
|
+
note (Safari repainted a filtered note as a hard grey band once a stamp on it had moved; Lh 2026-10-07).
|
|
3666
|
+
::after is the EDGE: the shape one hairline larger all round, so a pale note still ends somewhere on a pale ground.
|
|
3667
|
+
::before is the SHADOW: the shape set a little down and in, softened on its own layer — short and close, paper lying on a table. */
|
|
3668
|
+
.sticky::before, .sticky::after {
|
|
3669
|
+
content: "";
|
|
3670
|
+
position: absolute;
|
|
3671
|
+
z-index: -1;
|
|
3672
|
+
pointer-events: none;
|
|
3673
|
+
border-radius: var(--sticky-radius, var(--sp-1));
|
|
3674
|
+
background-image: linear-gradient(-45deg, transparent calc(var(--_fold) * 0.7071), var(--_ink) 0); /* the folded corner, cut */
|
|
3675
|
+
}
|
|
3676
|
+
.sticky::after { --_ink: var(--sticky-edge-bg, rgb(0 0 0 / 0.14)); inset: calc(-1 * var(--border-w)); }
|
|
3677
|
+
.sticky::before {
|
|
3678
|
+
--_ink: var(--sticky-shadow-bg, rgb(0 0 0 / 0.28));
|
|
3679
|
+
inset: var(--sp-1) var(--sp-0) calc(-1 * var(--sp-0)) var(--sp-0);
|
|
3680
|
+
filter: blur(var(--sticky-shadow-blur, var(--sp-0)));
|
|
3681
|
+
}
|
|
3682
|
+
.sticky--flat::before, .sticky--flat::after { background: var(--_ink); }
|
|
3637
3683
|
/* the tones — the names in src/sticky/tones.ts. A swatch of the chooser wears the same class. */
|
|
3638
3684
|
.sticky--cotton { background-color: var(--sticky-cotton-bg, #f3efe2); }
|
|
3639
3685
|
.sticky--blush { background-color: var(--sticky-blush-bg, #f6d5da); }
|
|
@@ -3675,6 +3721,8 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
3675
3721
|
background-color: var(--sticky-fold-bg, color-mix(in srgb, currentColor 14%, transparent)); /* a darker shade of whatever the paper is */
|
|
3676
3722
|
box-shadow: calc(-1 * var(--sp-0)) calc(-1 * var(--sp-0)) var(--sp-1) rgb(0 0 0 / 0.08);
|
|
3677
3723
|
}
|
|
3724
|
+
/* things stuck ON the note (a stamp, placed by hand): a layer over the whole paper. A tap between them still reaches the words under it. */
|
|
3725
|
+
.sticky__stuck { position: absolute; inset: 0; pointer-events: none; }
|
|
3678
3726
|
.sticky--flat > .sticky__paper { clip-path: none; }
|
|
3679
3727
|
.sticky--flat > .sticky__paper > .sticky__foot { padding-inline-end: 0; }
|
|
3680
3728
|
|
|
@@ -3692,8 +3740,8 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
3692
3740
|
/* --square: the note to write on — 1:1, as wide as its column up to a cap, in the middle */
|
|
3693
3741
|
.sticky--square { width: 100%; max-width: var(--sticky-square-max-w, var(--_cap)); aspect-ratio: 1; margin-inline: auto; }
|
|
3694
3742
|
|
|
3695
|
-
/* --corner: a control stands ON the bottom-right corner, half outside. The paper is notched round it (a mask, so
|
|
3696
|
-
|
|
3743
|
+
/* --corner: a control stands ON the bottom-right corner, half outside. The paper is notched round it (a mask, so no
|
|
3744
|
+
ground colour is needed; the edge and the shadow are notched the same way); the note has no fold. */
|
|
3697
3745
|
.sticky__corner {
|
|
3698
3746
|
position: absolute;
|
|
3699
3747
|
right: 0;
|
|
@@ -3710,6 +3758,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
3710
3758
|
max-width: calc(var(--sticky-max-w, 100%) - var(--_corner) / 2);
|
|
3711
3759
|
margin: 0 calc(var(--_corner) / 2) calc(var(--_corner) / 2) 0;
|
|
3712
3760
|
}
|
|
3761
|
+
.sticky--corner::before, .sticky--corner::after { background-image: radial-gradient(circle at 100% 100%, transparent calc(var(--_corner) / 2 + var(--sp-1)), var(--_ink) 0); }
|
|
3713
3762
|
.sticky--corner > .sticky__paper {
|
|
3714
3763
|
--_notch: calc(var(--_corner) / 2 + var(--sp-1));
|
|
3715
3764
|
clip-path: none;
|
|
@@ -3775,7 +3824,7 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
3775
3824
|
</div>
|
|
3776
3825
|
<div class="sticky-post sticky-post--right">…</div>
|
|
3777
3826
|
</div>
|
|
3778
|
-
Two columns: the avatar, then everything else
|
|
3827
|
+
Two columns: the avatar, then everything else. --right swaps the columns. */
|
|
3779
3828
|
.sticky-thread { display: grid; gap: var(--sticky-thread-gap, var(--sp-6)); --_thread-gap: var(--sticky-thread-gap, var(--sp-6)); } /* --_thread-gap: the same value, for the rail to run through */
|
|
3780
3829
|
.sticky-post {
|
|
3781
3830
|
display: grid;
|
|
@@ -3793,6 +3842,8 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
3793
3842
|
.sticky-post__notes > .sticky { max-width: var(--sticky-post-max-w, 85%); } /* a note leaves the far side open, as a bubble does */
|
|
3794
3843
|
.sticky-post__rail { display: none; grid-area: 2 / 1; justify-self: center; align-self: stretch; color: var(--sticky-thread-rail-ink, var(--on-surface-muted)); border-inline-start: var(--rule-w) dotted currentColor; margin-block-end: calc(-1 * var(--_thread-gap)); } /* runs on through the gap to the next avatar */
|
|
3795
3844
|
.sticky-thread--rail > .sticky-post:not(:last-child) > .sticky-post__rail { display: block; }
|
|
3845
|
+
/* a chat (no rail): a note leans on its writer's own edge of the thread — it starts under the avatar, not under the name, so theirs and mine stand apart (Lh 2026-10-07: both sat in the middle). With a rail the notes keep clear of it, under the name. */
|
|
3846
|
+
.sticky-thread:not(.sticky-thread--rail) > .sticky-post > .sticky-post__notes { grid-column: 1 / -1; }
|
|
3796
3847
|
.sticky-post--right { grid-template-columns: minmax(0, 1fr) auto; }
|
|
3797
3848
|
.sticky-post--right > .sticky-post__who { grid-column: 2; }
|
|
3798
3849
|
.sticky-post--right > .sticky-post__head { grid-column: 1; flex-direction: row-reverse; }
|
|
@@ -3800,6 +3851,519 @@ button:enabled { cursor: pointer; } /* a disabled one keeps the arrow */
|
|
|
3800
3851
|
.sticky-post--right > .sticky-post__notes { grid-column: 1; justify-items: end; }
|
|
3801
3852
|
.sticky-post--right > .sticky-post__rail { grid-column: 2; }
|
|
3802
3853
|
|
|
3854
|
+
/* ---- css/stamp.css — stamp, empty place, album page, a stamp stuck on a surface ---- */
|
|
3855
|
+
/* cardds — stamps: the paper stamp, an empty place for one, the album page, a stamp stuck on a surface.
|
|
3856
|
+
A stamp is its own paper, like a sticky note: it brings its ground and its ink, whatever card or palette it lies on.
|
|
3857
|
+
Its pictures are the APP's (any node); cardds lays them out by how many there are. */
|
|
3858
|
+
|
|
3859
|
+
/* ---- stamp: a perforated paper stamp, 4:5 — the picture fills the inner frame, the words sit ON it at the foot ----
|
|
3860
|
+
<span class="stamp stamp--3">
|
|
3861
|
+
<span class="stamp__paper">
|
|
3862
|
+
<span class="stamp__pic sticky--custard">
|
|
3863
|
+
<span class="stamp__art">…svg / img…</span>…
|
|
3864
|
+
<span class="stamp__cap"><span class="stamp__words">กาแฟ · หมา</span><span class="stamp__name">ต้น</span></span>
|
|
3865
|
+
</span>
|
|
3866
|
+
</span>
|
|
3867
|
+
</span>
|
|
3868
|
+
Two boxes, as the sticky note: the OUTER one holds the size and the shadow, the PAPER is the one whose edge is
|
|
3869
|
+
punched (a mask on the outer box would cut its own shadow off). The stamp is a size container: every inner length
|
|
3870
|
+
is a share of its width (cqw), so it reads the same from a tray to a sheet. The ground wears a sticky tone class —
|
|
3871
|
+
one list of tones (src/sticky/tones.ts), one set of tone knobs. */
|
|
3872
|
+
.stamp, .stamp-place {
|
|
3873
|
+
--_cap: calc(var(--tap) * 4.5);
|
|
3874
|
+
box-sizing: border-box;
|
|
3875
|
+
width: 100%; /* sized by its column… */
|
|
3876
|
+
max-width: var(--stamp-max-w, var(--_cap)); /* …up to a cap */
|
|
3877
|
+
aspect-ratio: 4 / 5;
|
|
3878
|
+
margin-inline: auto;
|
|
3879
|
+
}
|
|
3880
|
+
.stamp {
|
|
3881
|
+
position: relative;
|
|
3882
|
+
display: block;
|
|
3883
|
+
container-type: inline-size;
|
|
3884
|
+
color: var(--stamp-ink, #141414);
|
|
3885
|
+
filter: drop-shadow(var(--stamp-drop, 0 var(--sp-1) var(--sp-2) rgb(0 0 0 / 0.16))); /* drop-shadow follows the teeth; a box-shadow would draw the rectangle */
|
|
3886
|
+
}
|
|
3887
|
+
.stamp__paper {
|
|
3888
|
+
--_pitch: calc(100cqw / 12); /* one tooth: 12 across, 15 down — the holes land whole on every edge. Fewer and larger than a real stamp's: a small stamp's teeth must still read (Lh 2026-10-07; it was 20 × 25) */
|
|
3889
|
+
--_hole: 2.6cqw;
|
|
3890
|
+
position: absolute;
|
|
3891
|
+
inset: 0;
|
|
3892
|
+
background-color: var(--stamp-paper-bg, #fffdf8);
|
|
3893
|
+
-webkit-mask: linear-gradient(#000 0 0) center / calc(100% - var(--_pitch)) calc(100% - var(--_pitch)) no-repeat,
|
|
3894
|
+
radial-gradient(circle, transparent var(--_hole), #000 calc(var(--_hole) + 0.4cqw)) calc(var(--_pitch) / -2) calc(var(--_pitch) / -2) / var(--_pitch) var(--_pitch);
|
|
3895
|
+
mask: linear-gradient(#000 0 0) center / calc(100% - var(--_pitch)) calc(100% - var(--_pitch)) no-repeat,
|
|
3896
|
+
radial-gradient(circle, transparent var(--_hole), #000 calc(var(--_hole) + 0.4cqw)) calc(var(--_pitch) / -2) calc(var(--_pitch) / -2) / var(--_pitch) var(--_pitch);
|
|
3897
|
+
}
|
|
3898
|
+
.stamp__pic { position: absolute; inset: var(--stamp-frame, 8cqw); overflow: hidden; } /* the white frame round the picture; the ground is the tone class's */
|
|
3899
|
+
|
|
3900
|
+
/* the pictures, placed by COUNT: where, how wide, how far turned — the layout is the stamp's look, never the app's.
|
|
3901
|
+
A share of the picture area; the foot stays clear for the words. */
|
|
3902
|
+
.stamp__art { position: absolute; left: var(--_x); top: var(--_y); width: var(--_w); aspect-ratio: 1; translate: -50% -50%; rotate: var(--_r); }
|
|
3903
|
+
.stamp__art > * { display: block; width: 100%; height: 100%; }
|
|
3904
|
+
.stamp--1 .stamp__art { --_x: 50%; --_y: 40%; --_w: 72%; --_r: 0deg; }
|
|
3905
|
+
.stamp--2 .stamp__art:nth-child(1) { --_x: 37%; --_y: 27%; --_w: 58%; --_r: -6deg; }
|
|
3906
|
+
.stamp--2 .stamp__art:nth-child(2) { --_x: 63%; --_y: 56%; --_w: 54%; --_r: 8deg; }
|
|
3907
|
+
.stamp--3 .stamp__art:nth-child(1) { --_x: 30%; --_y: 21%; --_w: 50%; --_r: -8deg; }
|
|
3908
|
+
.stamp--3 .stamp__art:nth-child(2) { --_x: 71%; --_y: 31%; --_w: 46%; --_r: 7deg; }
|
|
3909
|
+
.stamp--3 .stamp__art:nth-child(3) { --_x: 47%; --_y: 58%; --_w: 54%; --_r: -2deg; }
|
|
3910
|
+
.stamp--4 .stamp__art:nth-child(1) { --_x: 29%; --_y: 19%; --_w: 44%; --_r: -8deg; }
|
|
3911
|
+
.stamp--4 .stamp__art:nth-child(2) { --_x: 71%; --_y: 24%; --_w: 42%; --_r: 7deg; }
|
|
3912
|
+
.stamp--4 .stamp__art:nth-child(3) { --_x: 31%; --_y: 57%; --_w: 42%; --_r: 5deg; }
|
|
3913
|
+
.stamp--4 .stamp__art:nth-child(4) { --_x: 70%; --_y: 60%; --_w: 44%; --_r: -6deg; }
|
|
3914
|
+
|
|
3915
|
+
/* the words on the picture's foot: a small line, then the big name — one line each (clipped sideways only: Thai tone marks stand above the line box) */
|
|
3916
|
+
.stamp__cap { position: absolute; inset-inline: 5cqw; bottom: 3.5cqw; display: grid; gap: 0.4cqw; }
|
|
3917
|
+
.stamp__words, .stamp__name { display: block; min-width: 0; white-space: nowrap; overflow-x: clip; text-overflow: ellipsis; }
|
|
3918
|
+
.stamp__words { --_words: 4.8cqw; font: 500 var(--_words) / 1.3 var(--font-ui); font-size: var(--stamp-words-font-size, var(--_words)); }
|
|
3919
|
+
.stamp__name { --_name: 9.5cqw; font: 900 var(--_name) / 1.15 var(--font-display); font-size: var(--stamp-name-font-size, var(--_name)); }
|
|
3920
|
+
/* tiny (a tray, an album cell, a stamp stuck on a note): the small line goes, the name grows */
|
|
3921
|
+
/* --bare: the pictures alone, no words — a stamp stuck on something (a note, a postcard). The words belong to the album.
|
|
3922
|
+
The pictures take the foot's room: each sits a little lower, so the group stands in the middle of the frame. */
|
|
3923
|
+
.stamp--bare .stamp__cap, .stamp-on .stamp__cap, .post-corner .stamp__cap { display: none; }
|
|
3924
|
+
.stamp--bare .stamp__art, .stamp-on .stamp__art, .post-corner .stamp__art { top: calc(var(--_y) + 9%); }
|
|
3925
|
+
|
|
3926
|
+
@container (max-width: 6rem) {
|
|
3927
|
+
.stamp__words { display: none; }
|
|
3928
|
+
.stamp__name { --_name: 12cqw; }
|
|
3929
|
+
}
|
|
3930
|
+
|
|
3931
|
+
/* --tilt: turned a little on its middle — the one stamp a start screen hands over */
|
|
3932
|
+
.stamp--tilt-left, .stamp--tilt-right { margin-block: var(--sp-4); } /* the turned corners and the shadow need room: a centre scrolls and would cut them */
|
|
3933
|
+
.stamp--tilt-left { rotate: calc(-1 * var(--stamp-tilt, 5deg)); }
|
|
3934
|
+
.stamp--tilt-right { rotate: var(--stamp-tilt, 5deg); }
|
|
3935
|
+
|
|
3936
|
+
/* --tap: the whole stamp is the button (`onClick`); the press and the ring are the PRESS list's (actions.css) */
|
|
3937
|
+
.stamp--tap { appearance: none; border: 0; padding: 0; background: none; font: inherit; text-align: start; -webkit-tap-highlight-color: transparent; }
|
|
3938
|
+
|
|
3939
|
+
/* a stamp handed over in a Centre: sized by the SCREEN (a little over half its width inside the inset), its greeting centred under it */
|
|
3940
|
+
.screen__centre:has(> .stamp) { justify-items: center; text-align: center; }
|
|
3941
|
+
.screen__centre > .stamp { max-width: var(--stamp-centre-max-w, 56cqw); }
|
|
3942
|
+
|
|
3943
|
+
/* ---- stamp place: an empty place for a stamp — an album's gap, "make yours" ----
|
|
3944
|
+
<span class="stamp-place">สร้าง</span> · <button class="stamp-place">…</button> · <span class="stamp-place stamp-place--faint"></span> */
|
|
3945
|
+
.stamp-place {
|
|
3946
|
+
display: grid;
|
|
3947
|
+
place-content: center;
|
|
3948
|
+
justify-items: center;
|
|
3949
|
+
padding: var(--sp-1);
|
|
3950
|
+
border: var(--rule-w) dashed var(--stamp-place-border, var(--on-surface-muted));
|
|
3951
|
+
border-radius: var(--stamp-place-radius, var(--sp-2));
|
|
3952
|
+
background: none;
|
|
3953
|
+
color: var(--stamp-place-ink, var(--on-surface-muted));
|
|
3954
|
+
font: var(--stamp-place-font, var(--type-caption));
|
|
3955
|
+
text-align: center;
|
|
3956
|
+
}
|
|
3957
|
+
.card .stamp-place, .post-paper .stamp-place { border-color: var(--stamp-place-border, var(--card-muted)); color: var(--stamp-place-ink, var(--card-muted)); }
|
|
3958
|
+
.stamp-place--faint { border-color: var(--stamp-place-faint-border, var(--hairline)); }
|
|
3959
|
+
|
|
3960
|
+
/* ---- stamp book: the album page — three stamps across on an album ground, who each is from under it ----
|
|
3961
|
+
<div class="stamp-book">
|
|
3962
|
+
<button class="stamp-cell"><span class="stamp">…</span><span class="stamp-cell__label">ต้น</span></button>
|
|
3963
|
+
<span class="stamp-cell">…</span>
|
|
3964
|
+
<span class="stamp-place stamp-place--faint" aria-hidden="true"></span>…
|
|
3965
|
+
</div> */
|
|
3966
|
+
.stamp-book {
|
|
3967
|
+
display: grid;
|
|
3968
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
3969
|
+
align-items: start;
|
|
3970
|
+
gap: var(--stamp-book-row-gap, var(--sp-4)) var(--stamp-book-col-gap, var(--sp-3));
|
|
3971
|
+
padding: var(--stamp-book-pad, var(--sp-4));
|
|
3972
|
+
border-radius: var(--stamp-book-radius, var(--r-media));
|
|
3973
|
+
background-color: var(--stamp-book-bg, var(--ink-tint));
|
|
3974
|
+
}
|
|
3975
|
+
.stamp-cell {
|
|
3976
|
+
position: relative;
|
|
3977
|
+
display: grid;
|
|
3978
|
+
gap: var(--stamp-cell-gap, var(--sp-2));
|
|
3979
|
+
min-width: 0;
|
|
3980
|
+
appearance: none;
|
|
3981
|
+
border: 0;
|
|
3982
|
+
padding: 0;
|
|
3983
|
+
background: none;
|
|
3984
|
+
color: var(--stamp-cell-ink, inherit);
|
|
3985
|
+
font: var(--stamp-cell-font, var(--type-caption));
|
|
3986
|
+
text-align: center;
|
|
3987
|
+
-webkit-tap-highlight-color: transparent;
|
|
3988
|
+
}
|
|
3989
|
+
.stamp-cell__label { display: block; min-width: 0; overflow-wrap: anywhere; }
|
|
3990
|
+
/* `on`: this one is in use (stuck on the note) — a filled disc with a check on its top corner */
|
|
3991
|
+
.stamp-cell__on {
|
|
3992
|
+
position: absolute;
|
|
3993
|
+
top: 0;
|
|
3994
|
+
right: 0;
|
|
3995
|
+
display: grid;
|
|
3996
|
+
place-items: center;
|
|
3997
|
+
width: var(--stamp-cell-on-size, var(--icon));
|
|
3998
|
+
aspect-ratio: 1;
|
|
3999
|
+
border-radius: 50%;
|
|
4000
|
+
background-color: var(--stamp-cell-on-bg, var(--accent));
|
|
4001
|
+
color: var(--stamp-cell-on-ink, var(--on-accent));
|
|
4002
|
+
}
|
|
4003
|
+
.stamp-cell__on > .icon { width: 70%; height: 70%; }
|
|
4004
|
+
/* --strip: ONE line of small stamps that scrolls sideways — the tray a member picks from. No ground. */
|
|
4005
|
+
.stamp-book--strip {
|
|
4006
|
+
--_strip: calc(var(--tap) * 1.5);
|
|
4007
|
+
display: flex;
|
|
4008
|
+
gap: var(--stamp-book-col-gap, var(--sp-3));
|
|
4009
|
+
padding: var(--sp-2) 0;
|
|
4010
|
+
border-radius: 0;
|
|
4011
|
+
background: none;
|
|
4012
|
+
overflow-x: auto;
|
|
4013
|
+
overscroll-behavior-x: contain;
|
|
4014
|
+
scrollbar-width: none;
|
|
4015
|
+
}
|
|
4016
|
+
.stamp-book--strip::-webkit-scrollbar { display: none; }
|
|
4017
|
+
.stamp-book--strip > * { flex: none; width: var(--stamp-book-strip-w, var(--_strip)); }
|
|
4018
|
+
|
|
4019
|
+
/* ---- stamp on: a stamp stuck on a surface where the member put it ----
|
|
4020
|
+
<div class="sticky__stuck"><div class="stamp-on stamp-on--move" style="--stamp-on-x: 0.8; --stamp-on-y: 0.2; --stamp-on-turn: 6"><span class="stamp">…</span></div></div>
|
|
4021
|
+
The surface is the nearest positioned box. --stamp-on-x / -y are the stamp's MIDDLE as a share of the surface (data, written by
|
|
4022
|
+
the component); --move = the member may drag it (StampOn with `onAtChange`). */
|
|
4023
|
+
.stamp-on {
|
|
4024
|
+
position: absolute;
|
|
4025
|
+
left: calc(var(--stamp-on-x, 0.5) * 100%);
|
|
4026
|
+
top: calc(var(--stamp-on-y, 0.5) * 100%);
|
|
4027
|
+
width: var(--stamp-on-w, 27%);
|
|
4028
|
+
translate: -50% -50%;
|
|
4029
|
+
rotate: calc(var(--stamp-on-turn, 0) * 1deg);
|
|
4030
|
+
pointer-events: auto; /* the layer it lies in lets a tap through to the surface; the stamp itself takes it */
|
|
4031
|
+
}
|
|
4032
|
+
.stamp-on > .stamp { max-width: none; }
|
|
4033
|
+
.stamp-on--move { will-change: transform; } /* its own layer: Safari left the stamp's shadow behind as a trail along the drag when it was painted with the note (seen on the simulator, 2026-10-07) */
|
|
4034
|
+
.stamp-on--move { cursor: grab; touch-action: none; -webkit-user-select: none; user-select: none; transition: scale var(--motion-press) ease-out; }
|
|
4035
|
+
.stamp-on--move:focus-visible { outline: var(--focus); outline-offset: var(--sp-1); }
|
|
4036
|
+
.stamp-on.is-held { cursor: grabbing; z-index: 1; scale: 1.06; } /* lifted off the paper while the finger holds it */
|
|
4037
|
+
@media (prefers-reduced-motion: reduce) { .stamp-on.is-held { scale: 1; } }
|
|
4038
|
+
|
|
4039
|
+
/* ---- css/post.css — the post: postmark, a postcard read (letter), written (desk), kept (box) ---- */
|
|
4040
|
+
/* cardds — the post: a postcard read (post-letter), written (post-desk) and kept (post-box), the postmark, the card stock.
|
|
4041
|
+
Not the `Postcard` of media.css (a member's cover card that flips) — nothing here flips, and no class is shared with it.
|
|
4042
|
+
The written side is the postcard; the corner — a stamp, the postmark struck over its edge — proves the journey. The stamp is
|
|
4043
|
+
always a `Stamp` (stamp.css), handed over by the app. */
|
|
4044
|
+
|
|
4045
|
+
/* ---- post-paper: the card stock. Its own ground and ink, wherever it lies; its parts read --_muted and --_rule ---- */
|
|
4046
|
+
.post-paper {
|
|
4047
|
+
background: var(--post-paper-bg, var(--card-1-bg));
|
|
4048
|
+
color: var(--post-paper-ink, var(--card-1-ink));
|
|
4049
|
+
border-radius: var(--post-paper-radius, var(--sp-2));
|
|
4050
|
+
box-shadow: var(--post-paper-shadow, var(--shadow-card));
|
|
4051
|
+
--_muted: var(--post-paper-muted-ink, var(--card-muted));
|
|
4052
|
+
--_rule: var(--post-paper-rule-ink, var(--ink-rail));
|
|
4053
|
+
}
|
|
4054
|
+
|
|
4055
|
+
/* ---- postmark: the round mark struck by the post — where, when — two rings, turned a little, its cancel lines running on
|
|
4056
|
+
<div class="postmark"><span>จันทบุรี</span><span>12 เม.ย.</span></div> ---- */
|
|
4057
|
+
.postmark {
|
|
4058
|
+
--_base: calc(var(--tap) * 1.75);
|
|
4059
|
+
position: relative;
|
|
4060
|
+
z-index: 1;
|
|
4061
|
+
flex: none;
|
|
4062
|
+
display: grid;
|
|
4063
|
+
place-content: center;
|
|
4064
|
+
justify-items: center;
|
|
4065
|
+
width: var(--postmark-size, var(--_base));
|
|
4066
|
+
--_size: var(--postmark-size, var(--_base));
|
|
4067
|
+
aspect-ratio: 1;
|
|
4068
|
+
border: var(--rule-w) solid currentColor;
|
|
4069
|
+
border-radius: 50%;
|
|
4070
|
+
outline: var(--border-w) solid currentColor;
|
|
4071
|
+
outline-offset: calc(-1 * var(--sp-1) - var(--rule-w));
|
|
4072
|
+
color: var(--postmark-ink, color-mix(in srgb, currentColor 72%, transparent));
|
|
4073
|
+
font: var(--postmark-font, var(--type-label));
|
|
4074
|
+
text-align: center;
|
|
4075
|
+
rotate: var(--postmark-tilt, -10deg);
|
|
4076
|
+
}
|
|
4077
|
+
.postmark::after { /* the cancel lines that run on over the stamp */
|
|
4078
|
+
content: "";
|
|
4079
|
+
position: absolute;
|
|
4080
|
+
inset-inline-start: 100%;
|
|
4081
|
+
inset-block-start: 28%;
|
|
4082
|
+
width: 46%;
|
|
4083
|
+
height: calc(var(--sp-3) * 2 + var(--rule-w));
|
|
4084
|
+
background: repeating-linear-gradient(to bottom, currentColor 0 var(--rule-w), transparent var(--rule-w) var(--sp-3));
|
|
4085
|
+
}
|
|
4086
|
+
.postmark > span { display: block; white-space: nowrap; }
|
|
4087
|
+
|
|
4088
|
+
/* ---- post-corner: the corner that proves the journey — the stamp, and the postmark struck over its edge
|
|
4089
|
+
<div class="post-corner"><div class="postmark">…</div><span class="post-corner__stamp"><span class="stamp">…</span></span></div>
|
|
4090
|
+
A stamp stuck here shows its pictures only, as one in a `.stamp-on` does (stamp.css `--bare`): the words belong to the album. ---- */
|
|
4091
|
+
.post-corner { display: flex; align-items: flex-end; justify-content: flex-end; flex: none; }
|
|
4092
|
+
.post-corner > .postmark { margin-inline-end: calc(var(--_size) * -0.4); margin-block-end: calc(var(--sp-2) * -1); }
|
|
4093
|
+
.post-corner__stamp {
|
|
4094
|
+
--_w: calc(var(--sp-2) * 9);
|
|
4095
|
+
position: relative;
|
|
4096
|
+
flex: none;
|
|
4097
|
+
display: grid;
|
|
4098
|
+
width: var(--post-corner-stamp-w, var(--_w));
|
|
4099
|
+
aspect-ratio: 4 / 5;
|
|
4100
|
+
rotate: var(--post-corner-stamp-tilt, 2deg);
|
|
4101
|
+
}
|
|
4102
|
+
.post-corner__stamp > :is(.stamp, .stamp-place) { grid-area: 1 / 1; max-width: none; }
|
|
4103
|
+
|
|
4104
|
+
/* ============================================================
|
|
4105
|
+
post-letter — READING one that arrived. The written side stood upright: the address half over the message half.
|
|
4106
|
+
The picture is the other sheet; the tab over the pair brings it forward. Two rigid sheets changing places —
|
|
4107
|
+
PostLetter swings the one in front to its edge, then the other in (Web Animations; nothing with reduced motion).
|
|
4108
|
+
<div class="post-letter post-letter--picture">
|
|
4109
|
+
<div class="post-letter__sheet post-letter__sheet--picture post-paper">
|
|
4110
|
+
<div class="post-letter__pic">…</div><p class="post-letter__caption">…</p>
|
|
4111
|
+
<div class="post-letter__voice"><button class="icon-btn icon-btn--invert">…</button><span class="post-letter__time">0:12</span></div>
|
|
4112
|
+
</div>
|
|
4113
|
+
<article class="post-letter__sheet post-letter__sheet--back post-paper" inert>
|
|
4114
|
+
<header class="post-letter__head">
|
|
4115
|
+
<div class="post-letter__to"><span class="post-letter__kicker">…</span>
|
|
4116
|
+
<span class="post-letter__lines"><span class="post-letter__line post-letter__line--name"><small>ถึง</small>ฝน</span><span class="post-letter__line">…</span></span></div>
|
|
4117
|
+
<div class="post-corner">…</div>
|
|
4118
|
+
</header>
|
|
4119
|
+
<div class="post-letter__message"><p>…</p><p class="post-letter__sign">…</p></div>
|
|
4120
|
+
</article>
|
|
4121
|
+
<button class="post-letter__turn">…icon…<span>พลิกดูด้านหลัง</span></button>
|
|
4122
|
+
<p class="post-letter__journey">…</p>
|
|
4123
|
+
</div>
|
|
4124
|
+
============================================================ */
|
|
4125
|
+
.post-letter {
|
|
4126
|
+
--_room: calc(var(--tap) + var(--sp-5));
|
|
4127
|
+
position: relative;
|
|
4128
|
+
display: grid;
|
|
4129
|
+
padding-block-start: var(--post-letter-peek, var(--tap));
|
|
4130
|
+
--_peek: var(--post-letter-peek, var(--tap));
|
|
4131
|
+
/* as wide as leaves the 2:3 sheet, the tab and the journey line whole above the bottom layer */
|
|
4132
|
+
width: min(100%, calc((100cqh - var(--bottom-h, 0rem) - var(--_peek) - var(--sp-6) - var(--post-letter-journey-room, var(--_room))) * 2 / 3));
|
|
4133
|
+
margin-inline: auto;
|
|
4134
|
+
}
|
|
4135
|
+
.post-letter__sheet {
|
|
4136
|
+
position: relative;
|
|
4137
|
+
grid-area: 1 / 1;
|
|
4138
|
+
aspect-ratio: 2 / 3;
|
|
4139
|
+
min-width: 0;
|
|
4140
|
+
min-height: 0;
|
|
4141
|
+
overflow: clip;
|
|
4142
|
+
visibility: hidden;
|
|
4143
|
+
rotate: calc(-1 * var(--post-letter-tilt, 2deg));
|
|
4144
|
+
}
|
|
4145
|
+
.post-letter__sheet--back { rotate: var(--post-letter-tilt, 2deg); } /* turned over, the lean is the other way */
|
|
4146
|
+
.post-letter--back > .post-letter__sheet--back,
|
|
4147
|
+
.post-letter--picture > .post-letter__sheet--picture { visibility: visible; }
|
|
4148
|
+
.post-letter:not(:has(> .post-letter__journey)) { --_room: 0rem; } /* no journey line: its room goes back to the sheet */
|
|
4149
|
+
.post-letter__journey { grid-area: 2 / 1; margin: var(--sp-3) 0 0; text-align: center; font: var(--type-caption); color: var(--post-letter-journey-ink, var(--on-surface-muted)); }
|
|
4150
|
+
|
|
4151
|
+
/* the picture side: the print with its white margin, the place printed under it */
|
|
4152
|
+
.post-letter__sheet--picture {
|
|
4153
|
+
display: grid;
|
|
4154
|
+
grid-template-rows: minmax(0, 1fr) auto;
|
|
4155
|
+
gap: var(--sp-2);
|
|
4156
|
+
padding: var(--post-letter-picture-pad, var(--sp-3));
|
|
4157
|
+
--_pad: var(--post-letter-picture-pad, var(--sp-3));
|
|
4158
|
+
}
|
|
4159
|
+
.post-letter__pic { min-height: 0; border-radius: var(--sp-1); overflow: clip; }
|
|
4160
|
+
.post-letter__pic > * { display: block; width: 100%; height: 100%; object-fit: cover; }
|
|
4161
|
+
.post-letter__caption { margin: 0; min-height: 1.35em; font: var(--type-caption); color: var(--_muted); }
|
|
4162
|
+
/* the voice on the picture side: the play button over the print's corner, its time under it in the margin */
|
|
4163
|
+
.post-letter__voice { position: absolute; inset-block-end: var(--_pad); inset-inline-end: calc(var(--_pad) + var(--sp-2)); display: grid; justify-items: center; gap: var(--sp-2); cursor: auto; }
|
|
4164
|
+
.post-letter__time { font: var(--type-caption); font-variant-numeric: tabular-nums; color: var(--_muted); }
|
|
4165
|
+
|
|
4166
|
+
/* the written side */
|
|
4167
|
+
.post-letter__sheet--back { display: flex; flex-direction: column; gap: var(--post-letter-gap, var(--sp-4)); padding: var(--post-letter-pad, var(--sp-5)); }
|
|
4168
|
+
.post-letter__head { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: end; gap: var(--sp-2); padding-block-end: var(--sp-4); border-block-end: var(--rule-w) solid var(--_rule); }
|
|
4169
|
+
.post-letter__to { display: grid; gap: var(--sp-2); min-width: 0; align-self: stretch; align-content: space-between; }
|
|
4170
|
+
.post-letter__kicker { font: var(--type-caption); color: var(--_muted); letter-spacing: 0.08em; }
|
|
4171
|
+
.post-letter__lines { display: grid; gap: var(--sp-1); }
|
|
4172
|
+
.post-letter__line { display: block; padding-block: var(--sp-1); border-block-end: var(--border-w) solid var(--_rule); font: var(--type-caption); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
4173
|
+
.post-letter__line--name { font: var(--type-title); }
|
|
4174
|
+
.post-letter__line > small { font: var(--type-caption); color: var(--_muted); margin-inline-end: var(--sp-2); }
|
|
4175
|
+
.post-letter__message {
|
|
4176
|
+
flex: 1;
|
|
4177
|
+
min-height: 0;
|
|
4178
|
+
display: flex;
|
|
4179
|
+
flex-direction: column;
|
|
4180
|
+
gap: var(--sp-3);
|
|
4181
|
+
font: var(--post-letter-hand-font, var(--type-h2));
|
|
4182
|
+
font-size: var(--post-letter-hand-font-size, var(--fs-lg));
|
|
4183
|
+
line-height: var(--post-letter-hand-leading, 1.6);
|
|
4184
|
+
}
|
|
4185
|
+
.post-letter__message > p { margin: 0; }
|
|
4186
|
+
.post-letter__sign { align-self: flex-end; }
|
|
4187
|
+
|
|
4188
|
+
/* the tab: the showing edge of the sheet behind is the control that brings it forward. A chip in the strip over the pair;
|
|
4189
|
+
its tap target is the strip's whole height (the ::after). The press and the ring are the PRESS list's (actions.css) */
|
|
4190
|
+
.post-letter__turn {
|
|
4191
|
+
position: absolute;
|
|
4192
|
+
inset-block-start: calc(var(--_peek) / 2);
|
|
4193
|
+
inset-inline-start: var(--sp-2);
|
|
4194
|
+
translate: 0 -50%;
|
|
4195
|
+
z-index: 2;
|
|
4196
|
+
display: inline-flex;
|
|
4197
|
+
align-items: center;
|
|
4198
|
+
gap: var(--sp-2);
|
|
4199
|
+
padding: var(--sp-1) var(--sp-3);
|
|
4200
|
+
border: 0;
|
|
4201
|
+
border-radius: var(--r-chip);
|
|
4202
|
+
background: var(--post-letter-tab-bg, var(--action-bg));
|
|
4203
|
+
color: var(--post-letter-tab-ink, var(--action-ink));
|
|
4204
|
+
font: var(--type-label);
|
|
4205
|
+
box-shadow: var(--shadow-float);
|
|
4206
|
+
}
|
|
4207
|
+
.post-letter__turn::after { content: ""; position: absolute; inset-inline: 0; inset-block-start: 50%; height: var(--_peek); translate: 0 -50%; }
|
|
4208
|
+
|
|
4209
|
+
/* ============================================================
|
|
4210
|
+
post-desk — WRITING one. The paper stays landscape and twice the screen; the phone looks at one half. Half of a 3:2 card
|
|
4211
|
+
is a 3:4 panel, so each half fills the hand: write on the left, slide the paper, address and stamp the right.
|
|
4212
|
+
The stamp box is a button; struck, the postmark lands on it; sent, the card leaves upward. Lives in a `Page bleed`.
|
|
4213
|
+
<div class="post-desk post-desk--message post-desk--writing">
|
|
4214
|
+
<div class="post-desk__window">
|
|
4215
|
+
<div class="post-desk__card post-paper">
|
|
4216
|
+
<section class="post-desk__half post-desk__half--message"><textarea class="post-desk__write"></textarea></section>
|
|
4217
|
+
<section class="post-desk__half post-desk__half--address" inert>
|
|
4218
|
+
<div class="post-desk__corner"><div class="post-corner">…postmark…<button class="post-corner__stamp post-desk__slot" aria-pressed="false">…</button></div></div>
|
|
4219
|
+
<div class="post-desk__lines"><label class="post-desk__line"><span>ถึง</span><input></label>…</div>
|
|
4220
|
+
</section>
|
|
4221
|
+
</div>
|
|
4222
|
+
<button class="post-desk__reach post-desk__reach--address"></button>
|
|
4223
|
+
<div class="post-desk__gone">…</div>
|
|
4224
|
+
</div>
|
|
4225
|
+
<p class="post-desk__note">…</p>
|
|
4226
|
+
<div class="post-desk__stamps" role="group"><button class="post-desk__pick" aria-pressed="false">…stamp…</button>…</div>
|
|
4227
|
+
</div>
|
|
4228
|
+
============================================================ */
|
|
4229
|
+
.post-desk { container-type: inline-size; display: grid; gap: var(--post-desk-gap, var(--sp-3)); }
|
|
4230
|
+
.post-desk__window { position: relative; min-width: 0; }
|
|
4231
|
+
.post-desk__card {
|
|
4232
|
+
--_peek: var(--post-desk-peek, var(--sp-6));
|
|
4233
|
+
--_p: min(calc(100cqi - var(--_peek)), calc((100cqh + var(--kb, 0rem) - var(--bottom-h, 0rem) - var(--sp-12) * 2) * 0.75)); /* + --kb: the screen's box shrinks under a keyboard, the paper never does — what was written must still fit */
|
|
4234
|
+
--_x: 0rem;
|
|
4235
|
+
--_y: 0rem;
|
|
4236
|
+
display: grid;
|
|
4237
|
+
grid-template-columns: 1fr 1fr;
|
|
4238
|
+
width: calc(var(--_p) * 2);
|
|
4239
|
+
aspect-ratio: 3 / 2;
|
|
4240
|
+
translate: var(--_x) var(--_y);
|
|
4241
|
+
transition: translate var(--motion-sheet) ease, rotate var(--motion-sheet) ease;
|
|
4242
|
+
}
|
|
4243
|
+
.post-desk--address .post-desk__card { --_x: calc(100cqi - var(--_p) * 2); }
|
|
4244
|
+
.post-desk--sent .post-desk__card { --_y: -160%; rotate: -5deg; }
|
|
4245
|
+
.post-desk__half { min-width: 0; min-height: 0; padding: var(--post-desk-pad, var(--sp-5)); display: flex; flex-direction: column; }
|
|
4246
|
+
.post-desk__half--address { border-inline-start: var(--rule-w) solid var(--_rule); gap: var(--sp-4); }
|
|
4247
|
+
.post-desk__write {
|
|
4248
|
+
flex: 1;
|
|
4249
|
+
width: 100%;
|
|
4250
|
+
min-height: 0;
|
|
4251
|
+
resize: none;
|
|
4252
|
+
border: 0;
|
|
4253
|
+
outline: none;
|
|
4254
|
+
background: transparent;
|
|
4255
|
+
color: inherit;
|
|
4256
|
+
font: var(--post-desk-hand-font, var(--type-h2));
|
|
4257
|
+
line-height: calc(var(--fs-body) * var(--post-desk-leading, 1.7));
|
|
4258
|
+
--_line: calc(var(--fs-body) * var(--post-desk-leading, 1.7));
|
|
4259
|
+
/* the ruled lines: one under every line of writing */
|
|
4260
|
+
background-image: linear-gradient(to bottom, transparent calc(100% - var(--border-w)), var(--_rule) 0);
|
|
4261
|
+
background-size: 100% var(--_line);
|
|
4262
|
+
caret-color: currentColor;
|
|
4263
|
+
}
|
|
4264
|
+
.post-desk__write::placeholder { color: var(--_muted); }
|
|
4265
|
+
.post-desk__corner { display: flex; justify-content: flex-end; }
|
|
4266
|
+
.post-desk__lines { display: grid; gap: var(--sp-3); margin-block-start: auto; padding-block-end: var(--sp-6); }
|
|
4267
|
+
.post-desk__line { display: grid; gap: var(--sp-0); border-block-end: var(--border-w) solid var(--_rule); }
|
|
4268
|
+
.post-desk__line:focus-within { border-block-end-color: currentColor; }
|
|
4269
|
+
.post-desk__line > span { font: var(--type-caption); color: var(--_muted); }
|
|
4270
|
+
.post-desk__line > input { width: 100%; min-width: 0; min-height: var(--tap); border: 0; outline: none; background: transparent; color: inherit; font: var(--type-title); }
|
|
4271
|
+
.post-desk__line > input::placeholder { color: var(--_muted); }
|
|
4272
|
+
/* the stamp box: empty it is a dashed place to press; pressed, the chosen stamp lies in it */
|
|
4273
|
+
.post-desk__slot { border: 0; background: transparent; color: inherit; }
|
|
4274
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
4275
|
+
.post-desk__slot > .stamp { animation: post-desk-stick var(--motion-quick) ease-out both; }
|
|
4276
|
+
.post-desk .postmark { animation: post-desk-strike var(--motion-press) ease-in both; }
|
|
4277
|
+
}
|
|
4278
|
+
@keyframes post-desk-stick { from { translate: 0 calc(-1 * var(--sp-8)); rotate: 14deg; opacity: 0; } }
|
|
4279
|
+
@keyframes post-desk-strike { from { translate: calc(-1 * var(--sp-3)) calc(-1 * var(--sp-6)); rotate: -24deg; opacity: 0; } }
|
|
4280
|
+
/* the other half's showing edge brings it across: a tap-wide strip out to the screen's edge */
|
|
4281
|
+
.post-desk__reach { position: absolute; inset-block: 0; width: var(--tap); border: 0; background: transparent; }
|
|
4282
|
+
.post-desk__reach--address { inset-inline-end: calc(-1 * var(--screen-pad)); }
|
|
4283
|
+
.post-desk__reach--message { inset-inline-start: calc(-1 * var(--screen-pad)); }
|
|
4284
|
+
.post-desk__reach:focus-visible { outline: var(--focus); outline-offset: calc(-1 * var(--sp-1)); }
|
|
4285
|
+
/* the sender's stamps to choose from, under the card */
|
|
4286
|
+
.post-desk__stamps { display: flex; gap: var(--post-desk-stamps-gap, var(--sp-4)); align-items: center; }
|
|
4287
|
+
.post-desk__pick {
|
|
4288
|
+
display: grid;
|
|
4289
|
+
place-items: center;
|
|
4290
|
+
min-width: var(--tap);
|
|
4291
|
+
min-height: var(--tap);
|
|
4292
|
+
padding: var(--sp-1);
|
|
4293
|
+
border: 0;
|
|
4294
|
+
border-block-end: var(--rule-w) solid transparent;
|
|
4295
|
+
background: transparent;
|
|
4296
|
+
}
|
|
4297
|
+
.post-desk__pick > .stamp { --_w: calc(var(--sp-2) * 9); width: var(--post-desk-pick-w, var(--_w)); max-width: none; rotate: var(--post-corner-stamp-tilt, 2deg); } /* in the tray a stamp keeps its words */
|
|
4298
|
+
.post-desk__pick[aria-pressed="true"] { border-block-end-color: var(--on-surface); }
|
|
4299
|
+
.post-desk__note { margin: 0; font: var(--type-caption); color: var(--post-desk-note-ink, var(--on-surface-muted)); min-height: 1.35em; }
|
|
4300
|
+
/* what stands where the card was, once it has left */
|
|
4301
|
+
.post-desk__gone { position: absolute; inset: 0; display: grid; grid-template-columns: minmax(0, 1fr); align-content: center; gap: var(--sp-2); }
|
|
4302
|
+
@media (prefers-reduced-motion: reduce) { .post-desk__card { transition: none; } }
|
|
4303
|
+
|
|
4304
|
+
/* ============================================================
|
|
4305
|
+
post-box — KEEPING many. The cards whole and landscape, standing one behind another; each shows its top edge: who, where,
|
|
4306
|
+
when, the stamp and the postmark. Pressing an edge parts the box: every card in front slides down and the whole card is
|
|
4307
|
+
there to read. The newest stands in front — the foot of the list, nearest the thumb.
|
|
4308
|
+
<ul class="post-box">
|
|
4309
|
+
<li class="post-box__card post-paper is-open">
|
|
4310
|
+
<button class="post-box__edge" aria-expanded="true"><span class="post-box__from">แม่</span><span class="post-box__when">เชียงใหม่ · 3 ม.ค. 69</span></button>
|
|
4311
|
+
<div class="post-corner" aria-hidden="true">…</div>
|
|
4312
|
+
<div class="post-box__body"><p class="post-box__message">…</p><div class="post-box__to"><span>ถึง ฝน</span>…</div></div>
|
|
4313
|
+
</li>…
|
|
4314
|
+
</ul>
|
|
4315
|
+
============================================================ */
|
|
4316
|
+
/* the card first: its edge is the knob's real reader — the box (below) hands the same length on to the parting */
|
|
4317
|
+
.post-box__card {
|
|
4318
|
+
position: relative;
|
|
4319
|
+
height: var(--_h);
|
|
4320
|
+
margin-block-end: calc(var(--_e) - var(--_h));
|
|
4321
|
+
display: grid;
|
|
4322
|
+
grid-template-rows: var(--post-box-edge, var(--_edge)) minmax(0, 1fr);
|
|
4323
|
+
transition: translate var(--motion-sheet) ease, rotate var(--motion-sheet) ease;
|
|
4324
|
+
}
|
|
4325
|
+
.post-box {
|
|
4326
|
+
--_edge: calc(var(--sp-base) * 20);
|
|
4327
|
+
--_e: var(--post-box-edge, var(--_edge));
|
|
4328
|
+
--_h: calc(100cqi * 2 / 3);
|
|
4329
|
+
--_part: calc(var(--_h) - var(--_e) + var(--sp-3));
|
|
4330
|
+
container-type: inline-size;
|
|
4331
|
+
list-style: none;
|
|
4332
|
+
margin: 0;
|
|
4333
|
+
padding: 0;
|
|
4334
|
+
display: grid;
|
|
4335
|
+
}
|
|
4336
|
+
.post-box:has(> .post-box__card.is-open:not(:last-child)) { padding-block-end: var(--_part); }
|
|
4337
|
+
.post-box__card:last-child { margin-block-end: 0; }
|
|
4338
|
+
.post-box__card:nth-child(3n + 1) { rotate: var(--post-box-lean, 0.6deg); }
|
|
4339
|
+
.post-box__card:nth-child(3n + 2) { rotate: calc(var(--post-box-lean, 0.6deg) * -0.8); }
|
|
4340
|
+
.post-box__card.is-open { rotate: none; }
|
|
4341
|
+
.post-box__card.is-open ~ .post-box__card { translate: 0 var(--_part); }
|
|
4342
|
+
.post-box__edge {
|
|
4343
|
+
display: grid;
|
|
4344
|
+
align-content: center;
|
|
4345
|
+
justify-items: start;
|
|
4346
|
+
gap: var(--sp-0);
|
|
4347
|
+
padding: var(--sp-2) calc(var(--tap) * 2.75) var(--sp-1) var(--sp-4);
|
|
4348
|
+
border: 0;
|
|
4349
|
+
border-radius: var(--post-paper-radius, var(--sp-2));
|
|
4350
|
+
background: transparent;
|
|
4351
|
+
color: inherit;
|
|
4352
|
+
text-align: start;
|
|
4353
|
+
}
|
|
4354
|
+
.post-box__edge:focus-visible { outline-offset: calc(-1 * var(--sp-1)); }
|
|
4355
|
+
.post-box__from { font: var(--type-title); }
|
|
4356
|
+
.post-box__when { font: var(--type-caption); color: var(--_muted); white-space: nowrap; }
|
|
4357
|
+
.post-box__card > .post-corner { position: absolute; inset-block-start: var(--sp-3); inset-inline-end: var(--sp-4); pointer-events: none; }
|
|
4358
|
+
.post-box__body { min-height: 0; display: grid; grid-template-columns: 5fr 4fr; padding: 0 var(--sp-4) var(--sp-3); }
|
|
4359
|
+
.post-box__message { margin: 0; padding-inline-end: var(--sp-3); font: var(--type-caption); font-family: var(--font-display); overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 6; line-clamp: 6; align-self: start; }
|
|
4360
|
+
.post-box__to { display: grid; align-content: end; gap: var(--sp-1); padding-inline-start: var(--sp-3); border-inline-start: var(--border-w) solid var(--_rule); min-width: 0; }
|
|
4361
|
+
.post-box__to > span { padding-block: var(--sp-1); border-block-end: var(--border-w) solid var(--_rule); font: var(--type-caption); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
4362
|
+
/* behind another card only the mark's top shows: the place alone, lifted clear of the next card's edge */
|
|
4363
|
+
.post-box__card:not(.is-open) .postmark > span:last-child:not(:first-child) { display: none; }
|
|
4364
|
+
.post-box__card:not(.is-open) .postmark { box-sizing: border-box; place-content: start center; padding-block-start: calc(var(--_size) * 0.18); }
|
|
4365
|
+
@media (prefers-reduced-motion: reduce) { .post-box__card { transition: none; } }
|
|
4366
|
+
|
|
3803
4367
|
/* ---- css/media.css — cover card, quote, tile grid, fold, mosaic, wave ---- */
|
|
3804
4368
|
/* ============================================================
|
|
3805
4369
|
cardds/media.css — media & display inside cards:
|