/* Card Cascade v2 - the native-feel UI.
   Design language: iOS system-grouped surfaces, the brand's dictionary
   voice (Georgia) reserved for French content and annotations, SF/system
   type for chrome. Light and dark are both first-class. */

/* Chrome-on-Android turns a top overscroll into pull-to-refresh -
   fatal mid-review (html/body already cut the chain; these cover the
   inner scrollers). Scrolling itself stays fully native. */
.v2-view, .v2-sheet-scroll { overscroll-behavior-y: contain; }

:root {
    --bg: #f2f2f7;
    --surface: #ffffff;
    --surface-2: #f7f7fa;
    --ink: #1c1c1e;
    --ink-2: #6e6e73;
    --ink-3: #aeaeb2;
    --hairline: rgba(60, 60, 67, 0.14);
    --accent: #2b5ca8;
    --accent-soft: #e7f0fb;
    --green: #2e7d52;
    --green-soft: #e7f6ec;
    --red: #a8433c;
    --red-soft: #f7ecea;
    /* the guided walk's veil: the page outside the ring fades toward
       its own background colour. It reads --bg where it is used, so
       dark mode needs no second definition (v2-tour.js). */
    --v2tour-veil: color-mix(in srgb, var(--bg) 62%, transparent);
    --card-shadow: 0 1px 2px rgba(0,0,0,0.04), 0 8px 24px rgba(30,40,60,0.07);
    --tabbar-bg: rgba(249, 249, 251, 0.82);
    --spring: cubic-bezier(0.32, 1.35, 0.45, 1);
    --ease-out: cubic-bezier(0.22, 0.8, 0.3, 1);
}

@media (prefers-color-scheme: dark) {
    :root {
        --bg: #000000;
        --surface: #1c1c1e;
        --surface-2: #2c2c2e;
        --ink: #f2f2f7;
        --ink-2: #98989f;
        --ink-3: #636368;
        --hairline: rgba(84, 84, 88, 0.5);
        --accent: #7aa5e8;
        --accent-soft: #1d2c44;
        --green: #63b98a;
        --green-soft: #16291f;
        --red: #d8887f;
        --red-soft: #2f1b18;
        --card-shadow: 0 1px 2px rgba(0,0,0,0.3), 0 8px 24px rgba(0,0,0,0.35);
        --tabbar-bg: rgba(22, 22, 24, 0.82);
    }
}

* { -webkit-tap-highlight-color: transparent; box-sizing: border-box; }

html, body {
    margin: 0; padding: 0; height: 100%;
    background: var(--bg);
    color: var(--ink);
    font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui, "Helvetica Neue", Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
    overscroll-behavior: none;
}

#v2 { height: 100%; display: flex; flex-direction: column; }

/* Content scrolls under the status bar; this keeps the clock legible -
   a frost that fades out just below the notch, standard iOS treatment.
   Zero-height on screens with no inset. */
#v2-statusbar-scrim {
    position: fixed; top: 0; left: 0; right: 0;
    height: calc(env(safe-area-inset-top, 0px) + 6px);
    z-index: 50; pointer-events: none;
    background: linear-gradient(to bottom,
        color-mix(in srgb, var(--bg) 88%, transparent) 70%, transparent);
    -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
    -webkit-mask-image: linear-gradient(to bottom, black 70%, transparent);
    mask-image: linear-gradient(to bottom, black 70%, transparent);
}

/* flight chips (z-index 40) must paint above the feeder and cascade */
#v2-feeder, #v2-cascade { position: relative; z-index: 1; }

/* ---------- views + tab bar ---------- */
#v2-views { flex: 1; overflow: hidden; position: relative; }

.v2-view {
    /* the app anchors scroll itself (cascade pinned through feeder
       reflows); the browser's own scroll anchoring would compensate
       a second time and push the cascade by the height delta */
    overflow-anchor: none;
    position: absolute; inset: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: calc(env(safe-area-inset-top, 0px) + 14px) 18px
             calc(env(safe-area-inset-bottom, 0px) + 24px) 18px;
    opacity: 0; pointer-events: none;
    transform: translateY(6px);
    transition: opacity 0.22s var(--ease-out), transform 0.22s var(--ease-out);
}

.v2-view.active { opacity: 1; pointer-events: auto; transform: none; }

/* ---------- shared primitives ---------- */
.v2-largetitle {
    font-size: 33px; font-weight: 800; letter-spacing: -0.02em;
    margin: 8px 2px 2px 2px;
}

.v2-subtitle { font-size: 14px; line-height: 1.45; color: var(--ink-2); margin: 0 2px 18px 2px; max-width: 34em; }

.v2-card {
    background: var(--surface);
    border-radius: 20px;
    box-shadow: var(--card-shadow);
    padding: 18px;
    margin-bottom: 14px;
}

