	        :root { --primary: #4f46e5; --success: #15803d; --error: #ef4444; }

        html, body { height: 100%; margin: 0; overscroll-behavior: none; }
        /* Stops double-tap zoom on iPad (children double-tap buttons a lot)
           while still allowing pinch-zoom for students who need it. */
        html { touch-action: manipulation; }
        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            background: #f1f5f9;
            display: flex; justify-content: center;
            padding: 20px 0 40px;
            box-sizing: border-box;
            min-height: 100%;
        }
        .card {
            background: white; padding: 1.8rem; border-radius: 20px;
            box-shadow: 0 15px 35px rgba(0,0,0,0.1);
            width: min(550px, 96vw); text-align: center;
            align-self: flex-start;
            position: relative;
        }

        /* Change Set button: fixed to top-right of viewport, outside the card */
        #changeSetBtn {
            position: fixed; top: 16px; right: 16px;
            background: #cbd5e1; color: #475569;
            border: none; border-radius: 8px;
            padding: 10px 16px; font-size: 0.95rem; font-weight: 700;
            min-height: 44px; min-width: 44px;
            cursor: pointer; z-index: 100;
            box-shadow: 0 2px 8px rgba(0,0,0,0.12);
            display: none; /* hidden until a quiz set is active */
            flex: none; /* exclude from body's flex sizing — see #nameModal note below */
        }
        #changeSetBtn:active { background: #94a3b8; }
        #changeSetBtn:not(.hidden) { display: block; }

        /* Leave-set confirmation modal: hidden by default, shown only when
           the .hidden class is removed (mirrors #nextBtn's pattern below).
           Inline styles on the element only cover layout/colour, never
           display, so the .hidden class always has the final say on visibility.
           flex: none is required here — without it, this being a second
           visible flex-item sibling of body (alongside whichever screen is
           showing underneath) causes the body's row-flex layout to squeeze
           both elements' widths instead of treating this fixed-position
           overlay as independent of normal layout, as position:fixed should. */
        #leaveConfirm {
            position: fixed; inset: 0;
            background: rgba(15,23,42,0.55);
            align-items: center; justify-content: center;
            z-index: 1000; padding: 16px;
            display: none;
            flex: none;
        }
        #leaveConfirm:not(.hidden) { display: flex; }

        /* Logout confirmation modal: same fixed-overlay pattern as #leaveConfirm. */
        #logoutConfirm {
            position: fixed; inset: 0;
            background: rgba(15,23,42,0.55);
            align-items: center; justify-content: center;
            z-index: 1000; padding: 16px;
            display: none;
            flex: none;
        }
        #logoutConfirm:not(.hidden) { display: flex; }

        /* "Something went wrong" panel (spelling.html's showAppError()): same
           overlay pattern, above everything else, since it's the way out of
           a screen that may itself be stuck. */
        #appError {
            position: fixed; inset: 0;
            background: rgba(15,23,42,0.6);
            align-items: center; justify-content: center;
            z-index: 2000; padding: 16px;
            display: none;
            flex: none;
        }
        #appError:not(.hidden) { display: flex; }

        /* Home screen's Log Out control: a small, de-emphasized link tucked
           into the card's corner (relies on .card's own position:relative)
           instead of a full-width button sitting directly under Start
           Practice, where it was too easy to tap by accident. */
        /* Still de-emphasised, but #64748b (~4.8:1 on white) rather than
           #94a3b8 (~2.6:1, under WCAG 1.4.3's 4.5:1), and a 44px tap
           target (Apple's minimum) in place of the old ~30px one. */
        .corner-link {
            position: absolute; top: 8px; right: 8px;
            background: none; border: none;
            color: #64748b; font-size: 0.9rem; font-weight: 600;
            padding: 10px 12px; min-height: 44px; min-width: 44px;
            cursor: pointer; border-radius: 8px;
        }
        .corner-link:focus-visible { color: #475569; background: #f1f5f9; }
        /* Touch screens keep :hover "stuck" after a tap, so only real pointers get it. */
        @media (hover: hover) {
            .corner-link:hover { color: #475569; background: #f1f5f9; }
        }

        /* Keeps the centred heading clear of the corner Log Out link on
           narrow screens, where the heading's own text-align:center box
           can otherwise span wide enough to sit under it. */
        #setSelector h2 { padding: 0 64px; }

        /* Name-entry modal: same fixed-overlay pattern as #leaveConfirm.
           Shown the first time a student reaches the results screen with
           CONFIG.collectStudentName enabled and no name saved yet — not at
           app launch. Also reused for changing/adding a name later.
           flex: none is required — see the note on #leaveConfirm above for
           why; this modal showing alongside #reviewArea (both visible flex
           children of body at once) is exactly the case that was squeezing
           the results card down to a sliver and leaving this modal off-centre
           instead of properly covering the full viewport. */
        #nameModal {
            position: fixed; inset: 0;
            background: rgba(15,23,42,0.55);
            align-items: center; justify-content: center;
            z-index: 1000; padding: 16px;
            display: none;
            flex: none;
        }
        #nameModal:not(.hidden) { display: flex; }
        #nameInput {
            width: 100%; padding: 12px; font-size: 1.1rem; border: 2px solid #e2e8f0;
            border-radius: 10px; text-align: center; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
            outline: none; margin: 4px 0 16px; box-sizing: border-box;
        }
        #nameInput:focus { border-color: var(--primary); box-shadow: 0 0 0 3px rgba(79,70,229,0.18); }

        /* Login / home screens */
        #setSelector h2, #loginArea h2 { margin: 0 0 1.2rem; font-size: 1.3rem; color: #1e293b; }
        .form-message { font-size: 0.85rem; margin: -6px 0 12px; min-height: 1.2em; }
        .form-message.error { color: #991b1b; }

        /* Progress */
        .progress-text { font-size: 0.95rem; font-weight: 700; color: #475569; text-align: right; margin-bottom: 4px; min-height: 1.2em; }
        .progress-bg { width: 100%; height: 12px; background: #e2e8f0; border-radius: 6px; margin-bottom: 1.4rem; overflow: hidden; }
        #progressBar { height: 100%; width: 0%; background: var(--primary); transition: width 0.3s; }

        .score-summary { background: #f8fafc; padding: 15px; border-radius: 12px; margin-bottom: 20px; border: 1px solid #e2e8f0; }
        .score-val { font-size: 2rem; font-weight: 800; color: var(--primary); }

        input {
            width: 90%; padding: 12px; font-size: 1.5rem; border: 2px solid #e2e8f0;
            border-radius: 10px; text-align: center; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; outline: none; margin-bottom: 16px;
            -webkit-appearance: none;
        }

        input:focus {
            border-color: var(--primary);
            box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.18);
        }
        .stage {
            display: flex; justify-content: center; align-items: center;
            flex-wrap: nowrap; white-space: nowrap;
            height: 90px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; font-size: 2rem; font-weight: bold;
            background: white; border-radius: 8px; position: relative; overflow: hidden;
        }

        .letter { display: inline-flex; justify-content: center; align-items: center; text-align: center; position: relative; padding: 0 3px; box-sizing: border-box; }
        /* All width/opacity transitions on the wrapper */
        .letter { transition: width 0.35s cubic-bezier(0.4,0,0.2,1), min-width 0.35s cubic-bezier(0.4,0,0.2,1), opacity 0.35s; overflow: hidden; }
        .letter.correct { color: #64748b; }
        .letter.highlight { color: var(--error); transform: scale(1.2); }
        /* Deleted letters collapse away */
        .letter.removing { opacity: 0; width: 0 !important; min-width: 0 !important; padding: 0 !important; transition: width 0.55s cubic-bezier(0.4,0,0.2,1), min-width 0.55s cubic-bezier(0.4,0,0.2,1), opacity 0.4s, padding 0.55s !important; }
        /* Inserted letters: wrapper starts at zero width and expands;
           inner text starts above and drops down */
        .letter.ins-wrapper .ins-inner {
            display: block;
            transform: translateY(-130%);
            opacity: 0;
            transition: transform 0.35s cubic-bezier(0.4,0,0.2,1) 0.3s, opacity 0.3s 0.3s;
        }
        .letter.ins-wrapper.dropping .ins-inner {
            transform: translateY(0);
            opacity: 1;
        }

        /* Definition box */
        #definitionBox {
            margin: 12px 0 0; padding: 12px 16px;
            background: #f0f4ff; border-left: 3px solid var(--primary);
            border-radius: 8px; font-size: 1rem; color: #334155;
            text-align: left; line-height: 1.6; cursor: pointer;
            user-select: none; -webkit-user-select: none;
            min-height: 2.6em;
        }
        /* #94a3b8 (the original colour) measured ~2.4:1 against this box's
           #f0f4ff background — well under WCAG 1.4.3's 4.5:1 minimum for
           normal-size text. #475569 measures ~6.9:1 here. */
        #definitionBox .def-hint { display: block; font-size: 0.85rem; color: #475569; margin-bottom: 4px; font-style: italic; }
        #definitionBox .def-word { display: inline; transition: background 0.2s, color 0.2s; border-radius: 3px; }
        #definitionBox .def-word.speaking { background: var(--primary); color: white; padding: 0 2px; border-radius: 3px; }

        /* #94a3b8 measured ~2.6:1 on the white card background — see the
           .def-hint comment above for the same fix, same reasoning. */
        #setLabel { font-size: 0.82rem; color: #475569; margin-bottom: 0.4rem; }
        /* Who's practising (#quizWho): the student's animal and name, left of
           the fixed Home button's corner. */
        .who-chip { display: flex; align-items: center; gap: 8px; margin: 0 0 0.5rem; max-width: calc(100% - 96px);
            font-size: 0.95rem; font-weight: 700; color: #334155; }
        .who-chip #quizName { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
        .who-chip:has(#quizName:empty) { display: none; }
        .avatar-mini { flex: none; width: 32px; height: 32px; border-radius: 50%; font-size: 20px; line-height: 32px; text-align: center; }
        .avatar-mini:empty { display: none; }

        .btn-group { margin-top: 18px; display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; }
        button { padding: 12px 24px; border-radius: 8px; border: none; cursor: pointer; font-weight: bold; font-size: 1rem; -webkit-tap-highlight-color: transparent; }
        /* Hear Word matters most to a child who can't read yet: a bright,
           friendly colour of its own, not a grey that looks off. White text
           is 5.7:1 on #7c3aed and 6.3:1 on #a21caf, the gradient's two ends.
           The darker bottom edge makes it look (and feel) pressable — also
           on Hear the Spelling, the other sound button. */
        .btn-audio {
            background: linear-gradient(135deg, #7c3aed, #a21caf);
            color: #fff;
            border-radius: 14px;
            box-shadow: 0 4px 0 #581c87;
            text-shadow: 0 1px 1px rgba(0, 0, 0, 0.25);
        }
        .btn-audio:active:not(:disabled) { transform: translateY(3px); box-shadow: 0 1px 0 #581c87; }
        .btn-audio:focus-visible { outline: 3px solid #581c87; outline-offset: 3px; }
        /* The two buttons every word needs: big, full width, easy to hit. */
        .btn-big { width: 100%; min-height: 56px; font-size: 1.15rem; }
        .btn-check { background: var(--primary); color: white; }
        #nextBtn:not(.hidden) { display: block; margin: 10px auto 0; padding: 10px 28px; }
        /* Blue like Check, not red: after a mistake, red reads to a child
           as "wrong again". */
        .btn-next { background: var(--primary); color: white; min-height: 52px; font-size: 1.1rem; }
        #nextBtn:focus {
            outline: none;
            box-shadow: 0 0 0 3px white, 0 0 0 5px var(--primary);
            transform: scale(1.06);
        }
        /* A gold trophy button. White text on gold fails contrast (~1.7-2.2:1),
           so the text is dark brown instead: #451a03 is 7.0:1 on #f59e0b, the
           gradient's darker end (11:1 on #fde047). The amber border and edge
           (5:1 on white) outline it against the white card. */
        .btn-certificate {
            background: linear-gradient(180deg, #fde047, #f59e0b);
            color: #451a03;
            border: 2px solid #b45309;
            border-radius: 14px;
            box-shadow: 0 4px 0 #b45309, inset 0 2px 0 rgba(255, 255, 255, 0.6);
            min-height: 56px;
            font-size: 1.15rem;
        }
        .btn-certificate:active:not(:disabled) { transform: translateY(3px); box-shadow: 0 1px 0 #b45309, inset 0 2px 0 rgba(255, 255, 255, 0.6); }
        .btn-certificate:focus-visible { outline: 3px solid #78350f; outline-offset: 3px; }
        .btn-back { background: #e2e8f0; color: #475569; }
        /* A button waiting on the server looks it (see setBusy()). */
        button:disabled { opacity: 0.6; cursor: progress; }

        /* "Loading…" / "Checking…" while waiting on the server. */
        .loading-note { color: #475569; font-size: 1.05rem; font-weight: 600; padding: 12px 0; }

        /* Each student's picture and colour on the home screen. */
        .avatar { width: 88px; height: 88px; margin: 4px auto 10px; border-radius: 50%; font-size: 52px; line-height: 88px; }
        .avatar:empty { display: none; }

        /* Tap to Start: big and unmissable, above the quiz. */
        .tap-to-start:not(.hidden) { display: block; width: 100%; min-height: 64px; font-size: 1.4rem; margin-bottom: 14px; }

        /* Confirm dialogs: stay (bright, full width) on top, leave (plain)
           well below it, so a mis-tap is the safe choice. */
        .confirm-buttons { display: flex; flex-direction: column; gap: 28px; }
        .confirm-buttons button { width: 100%; min-height: 52px; font-size: 1.1rem; }
        .confirm-buttons .btn-back { min-height: 44px; font-size: 1rem; }

        /* The big tick beside a correctly spelled word. */
        .correct-tick { color: var(--success); font-size: 1.6em; font-weight: 800; margin-left: 10px; align-self: center; }

        /* "Hear the Spelling" only while Next is showing (after a correction). */
        #hearSpellingBtn { display: block; margin: 10px auto 0; }
        #nextBtn.hidden ~ #hearSpellingBtn { display: none; }

        /* #setSelector/#gameArea/#reviewArea are focused programmatically
           on screen change (tabindex="-1") purely so screen readers
           announce the new region — never via keyboard Tab, since a
           tabindex="-1" element is never part of the normal Tab order. A
           sighted keyboard user can therefore never land here by tabbing,
           so the browser's default focus outline serves no purpose except
           to draw a distracting box around the whole card on every screen
           change (and, on the home screen, visually collide with the
           corner Log Out link). Safe to suppress outright, unlike removing
           it from an actually-tabbable control. */
        #setSelector:focus, #gameArea:focus, #reviewArea:focus { outline: none; }

        .review-list { text-align: left; margin-top: 10px; max-height: 300px; overflow-y: auto; }
        .review-item { display: flex; justify-content: space-between; align-items: center; padding: 12px; border-bottom: 1px solid #eee; }
        .review-item-word { flex: 1; }
        .review-item-badges { display: flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }
        .status-badge { padding: 5px 10px; border-radius: 6px; font-size: 0.85rem; font-weight: 800; }
        .perfect { background: #dcfce7; color: #166534; }
        .fixed { background: #fee2e2; color: #991b1b; }
        .mastered {
            /* Darkened from the original #f59e0b/#fbbf24 gradient — white
               text on that lighter amber measured ~1.7-2.2:1 contrast,
               well under WCAG 1.4.3's 4.5:1 minimum for this text size.
               This pairing measures ~4.9:1 (lighter end) to ~7.1:1
               (darker end). */
            background: linear-gradient(135deg, #92400e, #b45309);
            color: #fff;
            text-shadow: 0 1px 2px rgba(0,0,0,0.25);
            box-shadow: 0 2px 6px rgba(146,64,14,0.45);
        }
        /* Leitner outcome badges (rules 7/8: no shaming on a demotion) */
        .improving { background: #dcfce7; color: #166534; }
        .practicing { background: #e2e8f0; color: #475569; }
        .hidden { display: none; }
        /* Referenced by the <h1> and #stageAnnouncement but was never
           actually defined — pre-existing gap, not introduced by the
           Leitner rework; fixed here since it was directly visible during
           this change's own QA pass (the live-region text was rendering
           on-screen instead of staying screen-reader-only). */
        .visually-hidden {
            position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
            overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
        }

        /* Reduce Motion (iPad: Settings > Accessibility > Motion): the
           correction still happens letter by letter, since that's the
           lesson, but letters change in place instead of sliding, dropping
           or growing. !important because the animation code sets some of
           these as inline styles. transitionEnd() in spelling.html skips
           its waits to match. The fireworks are skipped entirely. */
        @media (prefers-reduced-motion: reduce) {
            .letter, .letter.removing, .letter .ins-inner, #progressBar {
                transition: none !important;
            }
            .letter { transform: none !important; }
            #nextBtn:focus { transform: none; }
        }

        /* Fireworks overlay */
        #celebrationCanvas {
            display: none;
            position: fixed; inset: 0; width: 100%; height: 100%;
            pointer-events: none; z-index: 9999;
            flex: none; /* exclude from body's flex sizing — see #nameModal note above */
        }
        /* Multi-round per-word tally badges */
        .tally-correct { background: #dcfce7; color: #166534; }
        .tally-wrong   { background: #fee2e2; color: #991b1b; }
        /* Subtle time display in summary */
        /* Same #94a3b8 contrast fix as #setLabel/.def-hint above. */
        #timeTaken { font-size: 0.85rem; color: #475569; margin-top: 6px; }

        /* ── Landscape + on-screen keyboard compact layout ───────────────────────
           Applied by JS when visualViewport shrinks in landscape orientation.
           Switches the quiz area to a two-column layout so everything stays
           visible above the keyboard without scrolling.                        */
        body.kbd-landscape {
            padding: 8px 0 12px;
            align-items: flex-start;
        }
        body.kbd-landscape .card {
            padding: 0.8rem 1rem;
            width: min(700px, 99vw);
            border-radius: 14px;
        }
        body.kbd-landscape #setLabel,
        body.kbd-landscape .progress-bg { margin-bottom: 0.5rem; height: 8px; }
        body.kbd-landscape .progress-text { font-size: 0.8rem; margin-bottom: 2px; }
        body.kbd-landscape .who-chip { margin-bottom: 0.3rem; font-size: 0.85rem; }

        /* Two-column grid: left = hear + input + buttons, right = stage + def */
        body.kbd-landscape #gameLayout {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 10px;
            align-items: start;
        }
        body.kbd-landscape #colLeft  { display: flex; flex-direction: column; gap: 8px; }
        body.kbd-landscape #colRight { display: flex; flex-direction: column; gap: 8px; }

        /* Shrink elements in compact mode */
        body.kbd-landscape .btn-audio { padding: 8px 14px; font-size: 0.88rem; }
        body.kbd-landscape input { font-size: 1.15rem; padding: 8px; margin-bottom: 0; }
        body.kbd-landscape .stage { height: 60px; font-size: 1.5rem; }
        body.kbd-landscape #definitionBox { font-size: 0.82rem; padding: 8px 10px; min-height: 0; margin-top: 0; }
        body.kbd-landscape #definitionBox .def-hint { display: none; }
        body.kbd-landscape .btn-group { margin-top: 6px; gap: 8px; }
        /* Compact, but never under a 44px target, even with the keyboard up. */
        body.kbd-landscape button { padding: 9px 16px; font-size: 0.95rem; min-height: 44px; }