/* ═══════════════════════════════════════════
   gift-of-flowers — beat 5: the-letter
   The film's quiet centre. A cream note card rises from the bottom (its top
   edge enters where the polaroids' exit left it peeking, at 92% of the
   screen) and the buyer's letter writes itself in Caveat, line by line: a
   card-coloured mask slides off each laid-out line (a compositor-only
   translateX), and the wine caret is that mask's leading edge, so it rides
   the writing for free. Long letters glide up inside the card (a Web
   Animation on the text block) so the line being written stays above 70%
   of the card; at the hold `fold` the card becomes a real scroller.

   This section is TRANSPARENT: the wash and the ambient bubbles are the
   bridge's #gof-ground beneath every beat. Only the card is ours.

   State classes on .the-letter-container (toggled by the beat's cues and
   handler): --in (the rise) · --rested (the writing has ended: the blank
   tail goes and the text glides to its resting place) · --held (hold
   `fold`: free to reread; hint and button) · --leaving (the card slides up
   and off) · --scrolls (layout: the letter is taller than the card) ·
   --whole (layout: at rest the whole letter shows, opening to last line).
   Line states are a data attribute (data-ink: wait | writing | done),
   outside the badge guard's class/style filter. Only transform and opacity move; the shadow is static.

   Geometry (all CSS; JS only sets --the-letter-text-h and --the-letter-tail
   from the one hidden layout pass per setter call):
     card     88vw wide (max 520px), height = text + padding, clamped to
              30dvh..72dvh and to the area above the controls
     padding  8vw top/bottom, 7vw sides (capped on wide screens)
     area     from max(5dvh, safe top + 66px) to 140px + safe bottom above
              the bottom edge; the card is centred in it (the page's
              optical middle, clear of the controls and the badge corner).
              66px = the mute pill's top (14px) + its 44px height + 8px,
              so the card never meets #stage-mute at any viewport.
              140px = room for the Reveel badge's lifted spot: the guard
              lifts it above the "swipe up" hint (hint top = 22 + 44 + 7 +
              17.5 = 90.5px up, + 8px gap = bottom 98.5px; the badge is
              ~31px tall, so its top is 129.5px up). The card ends 10.5px
              above that, so the guard can always lift the badge off the
              "Or tap to go on" button rather than leave it on top of it.
   ═══════════════════════════════════════════ */

.the-letter-container,
.the-letter-measure {
    --the-letter-top: max(5dvh, calc(env(safe-area-inset-top, 0px) + 66px));
    --the-letter-bottom: calc(140px + env(safe-area-inset-bottom, 0px));
    --the-letter-pad-x: min(7vw, 36px);
    --the-letter-pad-y: min(8vw, 40px);
    --the-letter-card-w: min(88vw, 520px);
    --the-letter-area: calc(100dvh - var(--the-letter-top) - var(--the-letter-bottom));
    --the-letter-card-h: clamp(30dvh,
        calc(var(--the-letter-text-h, 40dvh) + 2 * var(--the-letter-pad-y)),
        min(72dvh, var(--the-letter-area)));
    --the-letter-card-top: calc(var(--the-letter-top) + (var(--the-letter-area) - var(--the-letter-card-h)) / 2);
    --the-letter-para-gap: 0.75em;
}

.the-letter-container {
    position: absolute;
    inset: 0;
    pointer-events: auto;
    /* The whole beat is the swipe surface (data-reveel-avoid in the markup);
       the card turns pan-y on at the hold so a long letter can be reread. */
    touch-action: none;
    -webkit-user-select: none;
    user-select: none;
    -webkit-touch-callout: none;
    -webkit-tap-highlight-color: transparent;
}

/* ───── The card's station: rise in, slide off ───── */
.the-letter-station {
    position: absolute;
    left: 50%;
    top: var(--the-letter-card-top);
    width: var(--the-letter-card-w);
    height: var(--the-letter-card-h);
    margin-left: calc(var(--the-letter-card-w) / -2);
    /* Entry: the card's top edge at 92% of the screen (the polaroids' exit). */
    transform: translate3d(0, calc(92dvh - var(--the-letter-card-top)), 0);
    will-change: transform;
}
.the-letter-container.the-letter--in .the-letter-station {
    transform: none;
    animation: the-letter-rise 1.6s var(--ease-expo-out) backwards;
}
.the-letter-container.the-letter--leaving .the-letter-station {
    transform: translate3d(0, -115dvh, 0);
    animation: the-letter-leave 1.4s var(--ease-stage) backwards;
}

.the-letter-nudge {
    width: 100%;
    height: 100%;
}

/* ───── The cream note card ───── */
.the-letter-card {
    position: relative;
    width: 100%;
    height: 100%;
    background: var(--note-paper);
    border-radius: 3px;
    box-shadow: var(--note-shadow), inset 0 0 0 1px rgba(142, 42, 59, 0.05);
    overflow: hidden;
}
/* A long letter fades softly at the card's top and bottom edges as it
   glides (a static layer: nothing animates a gradient). */