.v2-section-label {
    font-size: 11px; font-weight: 700; text-transform: uppercase;
    letter-spacing: 0.12em; color: var(--ink-3);
    margin: 22px 4px 8px 4px;
}

.v2-btn {
    appearance: none; border: none; width: 100%;
    background: var(--accent); color: #fff;
    font-size: 17px; font-weight: 700; letter-spacing: -0.01em;
    padding: 15px 20px; border-radius: 15px;
    transition: transform 0.15s var(--spring), filter 0.15s ease;
}

.v2-btn:active { transform: scale(0.97); filter: brightness(1.08); }
.v2-btn.secondary { background: var(--surface-2); color: var(--accent); }
.v2-btn:disabled { opacity: 0.4; }

.v2-chip-row { display: flex; gap: 10px; margin-bottom: 14px; }

.v2-chip {
    flex: 1; background: var(--surface); border-radius: 16px;
    box-shadow: var(--card-shadow);
    padding: 12px 8px; text-align: center;
}

.v2-chip .num { font-size: 22px; font-weight: 800; letter-spacing: -0.02em; }
.v2-chip .lbl { font-size: 11px; color: var(--ink-2); margin-top: 2px; }

/* ---------- Today ---------- */
.v2-hero { text-align: left; }

.v2-hero .due-num {
    font-size: 54px; font-weight: 800; letter-spacing: -0.03em; line-height: 1;
}

.v2-hero .due-lbl { font-size: 15px; color: var(--ink-2); margin: 6px 0 16px 0; }

.v2-hero.done { text-align: center; padding: 26px 18px; }
.v2-hero.done .big { font-size: 40px; }
.v2-hero.done .msg { font-size: 16px; color: var(--ink-2); margin-top: 8px; }

.v2-levelbar { margin: 6px 0 2px 0; }

.v2-levelbar .track {
    height: 6px; border-radius: 3px; background: var(--surface-2);
    overflow: hidden; margin-top: 8px;
}

.v2-levelbar .fill {
    height: 100%; border-radius: 3px; background: var(--accent);
    transition: width 0.6s var(--ease-out);
}

.v2-levelbar .meta {
    display: flex; justify-content: space-between;
    font-size: 12px; color: var(--ink-2); margin-top: 6px;
}

.v2-row {
    display: flex; align-items: center; gap: 12px;
    padding: 13px 2px; border-bottom: 0.5px solid var(--hairline);
}

.v2-row:last-child { border-bottom: none; }
.v2-row .grow { flex: 1; min-width: 0; }
.v2-row .title { font-size: 16px; font-weight: 600; }
.v2-row .sub { font-size: 13px; color: var(--ink-2); margin-top: 1px; }

.v2-pill {
    appearance: none; border: none;
    background: var(--accent-soft); color: var(--accent);
    font-size: 14px; font-weight: 700;
    padding: 8px 16px; border-radius: 999px;
    white-space: nowrap;
    transition: transform 0.15s var(--spring);
}

.v2-pill:active { transform: scale(0.94); }
.v2-pill.green { background: var(--green-soft); color: var(--green); }

/* Away from the start view, big chevrons return to it - sitting on the
   edge they point toward: at the BOTTOM pointing down while you wander
   the feeder above, at the TOP pointing up when you sink below. */
#v2-tocasc {
    position: fixed; bottom: calc(env(safe-area-inset-bottom, 0px) + 16px);
    left: 50%; transform: translateX(-50%); z-index: 55;
    appearance: none; border: 0.5px solid var(--hairline);
    width: auto; min-width: 68px; padding: 0 12px;
    height: 42px; border-radius: 999px;
    background: color-mix(in srgb, var(--bg) 74%, transparent);
    -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
    color: var(--accent);
    display: flex; align-items: center; justify-content: center;
    box-shadow: 0 4px 14px rgba(15, 23, 42, 0.14);
    transition: transform 0.15s var(--spring);
}
#v2-tocasc[hidden] { display: none; }

/* THE SYNC PILL. A structural label, not a badge: it names a state of
   the machine in the same voice the group names and feeder levels use,
   frosted like the cascade pill so it belongs to the chrome rather
   than to the page. It sits to the LEFT of the help button at the top
   right, above the status-bar scrim (50) and the session layer (60),
   below the toast (80) - which is what answers when it is tapped. */
