/* ═══════════════════════════════════════════════════════════════════
   Listening Bingo — the caller and the card (D83)
   /games/listening-bingo · /chs/games/listening-bingo

   Hero: midnight → indigo → amber (#1e1b4b → #4338ca → #f59e0b). Deliberately
   distinct from Word Search's forest-lime, Cloze's violet, Sentence
   Dictation's cyan, Tone Pair Arcade's rose and Character Builder's indigo —
   every game hero reads as its own place. The amber is the bingo ball.

   ONE STYLESHEET FOR BOTH PAGES. The Chinese-UI twin is the same card with a
   different bank; a second stylesheet is how the two would start drifting.
   Everything colour-neutral uses Bootstrap's own CSS variables so the page
   follows the site's dark mode without a second palette to keep in step.

   EVERY ANIMATION IS DECORATION OVER A CHANGE THE HUD HAS ALREADY REPORTED,
   and all of them are switched off in one block at the foot of this file under
   prefers-reduced-motion — never given a quieter version, which is how an
   animation nobody asked for survives the setting that turned it off.
   ═══════════════════════════════════════════════════════════════════ */

/* ── Hero ────────────────────────────────────────────────────────── */
.lb-hero {
    background: linear-gradient(135deg, #1e1b4b 0%, #4338ca 58%, #f59e0b 100%);
    border-radius: 0.5rem;
}

/* ── HUD ─────────────────────────────────────────────────────────── */
.lb-hud {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem 1rem;
    margin-bottom: 0.75rem;
}

.lb-hud-item {
    font-weight: 700;
    font-size: 1.05rem;
    line-height: 1.2;
    white-space: nowrap;
}

.lb-hud-item small {
    display: block;
    font-weight: 400;
    font-size: 0.7rem;
    color: var(--bs-secondary-color);
}

.lb-combo-badge {
    display: inline-block;
    min-width: 2.2rem;
    padding: 0 0.35rem;
    border-radius: 0.4rem;
    background: #4338ca;
    color: #fff;
}

.lb-lives {
    color: #dc2626;
    letter-spacing: 0.1em;
}

/* The hint is a lightbulb, so it is the one amber control on the page rather than a
   third grey icon beside the flag. #d97706 and not the hero’s brighter #f59e0b: on the
   white button that one measures 2.1:1, under the 3:1 WCAG 1.4.11 asks of a graphic
   carrying its own meaning — and this icon *is* the button’s whole content, the title
   attribute being the only other thing naming it. Dark mode takes the brighter amber,
   where the deeper one is the same problem the other way round. */
#lbHintBtn .fa-lightbulb-o {
    color: #d97706;
}

/* Hover, focus and press fill the outline button, and amber on that grey fill is less
   legible than the white Bootstrap already puts there. Hand those states back. */
#lbHintBtn:hover .fa-lightbulb-o,
#lbHintBtn:focus-visible .fa-lightbulb-o,
#lbHintBtn:active .fa-lightbulb-o {
    color: inherit;
}

[data-bs-theme="dark"] #lbHintBtn .fa-lightbulb-o {
    color: #f59e0b;
}

/* Caller mode has no card, no lives and no score, so the HUD items that measure a
   run are hidden rather than shown reading zero — a zero is a claim, and "♥♥♥ 0/0"
   beside a caller that is not scoring anything is a wrong one.

   🔒 Scoped to the attribute WHEREVER it sits, not to .lb-stage. The HUD is a sibling
   ABOVE the stage, so a `.lb-stage[data-lb-mode]` selector matched nothing and every
   one of these stayed on screen reading zero — which looked completely normal. The
   engine sets the attribute on #lbPanel, which wraps both. */
[data-lb-mode="caller"] .lb-solo-only {
    display: none;
}

/* The mirror image: "Show the word" is the teacher's check against a pupil's card, and
   in solo it would simply be the answer. */
[data-lb-mode="solo"] .lb-caller-only {
    display: none;
}

/* ── The stage ───────────────────────────────────────────────────── */
/* 🔒 The play surface and the overlays are ONE grid cell, so the stage is always as
   tall as whichever of them is taller.

   The overlays used to be `position: absolute; inset: 0`, which pins them to the play
   area's own height and says nothing about the height they need: measured at 1280 the
   start screen wanted 216px inside a 142px stage, and a centred flex column spills the
   difference BOTH ways — so 37px of the megaphone and the heading were drawn straight
   over the score, combo and lives in the HUD above. It is worse at 390, where the three
   pickers wrap. A min-height would only move the number that is wrong; growing the cell
   to fit is what cannot go stale. Keep `position: relative` — #lbToast and #lbEffects
   are still absolute against this box. */
.lb-stage {
    position: relative;
    display: grid;
}

.lb-stage > .lb-play,
.lb-stage > .lb-overlay {
    grid-area: 1 / 1;
    min-width: 0;
}

.lb-effects {
    position: absolute;
    inset: 0;
    overflow: hidden;
    pointer-events: none;
    z-index: 3;
}