.the-letter-card::after {
    content: '';
    position: absolute;
    inset: 0;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.9s var(--ease-silk);
    background: linear-gradient(to bottom,
        var(--note-paper) 0,
        rgba(251, 247, 236, 0) calc(var(--the-letter-pad-y) * 0.85),
        rgba(251, 247, 236, 0) calc(100% - var(--the-letter-pad-y) * 0.85),
        var(--note-paper) 100%);
}
.the-letter-container.the-letter--scrolls .the-letter-card::after { opacity: 1; }
/* At rest a letter that shows whole is not faded at its foot. */
.the-letter-container.the-letter--scrolls.the-letter--whole.the-letter--rested .the-letter-card::after { opacity: 0; }

.the-letter-scroll {
    position: absolute;
    inset: 0;
    box-sizing: border-box;
    padding: var(--the-letter-pad-y) var(--the-letter-pad-x);
    overflow: hidden;
    /* The same content width while writing and while rereading. */
    scrollbar-gutter: stable;
    scrollbar-width: thin;
    scrollbar-color: rgba(142, 42, 59, 0.22) transparent;
    overscroll-behavior: contain;
    touch-action: none;
}
.the-letter-container.the-letter--held .the-letter-scroll {
    overflow-x: hidden;
    overflow-y: auto;
    touch-action: pan-y;
    -webkit-overflow-scrolling: touch;
}
.the-letter-scroll:focus-visible {
    outline: 2px solid var(--wine);
    outline-offset: -4px;
}

/* ───── The letter (Caveat 500, the ink) ───── */
.the-letter-text {
    position: relative;
    display: flow-root;
    padding-bottom: var(--the-letter-tail, 0px);
    font-family: var(--font-hand);
    font-weight: 500;
    font-style: normal;
    font-size: var(--type-hand-letter);
    line-height: 1.5;
    letter-spacing: normal;
    word-spacing: normal;
    text-transform: none;
    text-align: left;
    color: var(--ink);
    will-change: transform;
}

/* The tail is only for the writing (so the row being written can reach
   70%); at rest there is no blank paper under the last line. */
.the-letter-container.the-letter--rested .the-letter-text { padding-bottom: 0; }

/* The hidden layout pass's draft: one block per hard line, wrapped by the
   browser, read once, then rebuilt as one .the-letter-line per row. */
.the-letter-draft { white-space: normal; overflow-wrap: normal; }
.the-letter-draft--para { margin-top: var(--the-letter-para-gap); }

.the-letter-line {
    position: relative;
    white-space: pre;
}
.the-letter-line--para { margin-top: var(--the-letter-para-gap); }
.the-letter-ink {
    display: inline-block;
    vertical-align: top;
}

/* The mask: the card's own colour over the unwritten line. It slides right
   by the line's measured ink width (--the-letter-w) over its paced duration
   (--the-letter-d); its left edge is the edge of the writing. */
.the-letter-mask {
    position: absolute;
    top: 0;
    bottom: 0;
    left: -0.3em;
    width: calc(100% + 2.4em);
    background: var(--note-paper);
    pointer-events: none;
}
/* The caret: a 2px x 1.1em wine bar on the mask's leading edge. */
.the-letter-mask::before {
    content: '';
    position: absolute;
    left: 0;
    top: calc(50% - 0.55em);
    width: 2px;
    height: 1.1em;
    border-radius: 1px;
    background: var(--wine);
    opacity: 0;
}
/* Waiting at the first line (0.8 s): blinking on `breath`. */
.the-letter-line[data-ink="wait"] .the-letter-mask::before {
    opacity: 1;
    animation: the-letter-blink 1.1s var(--ease-breath) infinite;
}
/* Writing: the wipe; the caret rides it solid, then blinks where the line ends. */
.the-letter-line[data-ink="writing"] .the-letter-mask {
    animation: the-letter-wipe var(--the-letter-d, 1s) linear var(--the-letter-lag, 0s) both;
}
.the-letter-line[data-ink="writing"] .the-letter-mask::before {
    opacity: 1;
    animation: the-letter-blink 1.1s var(--ease-breath) calc(var(--the-letter-d, 1s) + var(--the-letter-lag, 0s)) infinite;
}
.the-letter-line[data-ink="done"] .the-letter-mask { display: none; }

/* A layout that could not be measured (no rendered measurer): the lines
   wrap by themselves and fade in whole, on the same schedule. */
.the-letter-line--loose { white-space: normal; }
.the-letter-line--loose .the-letter-mask { display: none; }
.the-letter-line--loose:not([data-ink="writing"]):not([data-ink="done"]) { opacity: 0; }
.the-letter-line--loose[data-ink="writing"] {
    animation: the-letter-ink-in 0.4s var(--ease-silk) var(--the-letter-lag, 0s) backwards;
}