#v2-sync {
    position: fixed; z-index: 72;
    top: calc(env(safe-area-inset-top, 0px) + 10px); right: 58px;
    appearance: none; border: 0.5px solid var(--hairline);
    height: 28px; padding: 0 10px; border-radius: 999px;
    background: color-mix(in srgb, var(--bg) 74%, transparent);
    -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
    box-shadow: 0 4px 14px rgba(15, 23, 42, 0.14);
    display: flex; align-items: center; gap: 6px;
    font-family: inherit; font-size: 11px; font-weight: 700;
    letter-spacing: 0.12em; text-transform: uppercase;
    color: var(--ink-2); white-space: nowrap;
    opacity: 0; transition: opacity 0.25s ease, transform 0.15s var(--spring);
}
#v2-sync.show { opacity: 1; }
#v2-sync[hidden] { display: none; }
#v2-sync:active { transform: scale(0.94); }
#v2-sync .s-glyph { display: flex; }
#v2-sync svg { width: 14px; height: 14px; display: block; }
/* the one state that is not routine: neither store would take the queue */
#v2-sync.bad { color: var(--red); }
/* The back-to-the-cascade pill shares this row when it is sunk to the
   top edge, and it is CENTRED, so a long count would run into it.
   While the sync pill is up, the chevron takes the left half. */
.v2-sync-on #v2-tocasc.up { left: 32%; }
/* A sheet is a modal with its own scrim, and the reset's busy state
   stops the page taking taps: the whole top-right row stands down. */
.v2-chrome-off #v2-sync { display: none; }

#v2-tocasc svg { width: 26px; height: 26px; display: block; }
#v2-tocasc .tc-lbl {
    font-size: 11px; font-weight: 700; letter-spacing: 0.12em;
    text-transform: uppercase; padding-left: 6px;
}
/* sunk below the start view: jump to the top edge, point back up */
#v2-tocasc.up {
    bottom: auto; top: calc(env(safe-area-inset-top, 0px) + 10px);
}
#v2-tocasc.up svg { transform: rotate(180deg); }
#v2-tocasc:active { transform: translateX(-50%) scale(0.92); }

/* the quiet destructive action at the page's very bottom */
.v2-reset {
    display: block; margin: 26px auto 6px auto;
    appearance: none; border: none; background: none;
    font-size: 13px; font-weight: 600; color: var(--ink-2);
    padding: 10px 18px; font-family: inherit;
}
.v2-reset:active { opacity: 0.6; }
/* armed: the second, unmistakable step before a reset executes */
.v2-reset.armed { color: #fff; background: var(--red); border-radius: 999px; }
.v2-reset:disabled { opacity: 0.55; }

/* the course line: which French this is, in its own words */
.v2-course {
    display: flex; align-items: center; gap: 8px;
    margin: 10px auto 0 auto; width: fit-content;
    appearance: none; background: var(--surface);
    border: 0.5px solid var(--hairline); border-radius: 999px;
    font-size: 13px; font-weight: 600; color: var(--ink);
    padding: 9px 16px 9px 18px; font-family: inherit;
    box-shadow: 0 1px 2px rgba(58, 74, 104, 0.06);
}
.v2-course::before {
    content: 'COURSE'; font-size: 10px; letter-spacing: 0.12em;
    color: var(--ink-3); font-weight: 600;
}
.v2-course::after {
    content: ''; width: 7px; height: 7px; flex: none;
    border-right: 1.5px solid var(--ink-2); border-bottom: 1.5px solid var(--ink-2);
    transform: rotate(45deg) translateY(-2px);
}
.v2-course:active { opacity: 0.6; }
.v2-course-row {
    display: block; width: 100%; text-align: left;
    -webkit-tap-highlight-color: rgba(43, 92, 168, 0.08);
    appearance: none; border: none; background: none;
    font-family: inherit; font-size: 17px; color: var(--ink);
    padding: 16px 6px; border-bottom: 0.5px solid var(--hairline);
}
.v2-course-row:last-child { border-bottom: none; }
.v2-course-row.current { color: var(--accent); font-weight: 700; }
.v2-course-row.current::after { content: ' ✓'; }

/* the quiet build stamp: one glance answers "did the update take?" */
.v2-build {
    text-align: center; font-size: 10px; font-weight: 700;
    letter-spacing: 0.12em; text-transform: uppercase;
    color: var(--ink-3); padding: 14px 0 10px 0;
}

/* the rescue: appears in the reset button's place after a reset */
.v2-restore {
    display: block; margin: 26px auto 6px auto;
    appearance: none; border: none; background: none;
    font-size: 13px; font-weight: 600; color: var(--accent);
    padding: 10px 18px; font-family: inherit;
}
.v2-restore:active { opacity: 0.6; }
.v2-restore:disabled { opacity: 0.55; }

/* a server round-trip is rearranging the cascade: the page dims and
   stops taking taps until the answer lands */
/* The path and the cascade are the page's two movements: a
   generous breath and one hairline mark the boundary between them,
   like a section rule in print. */
/* while nothing has been placed, the starting line is the only rule
   between the frontier and the cascade: colour only, so the box keeps
   its height when the class drops on the first Add */
#v2-feeder.v2p-virgin + #v2-cascade { border-top-color: transparent; }

#v2-cascade {
    margin-top: 38px;
    border-top: 0.5px solid var(--hairline);
    padding-top: 22px;
}

