/*
 * v3/assets/css/v3.css
 *
 * MINIMAL. Everything visual that already existed — body flex layout, the
 * 33/67 split, .panel-scene, .choice-screen, .flow-nav, .btn, .q-chip,
 * .q-input, .bf-segment, mascot, bubble, biome palettes — STAYS in the
 * existing /assets/css/{app,panel-scene,mascot,questions}.css. Don't
 * duplicate any of it here.
 *
 * v3 adds only:
 *   - The trail strip (new)
 *   - The .v3-main wrapper for top-aligned list panels (settings, account…)
 *   - Panel content typography (title / heading / row label / divider)
 *   - The .v3-row-pills / .v3-row-segment containers (just spacing — the
 *     pill/segment LOOK comes from .q-chip and .bf-segment in app.css)
 *
 * If a rule below could go in app.css instead, it probably should.
 */

/* ── Parchment background ─────────────────────────────────────────────
   app.css sets `background-color: var(--paper)` on body with a layered
   repeating gradient on top, but our flex+!important shell can strip it
   on some WebKit builds. Force the base color back so the lower panel
   shows parchment, not white. */
html, body {
    background-color: var(--paper, #f1ead9) !important;
}

/* ── Speech bubble strip ───────────────────────────────────────────────
   A dedicated 10vh slab right under the scene panel. The legacy #bf-bubble
   (rendered inside the scene via partials/mascot.php) gets DOM-moved INTO
   this strip on page load by the inline script in v3/index.php — so
   BFMascot.say() keeps writing to the same element, just at the new spot.

   The strip itself shows parchment behind the bubble so it visually merges
   with the lower panel. The bubble inside fills the strip with 5vw padding
   each side. Tail (::before/::after) is hidden — bubble's no longer next
   to his mouth. */
.v3-bubble-strip {
    flex: 0 0 10vh;
    min-height: 72px;
    max-height: 14vh;
    background: var(--paper, #f1ead9);
    position: relative;
    z-index: 5;
}
/* Settings menu (settings + its submenus) hides the bubble strip so the
   panel gets the full height below the scene. .v3-main is flex:1 1 auto,
   so it expands into the freed space automatically. */
html[data-panel="settings"] .v3-bubble-strip,
html[data-panel="account"] .v3-bubble-strip,
html[data-panel="connections"] .v3-bubble-strip {
    display: none;
}
.v3-bubble-strip .bf-bubble,
.bf-bubble {
    position: absolute !important;
    /* Centred in the strip, sized to its text, capped at two lines. Anything
       longer clips (hidden) rather than scrolling or ballooning the box. */
    top: 50% !important;
    bottom: auto !important;
    left: 50% !important;
    right: auto !important;
    width: 90% !important;
    max-width: 640px !important;
    height: auto !important;
    /* two lines: 2 × 1.3 line-height (2.6em) + vertical padding (16) + borders (6). */
    max-height: calc(2.6em + 22px) !important;
    min-height: 0 !important;
    margin: 0 !important;
    transform: translate(-50%, -50%) !important;
    overflow: hidden;
    background: var(--card, #fbf8ef) !important;
    color: var(--ink, #2a241c) !important;
    border: 3px solid var(--ink, #2f281f) !important;
    border-radius: 22px 18px 24px 20px / 18px 22px 20px 24px !important;
    padding: 8px 16px !important;
    font-family: var(--note, 'Kalam', cursive);
    font-size: 16px;
    line-height: 1.3;
    text-align: left;
    box-shadow: var(--shadow, 3px 3px 0 var(--ink, #2f281f));
    z-index: 1;
    box-sizing: border-box;
    /* DO NOT use display:flex here — flex children strip the whitespace
       between .bf-word spans so all the words run together with no spaces.
       Block display + word-spacing/line-height handles wrapping naturally. */
    display: block;
}
/* On wide screens (desktop / tablet landscape) the 90% bubble gets too
   wide to read comfortably — pull it in to ~70%. */
@media (min-width: 700px) {
    .v3-bubble-strip .bf-bubble,
    .bf-bubble { width: 70% !important; }
}
.bf-bubble::before,
.bf-bubble::after {
    display: none !important;
}

/* No extra padding-top on .v3-main — the strip is in flow above it
   and takes its own height. */

/* ── Mascot — allow overflow so the head/hair don't clip on bounce ──── */
.mascot-slot {
    overflow: visible !important;
}
.mascot-slot .bf,
.mascot-slot svg {
    overflow: visible !important;
}

/* Date/time row sizing + position moved into /assets/css/panel-scene.css
   (the source of truth). Don't add v3-overrides for those properties
   here — they'll fight the legacy layout and Tim will (rightly) call it
   out. If a scene-panel tweak is needed for v3, edit panel-scene.css. */

/* ── Ground extends past the quote box ────────────────────────────────
   The ground used to stop at the scene's swooping bottom edge, right above
   the quote strip. Now the ground continues DOWN through the quote strip:
   the scene's bottom goes flat (no edge), and the bubble strip becomes the
   ground "apron" Big Foot's patch of earth runs onto — same dirt fill as
   the scene's ground, with the swooping, ink-bordered bottom edge moved
   down here so it lands BELOW the quote box. Big Foot doesn't move; only
   the ground reaches further. Ground colours track <html data-phase> so
   the apron darkens at dusk/night with the rest of the scene. */
.panel-scene {
    border-bottom: 0 !important;
    border-bottom-left-radius: 0 !important;
    border-bottom-right-radius: 0 !important;
}

.v3-bubble-strip {
    background: var(--ground-fill, #d6cfb8) !important;
    border-bottom: 3px solid var(--ground-line, #2f281f);
    /* Very-rounded-square bottom: a fixed, generous corner radius (not a
       full-width 50% ellipse, which bowed into one big centre bump). The
       slight 56/60 asymmetry keeps the hand-drawn feel. */
    border-bottom-left-radius:  56px;
    border-bottom-right-radius: 60px;
}
/* Per-phase ground tint for the apron (the scene itself gets these from its
   own .phase-* palette; the strip is a sibling so it keys off <html>). */
html[data-phase="dawn"]  .v3-bubble-strip { background: #d4c8a6 !important; }
html[data-phase="dusk"]  .v3-bubble-strip { background: #b8a78a !important; }
html[data-phase="night"] .v3-bubble-strip {
    background: #2a3038 !important;
    border-bottom-color: #0e1220;
}

/* When a settings panel hides the strip, the scene keeps its own swooping
   edge so the bottom of the scene still reads as a finished page. */
html[data-panel="settings"] .panel-scene,
html[data-panel="account"] .panel-scene,
html[data-panel="connections"] .panel-scene {
    border-bottom: 3px solid var(--ground-line, #2f281f) !important;
    border-bottom-left-radius:  56px !important;
    border-bottom-right-radius: 60px !important;
}

/* ── Trail strip (when a panel sets show_trail: true) ─────────────── */
.v3-trail {
    flex: 0 0 56px;
    background: var(--card, #fbf8ef);
    border-bottom: 2px solid var(--ink, #2f281f);
    display: flex;
    align-items: center;
    justify-content: center;
}
.v3-trail-placeholder {
    font-family: var(--scrawl, 'Caveat', cursive);
    color: var(--ink-soft, #6a5f51);
    font-size: 15px;
}

/* ── Lower-panel wrapper ──────────────────────────────────────────────
   When the panel's content doesn't fill the viewport (story knot, auth
   method-pick, onboarding hello, etc.), it sits centered vertically with
   even padding above and below the buttons. When content overflows (long
   settings list), it falls back to top-aligned and scrolls — `safe center`
   makes the flexbox revert to flex-start so the top doesn't get cut off
   and become unscrollable. Bottom padding leaves room for the fixed
   .flow-nav bar. */
.v3-main {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 18px 18px 96px;
    width: 100%;
    max-width: 480px;
    margin: 0 auto;
    display: flex;
    flex-direction: column;
    justify-content: safe center;
}

/* A touch more breathing room between stacked big buttons (story knot,
   auth method-pick). Default row margin is 14px; large buttons get 18. */
.v3-row:has(> .btn-lg) {
    margin-bottom: 18px;
}

/* ── In-panel navigation transitions ──────────────────────────────────
   shell.js navigateTo() swaps #v3-panel's contents instead of reloading
   the document (which used to flash the native black canvas). These animate
   the swap so a screen change reads as motion within one screen: the old
   rows fade/lift out, the new rows stagger in. Respects reduced-motion. */
@media (prefers-reduced-motion: no-preference) {
    .v3-main.v3-leaving {
        opacity: 0;
        transform: translateY(-6px);
        transition: opacity .12s ease, transform .12s ease;
    }
    .v3-main.v3-entering > * {
        animation: v3RowIn .24s ease both;
    }
    .v3-main.v3-entering > *:nth-child(1) { animation-delay: 0ms; }
    .v3-main.v3-entering > *:nth-child(2) { animation-delay: 35ms; }
    .v3-main.v3-entering > *:nth-child(3) { animation-delay: 70ms; }
    .v3-main.v3-entering > *:nth-child(4) { animation-delay: 105ms; }
    .v3-main.v3-entering > *:nth-child(5) { animation-delay: 140ms; }
    .v3-main.v3-entering > *:nth-child(n+6) { animation-delay: 175ms; }
}
@keyframes v3RowIn {
    from { opacity: 0; transform: translateY(10px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* ── Panel content typography ─────────────────────────────────────── */
.v3-panel-title {
    font-family: var(--scrawl, 'Caveat', cursive);
    font-weight: 700;
    font-size: 24px;
    margin: 0 0 14px;
    color: var(--ink, #2a241c);
}

/* Top-of-panel back link (settings → home, sub-panels → settings). Plain
   text link, left-aligned, sits above the title. */
.v3-panel-back {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-family: var(--note, 'Kalam', cursive);
    font-size: 16px;
    color: var(--ink-soft, #6a5f51);
    text-decoration: none;
    margin: 0 0 10px;
    -webkit-tap-highlight-color: transparent;
}
.v3-panel-back span { font-size: 18px; line-height: 1; }

/* Hero name input — centered, big, NOT full width. Used by the new-user
   name page so the first thing typed feels personal, not like a form field. */
.v3-input-hero {
    display: block;
    width: auto;
    max-width: 14ch;
    margin: 0 auto;
    text-align: center;
    font-family: var(--scrawl, 'Caveat', cursive);
    font-size: 34px;
    font-weight: 700;
}

/* Centered input that keeps the normal field size/font (e.g. the sign-in
   email field). Unlike v3-input-hero this is not the handwriting hero look. */
.v3-input-center {
    display: block;
    margin: 0 auto;
    max-width: 22ch;
    text-align: center;
}

/* App-wide: this is an app, not a document — kill text selection and the
   iOS long-press callout everywhere. The legacy app.css block only covers
   .bfw-page/.panel-scene containers, which the v3 shell doesn't use, so
   v3 text stayed selectable. Two symptoms from that: (1) you could highlight
   any label, (2) a press-and-hold on a button started a text selection
   instead of firing the button's :active pressed state. This fixes both.
   Inputs/textareas re-enable selection so typed values stay editable. */
body {
    -webkit-user-select: none;
    user-select: none;
    -webkit-touch-callout: none;
    -webkit-tap-highlight-color: transparent;
}
input, textarea, [contenteditable], [contenteditable] * {
    -webkit-user-select: text;
    user-select: text;
    -webkit-touch-callout: default;
}

.v3-row { margin: 0 0 14px; }
.v3-row-label {
    display: block;
    font-family: var(--note, 'Kalam', cursive);
    font-size: 14px;
    color: var(--ink-soft, #6a5f51);
    margin-bottom: 6px;
}
.v3-row-heading {
    font-family: var(--scrawl, 'Caveat', cursive);
    font-size: 18px;
    margin: 16px 0 6px;
    color: var(--ink, #2a241c);
}
.v3-row-text {
    font-size: 16px;
    line-height: 1.4;
    margin: 0 0 12px;
    color: var(--ink, #2a241c);
}
.v3-row-divider {
    border: 0;
    border-top: 2px dashed rgba(47, 40, 31, 0.25);
    margin: 14px 0;
}

/* Inline mascot row (used by knot pages that need an in-panel bubble) */
.v3-row-mascot {
    display: flex;
    justify-content: center;
    margin: 6px 0 16px;
}
.v3-row-mascot-bubble {
    background: var(--card, #fbf8ef);
    border: 2px solid var(--ink, #2f281f);
    border-radius: 18px 22px 17px 21px;
    padding: 10px 14px;
    max-width: 320px;
    text-align: center;
    color: var(--ink, #2a241c);
}

/* ── Stylized box (age-gate consent panel) ────────────────────────────
   A framed sketchbook card grouping the agreement copy, doc links, and the
   "I agree" switch. Mirrors the .btn ink-border + offset-shadow look. */
.v3-box {
    background: var(--card, #fbf8ef);
    border: 3px solid var(--ink, #2f281f);
    border-radius: 20px 16px 18px 14px / 14px 18px 16px 20px;
    box-shadow: var(--shadow, 3px 3px 0 var(--ink, #2f281f));
    padding: 16px 18px;
    margin: 0 0 14px;
}
.v3-box > .v3-row:last-child,
.v3-box > .v3-row-text:last-child,
.v3-box > .v3-row-links:last-child {
    margin-bottom: 0;
}
/* The toggle inside the consent box drops its own frame — the box IS the
   frame, so the switch sits flush with the linked label left, switch right. */
.v3-consent-box .v3-row-toggle { margin: 0; }
.v3-consent-box .bfw-toggle-row {
    border: 0;
    background: transparent;
    padding: 0;
    border-radius: 0;
}
/* Inline doc links in the agree label + any consent copy. */
.v3-consent-box .bfw-toggle-label a,
.v3-consent-box .v3-row-text a {
    color: var(--ink, #2a241c);
    text-decoration: underline;
    text-underline-offset: 2px;
}
/* Fine print (medical disclaimer) under the switch. */
.v3-consent-box .v3-text-fine {
    font-size: 13px;
    line-height: 1.35;
    color: var(--ink-soft, #6a5f51);
    margin: 10px 0 0;
}

/* ── Universal digit-code input ───────────────────────────────────────
   Marker-style boxes, one per digit. Works for 4-digit (magic verify)
   AND 6-digit (PIN). Same .btn-feel: thick ink border, drop shadow,
   roughen filter, press-into-page on focus. Centered, generous size so
   they're easy to tap on a phone. */
.v3-code-input {
    display: flex;
    justify-content: center;
    gap: 12px;
    margin: 8px 0 4px;
}
.v3-code-input input {
    width: 56px;
    height: 64px;
    border: 3px solid var(--ink, #2f281f);
    border-radius: 14px 12px 13px 11px / 12px 13px 11px 14px;
    background: var(--card, #fbf8ef);
    color: var(--ink, #2a241c);
    font-family: var(--hand, 'Patrick Hand', cursive);
    font-size: 32px;
    font-weight: 700;
    text-align: center;
    box-shadow: var(--shadow-sm, 2px 2px 0 var(--ink, #2f281f));
    filter: url(#roughen);
    transition: transform .08s, box-shadow .08s, background .15s;
    outline: none;
    /* Hide the keyboard-style spinner that Safari shows on focused number-like inputs. */
    -moz-appearance: textfield;
}
.v3-code-input input::-webkit-outer-spin-button,
.v3-code-input input::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
}
.v3-code-input input:focus {
    background: var(--card2, #f4eee0);
    transform: translate(1px, 1px);
    box-shadow: 1px 1px 0 var(--ink, #2f281f);
}
.v3-code-input.is-invalid {
    animation: v3-code-shake .55s cubic-bezier(.36,.07,.19,.97) both;
}
.v3-code-input.is-invalid input {
    border-color: var(--warn, #b14b3a);
    background: #fbe9e3;
}
/* Bigger shake — the previous 4px / 350ms barely registered. Now ±12px
   for 550ms with a snappy cubic-bezier (Material's "wrong-password" shake). */
@keyframes v3-code-shake {
    0%, 100% { transform: translateX(0); }
    10%       { transform: translateX(-12px); }
    20%       { transform: translateX(12px); }
    30%       { transform: translateX(-10px); }
    40%       { transform: translateX(10px); }
    50%       { transform: translateX(-7px); }
    60%       { transform: translateX(7px); }
    70%       { transform: translateX(-4px); }
    80%       { transform: translateX(4px); }
    90%       { transform: translateX(-2px); }
}

/* Text-input validation (e.g. name needs ≥2 chars). Reuses the code-input
   shake; warn border + soft red fill while invalid. */
.q-input.is-invalid {
    animation: v3-code-shake .55s cubic-bezier(.36,.07,.19,.97) both;
    border-color: var(--warn, #b14b3a) !important;
    background: #fbe9e3 !important;
}
.v3-input-error {
    margin: 8px 2px 0;
    font-size: 14px;
    color: var(--warn, #b14b3a);
}
.v3-input-error[hidden] { display: none; }
/* Segment (age gate) shake when required/blocked on advance. */
.bf-segment.is-invalid {
    animation: v3-code-shake .55s cubic-bezier(.36,.07,.19,.97) both;
}

/* "Send another" cooldown — gray + disabled for the first 15s after the
   check_email page renders, with a small countdown label. shell.js
   handles the timing. */
.btn.v3-cooldown {
    opacity: 0.45;
    pointer-events: none;
}

/* Pills container — pills themselves are .q-chip from questions.css */
.v3-pills-group {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
}

/* ── Pill charm pass ──────────────────────────────────────────────────
   The legacy .q-chip in questions.css is a thin chip (1.8px border, no
   shadow, no roughen, just scale-down on press). It looks NOTHING like
   the .btn treatment in app.css. We promote the chip to look like a
   button — full 3px ink border, full var(--shadow), roughen filter, the
   3px press-translate. Same hand-drawn marker treatment as .btn, just
   smaller font + padding so the visual hierarchy stays (chips < buttons).

   Selector is .v3-pills-group .q-chip so specificity (0,2,0) is above
   the legacy .q-chip (0,1,0) even without !important — !important kept
   as a belt-and-suspenders for sub-pixel changes in a future cascade. */
.v3-pills-group .q-chip {
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    font-family: var(--hand, 'Patrick Hand', cursive) !important;
    font-size: 17px !important;
    font-weight: 700 !important;
    letter-spacing: 0.01em !important;
    color: var(--ink, #2a241c) !important;
    background: var(--card, #fbf8ef) !important;
    border: 3px solid var(--ink, #2f281f) !important;
    border-radius: 20px 16px 18px 14px / 14px 18px 16px 20px !important;
    padding: 12px 18px !important;
    box-shadow: var(--shadow, 3px 3px 0 var(--ink, #2f281f)) !important;
    filter: url(#roughen);
    transition: transform .08s, box-shadow .08s, background .15s, color .15s !important;
    cursor: pointer !important;
}
/* Only real pointers get hover. On touch (iOS), :hover sticks after a tap
   and — because the hover selector outranks .sel — it would mask the green
   selected state until you tapped elsewhere ("first tap doesn't turn green"). */
@media (hover: hover) and (pointer: fine) {
    .v3-pills-group .q-chip:hover:not(:disabled) {
        background: var(--card2, #f4eee0) !important;
    }
}
.v3-pills-group .q-chip:active {
    transform: translate(3px, 3px) !important;
    box-shadow: 0 0 0 var(--ink, #2f281f) !important;
}
.v3-pills-group .q-chip.sel {
    background: var(--accent, #2f8f86) !important;
    color: #fff !important;
    border-color: var(--ink, #2f281f) !important;     /* keep ink border on selected */
}
.v3-pills-group .q-chip.sel:active {
    transform: translate(3px, 3px) !important;
    box-shadow: 0 0 0 var(--ink, #2f281f) !important;
}

/* ── Segment charm pass ───────────────────────────────────────────────
   The settings option selectors (auto-lock, vibration, sounds) use the
   `segment` row type → legacy .bf-segment, a thin joined toolbar with no
   border/shadow/roughen on the individual buttons. Tim wants them to wear
   the same hand-drawn button treatment as the pills/buttons. We unbundle
   the joined bar into separate chips and give each the .q-chip look. */
.v3-row-segment .bf-segment {
    display: flex !important;
    flex-wrap: wrap !important;
    gap: 9px !important;
    border: none !important;
    border-radius: 0 !important;
    overflow: visible !important;
    background: transparent !important;
    width: 100% !important;
}
.v3-row-segment .bf-segment button {
    flex: 1 1 auto !important;
    border: 3px solid var(--ink, #2f281f) !important;
    border-radius: 20px 16px 18px 14px / 14px 18px 16px 20px !important;
    background: var(--card, #fbf8ef) !important;
    color: var(--ink, #2a241c) !important;
    font-family: var(--hand, 'Patrick Hand', cursive) !important;
    font-size: 16px !important;
    font-weight: 700 !important;
    padding: 11px 14px !important;
    box-shadow: var(--shadow, 3px 3px 0 var(--ink, #2f281f)) !important;
    filter: url(#roughen);
    transition: transform .08s, box-shadow .08s, background .15s, color .15s !important;
}
.v3-row-segment .bf-segment button.sel {
    background: var(--accent, #2f8f86) !important;
    color: #fff !important;
    border-color: var(--ink, #2f281f) !important;
}
.v3-row-segment .bf-segment button:active {
    transform: translate(3px, 3px) !important;
    box-shadow: 0 0 0 var(--ink, #2f281f) !important;
    background: var(--card, #fbf8ef) !important;
}
.v3-row-segment .bf-segment button.sel:active {
    transform: translate(3px, 3px) !important;
    box-shadow: 0 0 0 var(--ink, #2f281f) !important;
    background: var(--accent, #2f8f86) !important;
}

/* Inline links (legal row, etc.) */
.v3-row-links {
    display: flex;
    gap: 14px;
    justify-content: center;
    font-size: 13px;
    margin-top: 12px;
}
.v3-row-links a {
    color: var(--ink-soft, #6a5f51);
    text-decoration: underline;
}

/* ── Goal deck ─────────────────────────────────────────────────────
   Stacked sketchbook cards. The top card (--stack-i:0) is the active
   one; cards below it peek through at increasing offsets so the user
   sees the deck has depth. */
.v3-deck {
    position: relative;
    width: 100%;
    height: 320px;
    margin: 18px 0 26px;
    touch-action: none;             /* let pointermove drive the swipe */
    user-select: none;
    /* Lift the deck above adjacent panel content so cards aren't clipped
       by sibling rows or the panel's own paint stack. */
    z-index: 100;
}
.v3-deck-card {
    position: absolute;
    inset: 0;
    background: var(--card, #fbf5e6);
    color: var(--ink, #2d2622);
    border: 3px solid var(--ink, #2d2622);
    /* Hand-drawn irregular radius — same trick the box rows use. */
    border-radius: 22px 28px 19px 26px;
    box-shadow: 4px 5px 0 var(--ink, #2d2622);
    padding: 22px 22px 18px;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    will-change: transform;
    /* Heilmann's bottom-pivot trick: rotation hinges at the bottom-center so
       the card TIPS like a physical card being flicked, not spins around
       its middle. Much more natural read. */
    transform-origin: 50% 100%;
    /* Stack offset: each card sits a bit lower and smaller behind the one
       above it. Tilt slightly randomly so the deck reads as hand-stacked.
       --reveal is driven by deck.js — as the top card moves, the card
       behind scales up by reveal amount (depth breathes). */
    transform: translateY(calc(var(--stack-i, 0) * 10px))
               scale(calc(1 - var(--stack-i, 0) * 0.035 + var(--reveal, 0) * 0.035))
               rotate(calc(var(--stack-i, 0) * -0.6deg));
    z-index: calc(10 - var(--stack-i, 0));
    transition: transform 240ms cubic-bezier(.22,1.4,.36,1);
}
.v3-deck-card[data-stack-i="0"] {
    cursor: grab;
}
.v3-deck-card[data-stack-i="0"]:active { cursor: grabbing; }
.v3-deck-card-category {
    font-family: var(--scrawl, "Caveat", cursive);
    font-size: 1.1rem;
    color: var(--ink-soft, #6a5f51);
    margin-bottom: 8px;
    text-transform: lowercase;
}
.v3-deck-card-text {
    font-family: var(--note, "Patrick Hand", cursive);
    font-size: 1.4rem;
    line-height: 1.3;
    flex: 1;
    display: flex;
    align-items: center;
}
/* Ink-stamp direction labels — one per swipe direction, positioned at the
   matching card edge. Opacity is driven by --dir-up/--dir-right/--dir-left/
   --dir-down (0..1), each set by deck.js as the finger moves. So the
   stamps fade in SMOOTHLY in proportion to how far you've pushed in that
   direction — the user always sees exactly which action they're committing
   to before they release. (Borrowed from the v1 hahajk deck.) */
.v3-deck-dir {
    position: absolute;
    font-family: var(--note, "Patrick Hand", cursive);
    font-weight: 800;
    font-size: 1.25rem;
    letter-spacing: .09em;
    padding: 5px 13px;
    border: 3px solid currentColor;
    border-radius: 11px 13px 10px 12px;
    background: rgba(251, 245, 230, 0.92);
    pointer-events: none;
    z-index: 5;
    text-transform: uppercase;
    white-space: nowrap;
}
/* Stamps live on the OPPOSITE side of the swipe direction so the label
   stays visible as the card moves away — swipe up (DONE) shows the stamp
   at the bottom; swipe down (ANOTHER) shows it at the top; swipe right
   (ON THE LIST) shows it at the left; swipe left (NOT TODAY) at the right. */
.v3-deck-dir-up    { bottom: 14px; left: 50%; transform: translateX(-50%) rotate(-4deg); color: #3c965a; opacity: var(--dir-up,    0); }
.v3-deck-dir-down  { top: 14px;    left: 50%; transform: translateX(-50%) rotate( 3deg); color: #aa8c3c; opacity: var(--dir-down,  0); }
.v3-deck-dir-right { top: 50%; left:  14px; transform: translateY(-50%) rotate(-10deg); color: #5a82c8; opacity: var(--dir-right, 0); }
.v3-deck-dir-left  { top: 50%; right: 14px; transform: translateY(-50%) rotate( 10deg); color: #b45a46; opacity: var(--dir-left,  0); }

/* Lean-direction soft glow on the shadow side. Kept as a secondary cue. */
.v3-deck-card.lean-up    { box-shadow: 4px 5px 0 var(--ink, #2d2622), 0 -22px 32px -10px rgba(60,150,90,.45); }
.v3-deck-card.lean-down  { box-shadow: 4px 5px 0 var(--ink, #2d2622), 0  22px 32px -10px rgba(170,140,60,.45); }
.v3-deck-card.lean-right { box-shadow: 4px 5px 0 var(--ink, #2d2622),  22px 0 32px -10px rgba(90,130,200,.45); }
.v3-deck-card.lean-left  { box-shadow: 4px 5px 0 var(--ink, #2d2622), -22px 0 32px -10px rgba(180, 90, 70,.45); }

/* Past-threshold: ink the border bolder so the user sees it's "armed". */
.v3-deck-card.past-soft { border-width: 4px; }
.v3-deck-card.past-hard { border-width: 5px; filter: saturate(1.1); }

.v3-deck.v3-deck-empty {
    height: auto;
    min-height: 80px;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 16px;
    border: 2px dashed var(--ink-soft, #6a5f51);
    border-radius: 18px 22px 19px 24px;
    background: transparent;
}
.v3-deck-empty-line {
    font-family: var(--scrawl, "Caveat", cursive);
    font-size: 1.15rem;
    color: var(--ink-soft, #6a5f51);
    margin: 0;
    text-align: center;
}