/* ───── The hint and the button, on the wash below the card ───── */
.the-letter-controls {
    position: absolute;
    left: 0;
    right: 0;
    bottom: calc(22px + env(safe-area-inset-bottom, 0px));
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 7px;
    pointer-events: none;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.6s var(--ease-silk), visibility 0s linear 0.6s;
}
.the-letter-container.the-letter--held .the-letter-controls {
    opacity: 1;
    visibility: visible;
    transition: opacity 0.8s var(--ease-silk), visibility 0s linear 0s;
}
.the-letter-container.the-letter--leaving .the-letter-controls {
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.3s var(--ease-silk), visibility 0s linear 0.3s;
}

.the-letter-chev {
    display: block;
    width: 8px;
    height: 8px;
    margin-top: 4px;
    border-left: 1.5px solid var(--wine);
    border-top: 1.5px solid var(--wine);
    transform: rotate(45deg);
    opacity: 0.8;
}
/* Parked unless it is showing. */
.the-letter-container.the-letter--held:not(.the-letter--leaving) .the-letter-chev {
    animation: the-letter-chev 1.8s var(--ease-breath) infinite;
}

.the-letter-hint {
    margin: 0;
    font-family: var(--font-body);
    font-weight: 400;
    font-size: var(--type-hint);
    line-height: 1.4;
    letter-spacing: 0.14em;
    text-transform: lowercase;
    color: var(--wine);
}

.the-letter-btn {
    min-height: 44px;
    min-width: 44px;
    padding: 0 22px;
    border-radius: 999px;
    border: 1px solid var(--glass-border);
    background: var(--glass);
    color: var(--wine);
    font-family: var(--font-body);
    font-weight: 400;
    font-size: var(--type-hint);
    letter-spacing: 0.14em;
    text-transform: lowercase;
    cursor: pointer;
    pointer-events: auto;
    -webkit-tap-highlight-color: transparent;
    transition: border-color 0.3s var(--ease-silk);
}
.the-letter-btn:hover { border-color: var(--glass-border-hover); }
.the-letter-btn:focus-visible {
    outline: 2px solid var(--wine);
    outline-offset: 3px;
}
.the-letter-btn:disabled { cursor: default; }

/* ───── The hidden twin the layout pass measures in (appended to <body>) ───── */
.the-letter-measure {
    position: fixed;
    left: 0;
    top: 0;
    width: 100vw;
    height: 100dvh;
    overflow: hidden;
    visibility: hidden;
    pointer-events: none;
    z-index: -1;
    contain: strict;
}

/* Entry states are applied instantly (prepare on goto), never animated in. */
.the-letter-instant,
.the-letter-instant * { transition: none !important; }

/* ───── Keyframes ───── */
@keyframes the-letter-rise {
    from { transform: translate3d(0, calc(92dvh - var(--the-letter-card-top)), 0); }
    to { transform: none; }
}
@keyframes the-letter-leave {
    from { transform: none; }
    to { transform: translate3d(0, -115dvh, 0); }
}
@keyframes the-letter-wipe {
    from { transform: translate3d(0, 0, 0); }
    to { transform: translate3d(var(--the-letter-w, 100%), 0, 0); }
}
@keyframes the-letter-blink {
    0%, 46% { opacity: 1; }
    54%, 100% { opacity: 0.12; }
}
@keyframes the-letter-chev {
    0%, 100% { transform: translateY(0) rotate(45deg); opacity: 0.8; }
    50% { transform: translateY(-4px) rotate(45deg); opacity: 0.45; }
}
@keyframes the-letter-ink-in {
    from { opacity: 0; }
    to { opacity: 1; }
}
@keyframes the-letter-fade-in {
    from { opacity: 0; }
    to { opacity: 1; }
}
@keyframes the-letter-fade-out {
    from { opacity: 1; }
    to { opacity: 0; }
}

/* ───── Reduced motion: plays in full, calmer ─────
   The card fades up in place (no rise); each line fades in whole (400 ms)
   on the same schedule as the wipe; no sliding mask, no caret; the exit is
   a fade. (The module also skips the nudge and the bubbles' parallax.) */
@media (prefers-reduced-motion: reduce) {
    .the-letter-station {
        transform: none;
        opacity: 0;
    }
    .the-letter-container.the-letter--in .the-letter-station {
        transform: none;
        opacity: 1;
        animation: the-letter-fade-in 1.2s var(--ease-silk) backwards;
    }
    .the-letter-container.the-letter--leaving .the-letter-station {
        transform: none;
        opacity: 0;
        animation: the-letter-fade-out 0.9s var(--ease-silk) backwards;
    }
    .the-letter-mask { display: none; }
    .the-letter-line:not([data-ink="writing"]):not([data-ink="done"]) { opacity: 0; }
    .the-letter-line[data-ink="writing"] {
        animation: the-letter-ink-in 0.4s var(--ease-silk) var(--the-letter-lag, 0s) backwards;
    }
    .the-letter-container.the-letter--held:not(.the-letter--leaving) .the-letter-chev { animation: none; }
}