.v2-busy #v2-feeder, .v2-busy #v2-cascade {
    pointer-events: none; opacity: 0.55; transition: opacity 0.25s ease;
}

/* pulling a deck back: the cards that would leave burn red where they
   sit, everything else recedes - the verification IS the cascade */
/* The verification state is STILL: the doomed cards hold a steady red
   glow while the learner decides - nothing changes until they commit.
   The transitions on the base chip make both edges of the state soft:
   marking fades the red in, and Not-now fades every card back
   together. (The confirm path animates its own ghosts in v2.js.) */
.v2casc-chip {
    transition: background-color 0.9s ease, box-shadow 0.9s ease,
        opacity 0.9s ease;
}
.v2casc-chip.v2-doomed {
    background: var(--red) !important;
    box-shadow: 0 0 10px 3px color-mix(in srgb, var(--red) 60%, transparent);
}
.v2casc-chip.v2-dimmed { opacity: 0.12; }

/* the decision bar: one message, one go, one keep-as-is */
.v2-confirmbar {
    position: fixed; left: 14px; right: 14px;
    bottom: calc(env(safe-area-inset-bottom, 0px) + 18px);
    z-index: 90;
    background: var(--surface); border-radius: 18px;
    box-shadow: 0 10px 30px rgba(20, 28, 46, 0.22);
    padding: 14px 16px;
    animation: v2-bar-in 0.28s var(--spring);
}
@keyframes v2-bar-in {
    from { transform: translateY(24px); opacity: 0; }
}
.v2-confirmbar .msg {
    font-size: 14px; font-weight: 600; color: var(--ink);
    text-align: center; margin-bottom: 11px;
}
.v2-confirmbar .row { display: flex; gap: 10px; }
.v2-confirmbar button {
    flex: 1; appearance: none; border: none; font-family: inherit;
    font-size: 15px; font-weight: 700; padding: 11px 0;
    border-radius: 999px;
}
.v2-confirmbar .keep { background: var(--surface-2); color: var(--ink-2); }
.v2-confirmbar .go { background: var(--red); color: #fff; }
.v2-confirmbar .go.accent { background: var(--accent); color: var(--surface); }
.v2-confirmbar button:active { transform: scale(0.97); }

/* ---------- review session ---------- */

/* Focus falls where the light is: while a card is under review the
   living page recedes behind a veil of its own background color.
   Fixed at z-index 20 - above the page (1), BELOW the flight chips
   (40) and the card layer (60) - so the card and everything that
   flies stay bright while the rest quiets down. pointer-events:none
   keeps the classic arrangement intact: outside taps still dismiss,
   outside drags still scroll. */
body::after {
    content: '';
    position: fixed; inset: 0; z-index: 20;
    background: color-mix(in srgb, var(--bg) 72%, transparent);
    opacity: 0; pointer-events: none;
    transition: opacity 0.28s ease;
}
.v2-in-session body::after { opacity: 1; }

.v2-layer {
    position: fixed; inset: 0; z-index: 60;
    background: var(--bg);
    display: flex; flex-direction: column;
}

/* author display beats the hidden attribute unless we say otherwise */
.v2-layer[hidden], #v2-toast[hidden] { display: none !important; }

#v2-session {
    padding: calc(env(safe-area-inset-top, 0px) + 10px) 16px
             calc(env(safe-area-inset-bottom, 0px) + 8px) 16px;
    animation: v2-layer-in 0.28s var(--ease-out);
    /* the classic arrangement: the card floats over the living page.
       The layer itself swallows nothing - touches outside the card
       cluster fall through, so the cascade and feeder stay scrollable
       and even tappable mid-review. */
    background: transparent;
    pointer-events: none;
}

#v2-session .v2-studycard,
#v2-session .v2-tapline,
#v2-session .v2-complete {
    pointer-events: auto;
}

@keyframes v2-layer-in {
    from { opacity: 0; transform: translateY(24px); }
    to { opacity: 1; transform: none; }
}

.v2-cardstage {
    flex: 1; position: relative; perspective: 900px;
    display: flex; flex-direction: column; justify-content: flex-end;
    min-height: 0;
}

.v2-studycard {
    position: relative;
    width: 100%; height: min(52vh, 470px); margin: 0 auto;
    background: var(--surface);
    border-radius: 20px;
    /* floating over live content: a real but quiet lift (the veil
       does the isolating), and a hairline so the edge reads in dark */
    box-shadow: 0 12px 32px rgba(15, 23, 42, 0.14), 0 1px 3px rgba(15, 23, 42, 0.08);
    border: 0.5px solid var(--hairline);
    display: flex; flex-direction: column;
    overflow: hidden;
    transition: transform 0.3s var(--spring), opacity 0.3s ease;
}