.lb-toast {
    position: absolute;
    top: 0.25rem;
    left: 50%;
    transform: translateX(-50%);
    z-index: 4;
    padding: 0.3rem 0.8rem;
    border-radius: 999px;
    font-weight: 700;
    font-size: 0.9rem;
    background: var(--bs-tertiary-bg);
    color: var(--bs-body-color);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s ease;
}

.lb-toast-show {
    opacity: 1;
}

.lb-toast-good {
    background: #15803d;
    color: #fff;
}

.lb-toast-bad {
    background: #b91c1c;
    color: #fff;
}

/* ── The caller ──────────────────────────────────────────────────── */
.lb-caller {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.75rem;
    padding: 0.75rem;
    margin-bottom: 0.75rem;
    border-radius: 0.6rem;
    background: var(--bs-tertiary-bg);
}

.lb-ball {
    flex: 0 0 auto;
    display: grid;
    place-items: center;
    width: 3.4rem;
    height: 3.4rem;
    border-radius: 50%;
    font-weight: 800;
    font-size: 1.3rem;
    color: #1e1b4b;
    background: radial-gradient(circle at 32% 30%, #fde68a 0%, #f59e0b 65%, #b45309 100%);
    box-shadow: inset 0 -0.2rem 0.4rem rgba(0, 0, 0, 0.25);
}

.lb-ball-roll {
    animation: lb-roll 0.55s ease-out;
}

@keyframes lb-roll {
    0% { transform: translateX(-2.5rem) rotate(-220deg) scale(0.6); opacity: 0; }
    65% { transform: translateX(0.25rem) rotate(10deg) scale(1.08); opacity: 1; }
    100% { transform: none; opacity: 1; }
}

.lb-call-display {
    flex: 1 1 10rem;
    min-height: 3.4rem;
    display: flex;
    flex-direction: column;
    justify-content: center;
    line-height: 1.25;
}

.lb-call-w {
    font-size: 1.6rem;
    font-weight: 700;
}

.lb-call-s {
    font-size: 0.95rem;
    color: var(--bs-secondary-color);
}

.lb-call-e {
    font-size: 0.9rem;
    color: var(--bs-secondary-color);
}

.lb-call-buttons {
    flex: 0 0 auto;
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
}

/* ── The card ────────────────────────────────────────────────────── */
.lb-board {
    display: grid;
    gap: 0.4rem;
    margin: 0 auto;
    max-width: 30rem;
}

.lb-board-hidden {
    display: none;
}

.lb-cell {
    aspect-ratio: 1 / 1;
    display: grid;
    place-items: center;
    padding: 0.2rem;
    border: 2px solid var(--bs-border-color);
    border-radius: 0.45rem;
    background: var(--bs-body-bg);
    color: var(--bs-body-color);
    font-weight: 600;
    line-height: 1.15;
    text-align: center;
    overflow: hidden;
    transition: transform 0.12s ease, background-color 0.18s ease, border-color 0.18s ease;
}

.lb-cell-face {
    display: block;
    width: 100%;
    font-size: clamp(0.62rem, 2.6vw, 1.15rem);
    word-break: break-word;
    hyphens: auto;
}

.lb-board-3 .lb-cell-face { font-size: clamp(0.9rem, 4.4vw, 1.7rem); }
.lb-board-4 .lb-cell-face { font-size: clamp(0.75rem, 3.3vw, 1.35rem); }

.lb-cell:hover:not(:disabled) {
    border-color: #4338ca;
    transform: translateY(-2px);
}

.lb-cell:focus-visible {
    outline: 3px solid #4338ca;
    outline-offset: 2px;
}

.lb-cell-free {
    background: var(--bs-tertiary-bg);
    color: var(--bs-secondary-color);
    font-size: 0.7rem;
    letter-spacing: 0.08em;
}

.lb-cell-marked {
    background: #4338ca;
    border-color: #312e81;
    color: #fff;
}

/* The daub. A stamp rather than a tick, because a bingo card is marked, not ticked. */
.lb-cell-stamp {
    animation: lb-stamp 0.42s cubic-bezier(0.2, 1.6, 0.4, 1);
}

@keyframes lb-stamp {
    0% { transform: scale(1.5) rotate(-8deg); }
    60% { transform: scale(0.92) rotate(3deg); }
    100% { transform: none; }
}

/* The square that should have been marked, shown after a wrong answer. Without this a
   player learns nothing from the mistake — they are told they were wrong and never which
   one was right. */
.lb-cell-reveal {
    animation: lb-reveal 0.5s ease-in-out 3;
    border-color: #b91c1c;
}

@keyframes lb-reveal {
    0%, 100% { background: var(--bs-body-bg); }
    50% { background: #fecaca; color: #7f1d1d; }
}

.lb-cell-line {
    box-shadow: 0 0 0 3px #f59e0b inset;
}

.lb-board-shake {
    animation: lb-shake 0.4s ease-in-out;
}

@keyframes lb-shake {
    0%, 100% { transform: none; }
    25% { transform: translateX(-6px); }
    75% { transform: translateX(6px); }
}

/* ── Effects ─────────────────────────────────────────────────────── */
.lb-pop {
    position: absolute;
    transform: translate(-50%, 0);
    font-weight: 800;
    font-size: 1.05rem;
    color: #15803d;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
    animation: lb-float 1.1s ease-out forwards;
}

.lb-pop-good { color: #15803d; }

@keyframes lb-float {
    0% { opacity: 0; transform: translate(-50%, 0.4rem) scale(0.8); }
    25% { opacity: 1; transform: translate(-50%, -0.4rem) scale(1.1); }
    100% { opacity: 0; transform: translate(-50%, -3rem) scale(1); }
}

.lb-confetti {
    position: absolute;
    top: -1rem;
    width: 0.5rem;
    height: 0.85rem;
    border-radius: 1px;
    animation: lb-fall 1.6s linear forwards;
}

.lb-confetti-0 { background: #f59e0b; }
.lb-confetti-1 { background: #4338ca; }
.lb-confetti-2 { background: #15803d; }
.lb-confetti-3 { background: #db2777; }
.lb-confetti-4 { background: #0ea5e9; }

@keyframes lb-fall {
    0% { opacity: 0; transform: translateY(0) rotate(0deg); }
    10% { opacity: 1; }
    100% { opacity: 0; transform: translateY(22rem) rotate(540deg); }
}

/* ── Called list ─────────────────────────────────────────────────── */
.lb-called {
    list-style: none;
    margin: 0;
    padding: 0;
    max-height: 13rem;
    overflow-y: auto;
    font-size: 0.85rem;
}

.lb-called-item {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    padding: 0.2rem 0.1rem;
    border-bottom: 1px solid var(--bs-border-color);
}

.lb-called-no {
    flex: 0 0 1.6rem;
    font-weight: 700;
    color: var(--bs-secondary-color);
}

.lb-called-w {
    font-weight: 700;
}

.lb-called-s,
.lb-called-e {
    color: var(--bs-secondary-color);
}

/* ── Overlays ────────────────────────────────────────────────────── */
/* Stacked on the play surface by the grid above, never pinned over it — see .lb-stage.
   `relative` is here only so z-index applies; the overlay is an ordinary grid item. */
.lb-overlay {
    position: relative;
    z-index: 5;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.6rem;
    padding: 1rem;
    text-align: center;
    background: var(--bs-body-bg);
    border-radius: 0.6rem;
}

.lb-big-icon {
    font-size: 2.4rem;
    color: #4338ca;
}

.lb-final-score {
    font-size: 2.6rem;
    font-weight: 800;
    line-height: 1;
    color: #4338ca;
}

.lb-final-stats {
    display: flex;
    flex-wrap: wrap;
    gap: 0.25rem 1rem;
    justify-content: center;
    font-size: 0.85rem;
    color: var(--bs-secondary-color);
}

.lb-picker {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 0.35rem;
}

.lb-picker-label {
    font-size: 0.75rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--bs-secondary-color);
}

.lb-pick-btn {
    padding: 0.25rem 0.7rem;
    border: 1px solid var(--bs-border-color);
    border-radius: 999px;
    background: var(--bs-body-bg);
    color: var(--bs-body-color);
    font-size: 0.85rem;
}

.lb-pick-btn.active {
    background: #4338ca;
    border-color: #312e81;
    color: #fff;
}

.lb-btn-primary {
    background: #4338ca;
    border-color: #312e81;
    color: #fff;
}

.lb-btn-primary:hover,
.lb-btn-primary:focus {
    background: #312e81;
    color: #fff;
}

/* ── The worked example, which is the half that works with JavaScript off ── */
.lb-example-grid {
    display: grid;
    gap: 0.3rem;
}

.lb-example-cell {
    aspect-ratio: 1 / 1;
    display: grid;
    place-items: center;
    padding: 0.15rem;
    border: 2px solid var(--bs-border-color);
    border-radius: 0.35rem;
    font-size: clamp(0.7rem, 3vw, 1.1rem);
    font-weight: 600;
    text-align: center;
}

.lb-example-cell.hit {
    background: #4338ca;
    border-color: #312e81;
    color: #fff;
}

.lb-example-cell.free {
    background: var(--bs-tertiary-bg);
    color: var(--bs-secondary-color);
    font-size: 0.65rem;
}

.lb-visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* ── Reduced motion ──────────────────────────────────────────────────
   Everything above that moves is decoration over a change the HUD and the
   aria-live region have already reported, so all of it is simply off here.
   The colour states (marked, revealed, line) stay — they are information. */
@media (prefers-reduced-motion: reduce) {
    .lb-ball-roll,
    .lb-cell-stamp,
    .lb-cell-reveal,
    .lb-board-shake,
    .lb-pop,
    .lb-confetti {
        animation: none;
    }

    .lb-cell {
        transition: none;
    }

    .lb-cell:hover:not(:disabled) {
        transform: none;
    }
}