.v2-studycard.enter { animation: v2-card-in 0.34s var(--spring); }

@keyframes v2-card-in {
    from { opacity: 0; transform: translateY(34px) scale(0.96); }
    to { opacity: 1; transform: none; }
}

/* Gravity, like the cascade: a pass falls deeper; a fail is tossed
   back up toward the start. */
.v2-studycard.exit-pass { transform: translate(12%, 130%) rotate(9deg); opacity: 0; }
/* a miss doesn't hurl the whole card - the card quietly bows out
   while its little square is tossed back up the cascade (v2.js) */
.v2-studycard.exit-fail { transform: scale(0.94); opacity: 0; }

.v2-studycard .zone-prompt {
    flex: 1.1; display: flex; align-items: center; justify-content: center;
    padding: 46px 26px 22px 26px; text-align: center; position: relative;
}

.v2-studycard .zone-answer {
    flex: 1; display: flex; flex-direction: column;
    align-items: center; justify-content: center;
    padding: 22px 26px; text-align: center;
    border-top: 0.5px solid var(--hairline);
    color: var(--ink);
}

.v2-studycard .card-action { top: 14px; }

/* French content speaks in the dictionary voice */
.v2-studycard .zone-prompt p { margin: 0; font-size: 24px; line-height: 1.45; }
.v2-studycard .zone-answer { font-family: Georgia, 'Times New Roman', serif; font-size: 24px; line-height: 1.5; }

/* the prompt to reveal is wayfinding: the structural voice, quiet */
.v2-answer-hidden {
    color: var(--ink-2); font-size: 12px !important;
    font-family: -apple-system, system-ui, sans-serif !important;
    letter-spacing: 0.12em; font-weight: 700;
}

/* The judgment is the card's own bottom edge - an action row in the
   iOS alert idiom: flat colors split by hairlines, one object with the
   card. Hidden until the answer is. */
.v2-judge {
    flex: none; display: flex;
    min-height: 58px;
    border-top: 0.5px solid var(--hairline);
}
/* the row keeps its 58px from the first frame, so the prompt does not
   jump when the answer appears; hidden boxes are not hit-tested, so a
   tap there reaches the card and reveals */
.v2-judge.waiting { visibility: hidden; }

.v2-judge button {
    flex: 1; appearance: none; border: none; background: none;
    font-size: 16px; font-weight: 600;
    font-family: inherit;
    border-radius: 0;
}

.v2-judge button:active { background: var(--surface-2); }
.v2-judge .again { color: var(--red); }
.v2-judge .got { color: var(--green); border-left: 0.5px solid var(--hairline); }

/* While a card is under review, band taps dismiss the session instead
   of starting one - so the pointer cursor and the "tap a bucket" hint
   would be false affordances (same call as the classic UI). */
.v2-in-session .v2casc-band.tappable { cursor: default; }
.v2-in-session .v2casc-hint { visibility: hidden; }
/* the way-back pill has no business floating over a review */
.v2-in-session #v2-tocasc { display: none; }

/* Screenshot harness (?v2shot=...): freeze-frame captures. Headless
   virtual time can freeze transitions at their first frame, so shot
   mode simply has none - every capture is the settled final state. */
.v2-shotmode *, .v2-shotmode *::before, .v2-shotmode *::after {
    transition: none !important;
    animation: none !important;
}

.v2-tapline {
    text-align: center; font-size: 13px; font-weight: 600; color: var(--ink-2);
    margin: 8px auto 0 auto; min-height: 20px;
    padding: 5px 14px; border-radius: 999px; width: fit-content;
    background: color-mix(in srgb, var(--bg) 74%, transparent);
    -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
}
.v2-tapline:empty { visibility: hidden; }

.v2-undo {
    appearance: none; border: none; background: none; color: var(--accent);
    font-size: 14px; font-weight: 700; padding: 6px 10px;
}

/* rung toast: where the card landed. It began as two words ("3
   nights") and stayed on one line by fiat; the sentences the offline
   states need are three times that, and a centred nowrap pill runs off
   BOTH edges of a 390px phone. So it wraps, inside the same margins
   every card on the page keeps, and stays centred while it does. */
#v2-toast {
    position: fixed; left: 18px; right: 18px;
    bottom: calc(env(safe-area-inset-bottom, 0px) + 30px);
    /* left+right, then shrink-to-fit and auto margins: with `left:50%`
       alone the box may only use the right half of the screen, so a
       long sentence stacked into a narrow column instead of using the
       page's own margins. */
    width: -webkit-fit-content; width: fit-content;
    max-width: 100%; margin: 0 auto;
    background: var(--ink); color: var(--bg);
    font-size: 13px; font-weight: 700; line-height: 1.35;
    padding: 9px 16px; border-radius: 18px;
    text-align: center;
    z-index: 80; opacity: 0; transition: opacity 0.25s ease, transform 0.25s var(--spring);
    pointer-events: none;
}

#v2-toast.show { opacity: 0.94; transform: translateY(-6px); }

/* session complete: a card of its own, floating over the cascade the
   session just reshaped */
.v2-complete {
    display: flex; flex-direction: column; margin: auto 0;
    align-items: center; justify-content: center; gap: 10px; text-align: center;
    background: var(--surface);
    border-radius: 24px; border: 0.5px solid var(--hairline);
    box-shadow: 0 18px 50px rgba(15, 23, 42, 0.28), 0 2px 10px rgba(15, 23, 42, 0.12);
    padding: 44px 28px;
    animation: v2-layer-in 0.4s var(--ease-out);
}

.v2-complete .glyph { font-size: 56px; }
.v2-complete .head { font-size: 28px; font-weight: 800; letter-spacing: -0.02em; }
.v2-complete .sub { font-size: 15px; color: var(--ink-2); max-width: 260px; }

/* ---------- sheet (deck previews etc.) ---------- */
#v2-sheet { background: rgba(0,0,0,0.4); justify-content: flex-end; }

.v2-sheet-panel {
    background: var(--surface);
    border-radius: 22px 22px 0 0;
    max-height: 78%;
    display: flex; flex-direction: column;
    padding: 10px 18px calc(env(safe-area-inset-bottom, 0px) + 16px) 18px;
    animation: v2-sheet-in 0.32s var(--spring);
}

@keyframes v2-sheet-in { from { transform: translateY(100%); } to { transform: none; } }

.v2-sheet-grab {
    width: 38px; height: 5px; border-radius: 3px;
    background: var(--ink-3); opacity: 0.5; margin: 2px auto 10px auto;
}

.v2-sheet-title { font-size: 18px; font-weight: 700; margin-bottom: 2px; }
.v2-sheet-sub { font-size: 13px; color: var(--ink-2); margin-bottom: 10px; }
.v2-sheet-scroll { overflow-y: auto; -webkit-overflow-scrolling: touch; }

/* A sheet you TYPE into holds still. The panel is bottom-anchored and
   normally sized by its content, so a result list that grows and
   shrinks with every keystroke drags the field up and down under the
   finger. Fixed height: the field never moves, only the results do. */
.v2-sheet-panel.v2-sheet--fixed { height: 78%; }
/* every part above the results is rigid - flex must not squeeze the
   grab handle (it was losing 5px to a long list, which moved the field
   under the finger); only the results take up the slack */
.v2-sheet-panel.v2-sheet--fixed > * { flex: 0 0 auto; }
.v2-sheet-panel.v2-sheet--fixed .v2-sheet-scroll { flex: 1 1 auto; min-height: 0; }

/* compact preview table inside sheets */
.deck-preview-table { width: 100%; border-collapse: collapse; font-size: 14px; }
.deck-preview-table td { padding: 10px 6px; border-bottom: 1px solid var(--hairline); vertical-align: top; }
.deck-preview-table tr:last-child td { border-bottom: none; }
.deck-preview-prompt { width: 52%; padding-left: 34px; }
.deck-preview-answer { color: var(--ink-2); font-family: Georgia, serif; }
.deck-preview-table .amb-note { font-size: 10px; }
.deck-preview-table .prompt-chips { font-size: 11px; margin-top: 4px; }
.deck-preview-table .audio-btn { font-size: 11px; padding: 2px 9px; }
.deck-preview-table .answer-guide { font-size: 14px; max-width: none; }

/* annotations inherit card-styles.css; keep their ink in theme */
.amb-note, .prompt-chips { color: #64708c; }
@media (prefers-color-scheme: dark) {
    .amb-note, .prompt-chips { color: #8fa0c2; }
    .amb-w-ink { text-decoration-color: #3d4c66; }
    .card-action--translate { background: #16291f; border-color: #2e5340; color: var(--green); }
    .card-action--pronounce { background: #1d2c44; border-color: #33518a; color: var(--accent); }
    .audio-btn { background: #1d2c44; border-color: #33518a; color: var(--accent); }
}

/* ---------- Find a word ----------
   The search sheet is set like the rest of the app: a structural label
   says WHERE (level · group), the deck name is content, the matched
   lines are quiet print with only the found word lit. */
.v2s-field {
    display: flex; align-items: center; gap: 8px;
    margin: 2px 0 12px 0; padding: 9px 12px;
    background: var(--surface-2); border-radius: 12px;
    color: var(--ink-3);
}
.v2s-field svg { width: 17px; height: 17px; flex: 0 0 17px; display: block; }
.v2s-field input {
    flex: 1; min-width: 0; appearance: none; border: none; background: none;
    font-family: inherit; font-size: 16px; color: var(--ink);
    padding: 0; outline: none;
}
.v2s-field input::-webkit-search-cancel-button { display: none; }

.v2s-count {
    font-size: 10px; font-weight: 700; letter-spacing: 0.12em;
    text-transform: uppercase; color: var(--ink-3);
    margin: 0 0 12px 0;
}
.v2s-none { font-size: 14px; color: var(--ink-2); padding: 10px 0 22px 0; }

.v2s-deck {
    padding: 12px 0 14px 0;
    border-top: 0.5px solid var(--hairline);
    cursor: pointer;
}
.v2s-deck:active { opacity: 0.6; }
.v2s-where {
    font-size: 10px; font-weight: 700; letter-spacing: 0.12em;
    text-transform: uppercase; color: var(--ink-3); margin-bottom: 3px;
}
.v2s-deckrow { display: flex; align-items: baseline; gap: 8px; }
.v2s-name { flex: 1; min-width: 0; font-size: 16px; font-weight: 600; }
.v2s-tag {
    font-size: 10px; font-weight: 700; letter-spacing: 0.12em;
    text-transform: uppercase; color: var(--ink-3); white-space: nowrap;
}
.v2s-tag.in { color: var(--green); }

/* the matched lines: English above, French below, hairline between -
   the same reading order as a card */
.v2s-card { margin-top: 9px; padding-left: 11px; border-left: 2px solid var(--hairline); }
.v2s-prompt { font-size: 13px; color: var(--ink-2); }
.v2s-answer { font-size: 15px; color: var(--ink); margin-top: 1px; }
.v2s-hit { background: none; color: var(--accent); font-weight: 700; }
.v2s-more { font-size: 12px; color: var(--ink-3); margin-top: 8px; }

/* arriving from a search: the row is lit once, then lets go */
.v2p-deck.v2s-found {
    animation: v2sfound 2s ease-out;
}
@keyframes v2sfound {
    0%, 55% { background: var(--accent-soft); }
    100% { background: transparent; }
}

/* a newer answer is in flight: the standing results wait rather than
   being wiped - a list that vanishes on every keystroke reads as a jump */
.v2-sheet-scroll.v2s-waiting { opacity: 0.45; transition: opacity 0.15s ease; }

/* ================= the "?" and the guided walk ================= */

/* One fixed control. At the top of the page it reads as the last
   button of the title row (the path's own line makes room for it);
   scrolled, it floats over the passing rows. The tocasc recipe -
   hairline, frost, the same soft lift - at z 72: above the tour's own
   marks (70) and above the status-bar scrim (50), so it stays
   tappable through a review, and below the toast (80). */
#v2-help {
    position: fixed;
    top: calc(env(safe-area-inset-top, 0px) + 10px);
    right: 14px;
    z-index: 72;
    appearance: none; -webkit-appearance: none;
    border: 0.5px solid var(--hairline);
    width: 34px; height: 34px; border-radius: 999px; padding: 0;
    background: color-mix(in srgb, var(--bg) 74%, transparent);
    -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
    color: var(--accent);
    display: flex; align-items: center; justify-content: center;
    box-shadow: 0 4px 14px rgba(15, 23, 42, 0.14);
    transition: transform 0.15s var(--spring);
}
#v2-help[hidden] { display: none; }
#v2-help:active { transform: scale(0.92); }

/* the question mark is the app's own voice asking: Georgia, upright,
   optically centred (the glyph's ink sits low in its box) */
#v2-help .hq {
    font-family: Georgia, 'Times New Roman', serif;
    font-size: 19px; font-weight: 400; line-height: 1;
    margin-top: -1px;
}
#v2-help .hx { display: none; width: 14px; height: 14px; color: var(--ink-2); }
/* a walk is running: the same button closes it */
.v2-tour-on #v2-help .hq { display: none; }
.v2-tour-on #v2-help .hx { display: block; }
/* an empty cascade: the ? asks to be tapped, with the walk's own
   beacon (v2-tour.js beckon(); the motion is .v2tour-ring's) */
#v2-help.v2-help-beckon::after {
    content: ""; position: absolute; inset: -1.5px;
    border-radius: 999px;
    border: 1.5px solid var(--accent); opacity: 0;
    animation: v2tour-beacon 2.4s ease-out infinite;
    pointer-events: none;
}

/* the layer: a sheet of glass over the whole page. It points, it
   never catches - only the note's own pill takes a tap, so a finger
   landing anywhere on the walk can still scroll the page under it.
   Which taps belong to the step is decided in v2-tour.js while a
   walk runs (see "the tap gate"). The veil is a 200vmax shadow on
   the ring, so the layer clips: an unclipped shadow would lengthen
   the page's scroll area. */
#v2-tour {
    position: fixed; inset: 0; z-index: 70;
    pointer-events: none;
    overflow: hidden;
}
#v2-tour[hidden] { display: none; }
/* the target went away (a render, a fold): the marks wait in silence
   rather than pointing at nothing */
#v2-tour.v2tour-quiet > * { visibility: hidden; }

.v2tour-ring {
    position: absolute;
    border: 1.5px solid var(--accent);
    pointer-events: none;
    /* everything outside the ring is veiled, and the ring's own
       shadow IS that outside: one box, no second element to keep in
       step with it. The note is appended after the rings, so it
       paints over the veil. */
    box-shadow: 0 0 0 200vmax var(--v2tour-veil);
}
.v2tour-ring[hidden] { display: none; }

/* the first-use beacons' motion, now general: one ring leaving the
   mark and fading, growing by a fixed 12px a side (never a scale -
   v2-progress.js records the sideways pan a scaled ring caused).
   It rises from nothing as it leaves: a ring that began at full
   strength on the mark's own edge popped on as a second, bolder
   outline every cycle, and the pop was the whole impression. */
.v2tour-ring::after {
    content: ""; position: absolute; inset: -1.5px;
    border-radius: inherit;
    border: 1.5px solid var(--accent); opacity: 0;
    animation: v2tour-beacon 2.4s ease-out infinite;
    pointer-events: none;
}
@keyframes v2tour-beacon {
    0% { inset: -1.5px; opacity: 0; }
    25% { opacity: 0.45; }
    65% { inset: -13.5px; opacity: 0; }
    100% { inset: -13.5px; opacity: 0; }
}
/* a swallowed tap: the ring says the step again by pulsing once from
   the top. An identical second set of keyframes is the restart - the
   animation-name changes, so the animation begins over. */
.v2tour-ring.v2tour-beacon-b::after { animation-name: v2tour-beacon-b; }
@keyframes v2tour-beacon-b {
    0% { inset: -1.5px; opacity: 0; }
    25% { opacity: 0.45; }
    65% { inset: -13.5px; opacity: 0; }
    100% { inset: -13.5px; opacity: 0; }
}

/* the note is the app commenting on the material: Georgia, upright,
   small, on a card of its own */
.v2tour-note {
    position: absolute;
    max-width: min(320px, calc(100vw - 24px));
    background: var(--surface);
    border: 1px solid var(--hairline);
    border-radius: 16px;
    box-shadow: 0 6px 22px rgba(15, 23, 42, 0.16);
    padding: 13px 16px;
    /* the note is scenery, not a control: the gate claims a tap inside
       its box by geometry, and a drag on it still scrolls the list
       underneath */
    pointer-events: none;
}
.v2tour-note .t {
    font-family: Georgia, 'Times New Roman', serif;
    font-size: 17px; line-height: 1.4; color: var(--ink);
    white-space: pre-line;   /* a note's text may break lines on purpose */
}

/* the pointer: the note's own corner turned toward its target */
.v2tour-ptr {
    position: absolute; width: 10px; height: 10px;
    background: var(--surface);
    border: 1px solid var(--hairline);
    transform: rotate(45deg);
}
.v2tour-ptr[hidden] { display: none; }
.v2tour-ptr.up { top: -5px; border-right: none; border-bottom: none; }
.v2tour-ptr.down { bottom: -5px; border-left: none; border-top: none; }

.v2tour-go {
    display: block; margin: 11px 0 0 auto;
    appearance: none; -webkit-appearance: none; border: none;
    font-family: inherit;
    background: var(--accent); color: var(--surface);
    font-size: 14px; font-weight: 600;
    min-height: 34px; padding: 0 16px; border-radius: 999px;
    pointer-events: auto;
    transition: transform 0.15s var(--spring);
}
.v2tour-go:active { transform: scale(0.94); }

@media (prefers-reduced-motion: reduce) {
    .v2tour-ring::after,
    .v2tour-ring.v2tour-beacon-b::after { animation: none; }
    /* still an invitation, held still: one ring a breath outside */
    #v2-help.v2-help-beckon::after { animation: none; inset: -5px; opacity: 0.5; }
}

@media (prefers-color-scheme: dark) {
    .v2tour-note { box-shadow: 0 6px 22px rgba(0, 0, 0, 0.5); }
    #v2-help { box-shadow: 0 4px 14px rgba(0, 0, 0, 0.4); }
}
