/* ============================================================
   JetRoute — style.css
   Single source of truth. No duplicate rules.
   Last cleaned: June 2026
   ============================================================ */

/* ============================================================
   0. Design Tokens
   ============================================================ */
:root {
    /* Fonts — loaded via Google Fonts in fonts.php */
    --font-stack:   'Noto Sans', 'Noto Sans SC', 'Noto Sans JP', 'Noto Sans KR',
                    'Noto Sans Arabic', 'Noto Sans Thai', Arial, sans-serif;

    /* Colours */
    --blue:         #007bff;
    --blue-dark:    #0056b3;
    --navy:         #2c3e50;
    --green:        #28a745;
    --red:          #dc3545;
    --border:       #ddd;
    --bg:           #f4f4f4;
    --white:        #fff;
    --grey-text:    #666;
    --grey-light:   #e9ecef;

    /* ── Flashcard dimensions ──────────────────────────────────
       Change these two values to resize ALL flashcard images
       across browse, learn-browse, and quiz simultaneously.
       Set to a true 4:3 ratio (90vw x 67.5vw, 600px x 450px max)
       to match the actual 640x480 image export size.
       ------------------------------------------------------- */
    --card-w:       90vw;
    --card-h:       67.5vw;
    --card-max-w:   600px;
    --card-max-h:   450px;
    --card-radius:  10px;

    /* Gap between an image and its container's border. Source of truth
       is .flip-card-front (browse mode) — every other image container
       (#learn-image-wrap, .picture-book-page) now reads this same
       variable instead of its own hardcoded value, so changing the
       padding anywhere only requires changing it here. */
    --card-img-padding: 10px;

    /* Icon button (circular nav buttons) */
    --icon-btn-size: 52px;
}

/* ============================================================
   1. Global Reset
   ============================================================ */
* { box-sizing: border-box; }

/* ============================================================
   2. Global Body
   ============================================================ */
body {
    font-family: var(--font-stack);
    background-color: var(--bg);
    margin: 0;
    padding: 20px;
    width: 100%;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
}

/* ============================================================
   2b. Global Navigation Header — header_nav.php
   This was proof-of-concept on mode_selection.php only when this
   comment was originally written; it has since been rolled out site-
   wide (confirmed directly against header_nav.php's own file comment,
   which lists mode_selection.php, category_selection.php, flashcard.php,
   lesson.php, lesson_list.php, lesson_learn.php, lesson_type_d.php,
   lesson_type_e.php, picture_book_list.php, picture_book_view.php, and
   picture_book_quiz.php as already wired to it — not re-verified page by
   page here, but the note below about body.nav-page's padding was never
   revisited and may still be stale; check it directly before assuming
   either way if it comes up again).
   ============================================================ */
.jr-header {
    width: 100%;
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 20px;
    padding: 14px 24px;
    background: var(--white);
    box-shadow: 0 2px 8px rgba(0,0,0,0.08);
    box-sizing: border-box;
}

.jr-header-logo {
    font-size: 1.3rem;
    font-weight: 700;
    color: var(--navy);
    text-decoration: none;
    white-space: nowrap;
    justify-self: start;
}
.jr-header-logo:hover { color: var(--blue); }

.jr-header-lang {
    display: flex;
    align-items: center;
    justify-content: center;
    justify-self: center;
}

.jr-header-account {
    font-size: 0.9em;
    color: var(--grey-text);
    white-space: nowrap;
    display: flex;
    flex-direction: column;
    align-items: end;
    gap: 6px;
    justify-self: end;
}
.jr-header-account-row {
    display: flex;
    align-items: center;
    gap: 8px;
}
.jr-header-account a { color: var(--blue); text-decoration: none; }
.jr-header-account a:hover { text-decoration: underline; }

/* TTS auto-play toggle (July 2026) — small pill, same visual weight as
   the login/logout link above it, not a full icon-btn circle (would
   read as too heavy for a secondary, easily-missable setting). Muted
   gray by default (off — auto-play is opt-in, see tts.js's
   isTTSAutoplayEnabled() comment for why); switches to blue/filled when
   active, matching .icon-btn's own existing hover-to-blue color
   language rather than inventing a new on/off palette. */
.jr-tts-toggle {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 4px 10px;
    border-radius: 20px;
    border: 1px solid var(--border);
    background: var(--white);
    color: var(--grey-text);
    font-size: 0.8rem;
    font-family: var(--font-stack);
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s, color 0.15s;
}
.jr-tts-toggle:hover { border-color: var(--blue); color: var(--blue); }
.jr-tts-toggle-active {
    background: var(--blue);
    border-color: var(--blue);
    color: var(--white);
}
.jr-tts-toggle-active:hover { background: var(--blue-dark); border-color: var(--blue-dark); color: var(--white); }

@media (max-width: 600px) {
    .jr-header { grid-template-columns: 1fr; justify-items: center; padding: 10px 16px; }
    .jr-header-logo, .jr-header-account { justify-self: center; }
    .jr-header-account { align-items: center; }
    .jr-header-lang { order: 3; width: 100%; margin-top: 6px; }
}

/* ============================================================
   3. Navigation Pages — mode_selection.php, category_selection.php
   ============================================================ */
body.nav-page { padding-top: 50px; text-align: center; }

@media (min-width: 768px) {
    body.nav-page { padding: 50px 20px; }
}

/* ============================================================
   4. User Corner
   ============================================================ */
.user-corner {
    position: absolute;
    top: 20px;
    right: 20px;
    font-size: 0.9em;
    color: var(--grey-text);
    z-index: 1000;
}
.user-corner a { color: var(--blue); text-decoration: none; }
.user-corner a:hover { text-decoration: underline; }

/* ============================================================
   5. Language Selector — custom flag dropdown
   ============================================================ */
.lang-wrapper {
    margin: 20px auto 30px;
    display: flex;
    flex-direction: column;
    align-items: center;
}

/* Hidden native select — kept as JS source of truth */
.jr-lang-hidden {
    position: absolute;
    opacity: 0;
    pointer-events: none;
    width: 0;
    height: 0;
}

/* Outer wrapper — positions the open list */
.jr-lang-dropdown {
    position: relative;
    display: inline-block;
    font-family: var(--font-stack);
    font-size: 1rem;
    min-width: 160px;
}

/* The always-visible "selected" pill */
.jr-lang-selected {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 8px 14px;
    border-radius: 8px;
    border: 1px solid var(--border);
    background: var(--white);
    cursor: pointer;
    box-shadow: 0 2px 5px rgba(0,0,0,0.05);
    user-select: none;
    white-space: nowrap;
}
.jr-lang-selected:hover { border-color: var(--blue); }

.jr-lang-chevron {
    margin-left: auto;
    font-size: 0.75rem;
    color: var(--grey-text);
    transition: transform 0.2s;
}
.jr-lang-selected[aria-expanded="true"] .jr-lang-chevron {
    transform: rotate(180deg);
}

/* Flag icon sizing */
.jr-lang-flag,
.jr-lang-option .fi {
    width: 20px;
    height: 15px;
    flex-shrink: 0;
    border-radius: 2px;
}

/* Dropdown list */
.jr-lang-options {
    display: none;
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    min-width: 100%;
    background: var(--white);
    border: 1px solid var(--border);
    border-radius: 8px;
    box-shadow: 0 4px 16px rgba(0,0,0,0.12);
    list-style: none;
    margin: 0;
    padding: 4px 0;
    z-index: 999;
    max-height: 320px;
    overflow-y: auto;
}

/* Individual option */
.jr-lang-option {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 9px 14px;
    cursor: pointer;
    white-space: nowrap;
    transition: background 0.12s;
}
.jr-lang-option:hover { background: var(--grey-light); }
.jr-lang-option[dir="rtl"] { flex-direction: row-reverse; }

/* ============================================================
   6. Controls & Headers
   ============================================================ */
.user-status-bar {
    width: 100%;
    max-width: 1200px;
    text-align: right;
    font-size: 0.9em;
    color: var(--grey-text);
    margin-bottom: 10px;
    align-self: center;
}

#category-heading { color: var(--navy); text-align: center; margin-bottom: 5px; }
#subtitle { color: #7f8c8d; margin-bottom: 30px; text-align: center; font-size: 0.95rem; }

/* ============================================================
   7. Flashcard — shared card shell (browse + quiz)

   .flip-card is used in:
     - flashcard.php (browse mode) only. flashcard_learn.php's Learn-mode
       phase used to share this too, but was moved to the non-flipping
       #browse-image-wrap pattern — see section 22b.

   #learn-image-wrap is the non-flipping image container in:
     - flashcard_learn.php quiz phase (#learn-quiz)

   #browse-image-wrap (section 22b) is the same non-flipping pattern,
   under its own id only because #learn-image-wrap is already used
   elsewhere on the same page (flashcard_learn.php's quiz phase).

   ALL use the same --card-* variables so dimensions, corners, and
   shadow are changed in one place.
   ============================================================ */

/* Flip card — browse mode and learn-browse phase */
.flip-card {
    background-color: transparent;
    perspective: 1000px;
    border: 2px solid #ccc;
    box-shadow: 0 4px 8px rgba(0,0,0,0.1);
    cursor: pointer;
    margin-bottom: 20px;
    position: relative;
    width: var(--card-w);
    height: var(--card-h);
    max-width: var(--card-max-w);
    max-height: var(--card-max-h);
    border-radius: var(--card-radius);
}

.flip-card-inner {
    position: absolute;
    top: 0; left: 0; width: 100%; height: 100%;
    text-align: center;
    transition: transform 0.8s;
    transform-style: preserve-3d;
}

.flip-card-front,
.flip-card-back {
    position: absolute;
    width: 100%; height: 100%;
    backface-visibility: hidden;
    display: flex;
    justify-content: center;
    align-items: center;
    border-radius: var(--card-radius);
    padding: var(--card-img-padding);
}

.flip-card-front { background-color: var(--white); }
.flip-card-back {
    background-color: #333;
    color: var(--white);
    transform: rotateY(180deg);
    flex-direction: column;
    justify-content: center;
    gap: 6px;
}

/* Image on the front face — browse mode */
#image {
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: var(--card-radius);
    transition: opacity 0.5s ease-in-out;
}

/* Word display on back face — flashcard.php's own browse mode only.
   #browse-word (flashcard_learn.php's Learn-mode phase) moved to its
   own light-styled rule in section 22b below — it no longer sits on a
   flip-card back face, see that page's own comment. */
.flip-card-back .word-display,
#word {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    text-align: center;
    width: 100%;
    padding: 6px 10px;
}

.flip-card-back .word-display p,
#word p {
    margin: 0;
    color: var(--white);
    font-family: var(--font-stack);
    font-weight: 700;
    font-size: 2.4rem;
    line-height: 1.3;
}

/* Phonetic lines (pinyin, hiragana, romaji, romanization) — flashcard.php's
   browse-mode flip-card back only. flashcard_learn.php's Learn-mode phase
   used .browse-phonetic here too, before moving to the CSS-structural
   #browse-word p:not(:first-child) rule in section 22b (no longer a
   distinct class, matching lesson_learn.php's own approach). */
.phonetic-line {
    font-size: 1rem;
    font-weight: 400;
    opacity: 0.75;
    color: var(--white);
    font-family: var(--font-stack);
    margin: 0;
}

/* Quiz image container — non-flipping, same physical size as flip-card.
   Padding now reads --card-img-padding, the same variable
   .flip-card-front uses, so this stays visually consistent with browse
   mode by construction rather than by a separately-maintained value. */
#learn-image-wrap {
    width: var(--card-w);
    height: var(--card-h);
    max-width: var(--card-max-w);
    max-height: var(--card-max-h);
    border: 2px solid #ccc;
    border-radius: var(--card-radius);
    padding: var(--card-img-padding);
    overflow: hidden;
    box-shadow: 0 4px 8px rgba(0,0,0,0.1);
    background: var(--white);
}

#learn-image {
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: var(--card-radius);
    transition: opacity 0.4s ease;
}

/* Browse phase image (inside flip-card-front) */
#browse-image {
    width: 100%;
    height: 100%;
    object-fit: cover;
    border-radius: var(--card-radius);
    transition: opacity 0.4s ease;
}

/* ============================================================
   8. Icon Buttons — circular, used in browse and learn-browse
   All icon buttons share the same base style.
   ============================================================ */
.icon-btn {
    width: var(--icon-btn-size);
    height: var(--icon-btn-size);
    border-radius: 50%;
    border: 2px solid var(--border);
    background: var(--white);
    color: var(--navy);
    font-size: 1.1rem;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    flex: none;
    transition: background 0.15s, border-color 0.15s, color 0.15s;
    text-decoration: none;
}
.icon-btn:hover:not(:disabled):not([disabled]) {
    background: var(--blue);
    border-color: var(--blue);
    color: var(--white);
}
.icon-btn:disabled,
.icon-btn[disabled] {
    opacity: 0.3;
    cursor: default;
}

/* ============================================================
   9. Flashcard Buttons — browse mode (flashcard.php)
   ============================================================ */
#button-container {
    display: flex;
    justify-content: center;
    gap: 10px;
    margin-bottom: 30px;
    width: var(--card-w);
    max-width: var(--card-max-w);
}

/* All buttons in #button-container are the circular icon-btn style */
#button-container button {
    width: var(--icon-btn-size);
    height: var(--icon-btn-size);
    border-radius: 50%;
    border: 2px solid var(--border);
    background: var(--white);
    color: var(--navy);
    font-size: 1.1rem;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    flex: none;
    transition: background 0.15s, border-color 0.15s, color 0.15s;
}
#button-container button:hover {
    background: var(--blue);
    border-color: var(--blue);
    color: var(--white);
}

/* ============================================================
   10. Category Grid — category_selection.php
   ============================================================ */
.category-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: 15px;
    width: 100%;
    max-width: 1200px;
    margin-bottom: 30px;
}

.category-card {
    background: var(--white);
    padding: 20px 10px;
    border-radius: 12px;
    box-shadow: 0 4px 10px rgba(0,0,0,0.05);
    text-decoration: none;
    color: #34495e;
    font-weight: bold;
    font-size: 1rem;
    transition: transform 0.2s;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    min-height: 80px;
    text-align: center;
}

.category-card:active { transform: scale(0.98); }
.category-card.all-card { background-color: var(--navy); color: var(--white); }
.category-card.all-card .cat-phonetic { color: #ccc; }

.cat-phonetic {
    font-size: 0.85em;
    color: #888;
    margin-top: 4px;
    font-weight: normal;
    line-height: 1.2;
    font-family: var(--font-stack);
}

@media (min-width: 768px) {
    .category-grid { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 20px; }
    .category-card { padding: 30px 20px; font-size: 1.1rem; }
    .category-card:hover {
        background-color: var(--blue);
        color: var(--white);
        transform: translateY(-3px);
        box-shadow: 0 6px 15px rgba(0,123,255,0.2);
    }
    .category-card:hover .cat-phonetic { color: #e0e0e0; }
}

/* ============================================================
   10a. Library Grid — library_list.php
   Reuses .category-grid for layout; cards get their own class
   because they carry a cover image, unlike the plain text
   .category-card used for word categories.
   ============================================================ */
.library-card {
    background: var(--white);
    border-radius: 12px;
    box-shadow: 0 4px 10px rgba(0,0,0,0.05);
    text-decoration: none;
    color: #34495e;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    transition: transform 0.2s, box-shadow 0.2s;
}

.library-card-img {
    width: 100%;
    aspect-ratio: 4 / 3;
    object-fit: cover;
    display: block;
}

.library-book-title {
    margin: 12px 12px 4px;
    font-size: 1.1rem;
}

.library-book-pages {
    margin: 0 12px 14px;
    font-size: 0.85rem;
    color: #888;
}

@media (min-width: 768px) {
    .library-card:hover {
        transform: translateY(-3px);
        box-shadow: 0 6px 15px rgba(0,123,255,0.2);
    }
}

/* Shared bottom-of-page exit/back control — icon badge with a short
   label underneath. Used by every content-player-style page that isn't
   the mode selector itself: picture_book_view.php, lesson.php,
   category_selection.php, flashcard_selection.php. (memory_game.php's
   win-state buttons reuse only the .exit-bracket icon badge below, not
   this wrapper, since they sit inside a different flex layout.)

   Originally built as a one-off for picture_book_view.php only, under
   a "pre-reading audience" rationale for going icon-primary — but that
   reasoning didn't actually hold up: this control keeps a visible text
   label under the icon on every page, including the one it was
   supposedly written for, which cuts against a reading-ability
   justification. Retired that framing. This is now a deliberate,
   uniform visual style applied consistently across the app, not an
   accessibility accommodation for any one page's audience — corrected
   here after the inconsistency was flagged in review.

   .back-nav/#back-link (plain text-link back button) previously served
   this role on lesson.php, category_selection.php, and
   flashcard_selection.php; removed as dead code once all three were
   migrated to this control — nothing else referenced it.

   fa-right-from-bracket confirmed present and free in Font Awesome
   6.2.1 (this app's pinned CDN version) by fetching that exact
   stylesheet and finding the rule directly, not inferred from a
   different version's documentation — several third-party sources
   disagreed on its Free/Pro tier before that direct check settled it.
   Mirrored with the standard fa-flip-horizontal utility class (also
   confirmed present in the same 6.2.1 stylesheet), since the icon's
   native orientation points right and this app's back-navigation
   convention points left. */
.page-exit-nav { margin-top: 40px; padding-bottom: 20px; width: 100%; text-align: center; }
.page-exit-nav a {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    text-decoration: none;
    color: var(--navy);
}
.page-exit-nav a:hover .exit-bracket { background: var(--blue); border-color: var(--blue); }
.page-exit-nav a:hover .exit-bracket i { color: var(--white); }
.page-exit-nav a:hover span:not(.exit-bracket) { color: var(--blue); }

.exit-bracket {
    width: var(--icon-btn-size);
    height: var(--icon-btn-size);
    border-radius: 50%;
    border: 2px solid var(--border);
    background: var(--white);
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background 0.15s, border-color 0.15s;
}
.exit-bracket i { color: var(--navy); font-size: 1.1rem; transition: color 0.15s; }

.page-exit-nav span:not(.exit-bracket) {
    font-size: 0.85rem;
    color: var(--grey-text);
    font-weight: 600;

}

/* ============================================================
   11. Category Selection — phonetic toggles
   ============================================================ */
#toggle-container {
    margin-top: 15px;
    display: flex;
    gap: 15px;
    flex-wrap: wrap;
    justify-content: center;
    min-height: 24px;
}

.toggle-item {
    display: none;
    align-items: center;
    gap: 5px;
    color: #555;
    font-size: 0.9em;
}

/* ============================================================
   12. Mode Selection — mode_selection.php
   ============================================================ */
.selection-container {
    display: flex;
    justify-content: center;
    gap: 30px;
    margin-top: 20px;
    flex-wrap: wrap;
    padding: 0 20px;
}

.mode-card {
    background: var(--white);
    width: 220px;
    padding: 40px;
    border-radius: 20px;
    box-shadow: 0 4px 15px rgba(0,0,0,0.1);
    text-decoration: none;
    color: #333;
    transition: all 0.2s;
    border: 2px solid transparent;
    display: block;
}
.mode-card:hover { transform: translateY(-5px); border-color: var(--blue); box-shadow: 0 8px 25px rgba(0,0,0,0.15); }
.mode-card i { font-size: 4rem; color: var(--blue); margin-bottom: 20px; }
.mode-card h2 { margin: 0; font-size: 1.8rem; }
.mode-card p { margin-top: 10px; color: #7f8c8d; font-size: 0.9rem; }

/* Inactive placeholder card (e.g. Games) — visible but not clickable.
   Rendered as a <div>, not <a>, by mode_selection.php; this modifier
   only needs to neutralise the hover affordance and mute the icon/
   heading, since the element itself carries no href to disable. */
.mode-card.inactive { cursor: default; opacity: 0.55; }
.mode-card.inactive:hover { transform: none; border-color: transparent; box-shadow: 0 4px 15px rgba(0,0,0,0.1); }
.mode-card.inactive i, .mode-card.inactive h2 { color: var(--grey-text); }
.mode-card-badge { display: inline-block; margin-top: 12px; padding: 4px 12px; border-radius: 12px; background: var(--grey-light); color: var(--grey-text); font-size: 0.75rem; font-weight: 600; text-transform: uppercase; }

/* Home screen (mode_selection.php) icon-first simplification, July 2026.
   Scoped to #home-selection specifically, NOT to the shared .mode-card
   base rule above — that base rule is also used by game_selection.php's
   Memory tile (button.mode-card). Originally game_selection.php kept its
   own .mode-card p description text and was deliberately excluded here;
   that description was removed in a later session (see #game-selection
   below), so both pages now use the same reduced-padding treatment, each
   still scoped independently in case a future page reusing .mode-card
   (e.g. a genuinely inactive placeholder tile) needs to keep its own
   description text and default padding. */
#home-selection .mode-card { padding: 24px 40px; }
#home-selection .mode-card i { margin-bottom: 12px; }

/* Games screen (game_selection.php) icon-first simplification, July 2026.
   Same treatment as #home-selection above, applied here once this page's
   Memory tile description text (.mode-card p) was removed — scoped
   separately rather than merged into one shared selector, so either
   page's spacing can change independently later without affecting the
   other. */
#game-selection .mode-card { padding: 24px 40px; }
#game-selection .mode-card i { margin-bottom: 12px; }

h1#welcome-text { color: var(--navy); margin-bottom: 5px; }
p#instruction-text { color: var(--grey-text); margin-top: 0; font-size: 1.1rem; }

/* ============================================================
   13. Flashcard Player Page — flashcard.php
   ============================================================ */
body.player-page { padding: 50px 20px; }

h1#category-title { color: var(--navy); margin-bottom: 5px; }

#filter-container { display: flex; justify-content: center; margin: 20px 0; }

.segmented-control {
    display: flex;
    background: var(--white);
    padding: 4px;
    border-radius: 50px;
    box-shadow: 0 4px 10px rgba(0,0,0,0.1);
    border: 1px solid var(--border);
}

.filter-btn {
    border: none;
    background: none;
    width: 55px;
    height: 45px;
    border-radius: 40px;
    cursor: pointer;
    font-size: 1.3rem;
    transition: all 0.3s ease;
    display: flex;
    align-items: center;
    justify-content: center;
}

.filter-btn[data-status="all"]         { color: var(--navy); }
.filter-btn[data-status="learned"]     { color: var(--green); }
.filter-btn[data-status="not_learned"] { color: #adb5bd; }
.filter-btn.active[data-status="all"]         { background-color: var(--navy);  color: var(--white); }
.filter-btn.active[data-status="learned"]     { background-color: var(--green); color: var(--white); }
.filter-btn.active[data-status="not_learned"] { background-color: #adb5bd;      color: var(--white); }

#toggle-container div { display: none; margin: 0 10px; }

#controls-and-toggles {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 15px;
    width: 100%;
}

#dropdown-group {
    display: flex;
    gap: 15px;
    flex-wrap: wrap;
    justify-content: center;
}

#admin-category-section {
    margin: 20px auto;
    max-width: 800px;
    padding: 20px;
    background: var(--white);
    border: 1px solid var(--border);
    border-radius: 10px;
    text-align: left;
}

.category-checkbox-group {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
    gap: 12px;
    margin-top: 15px;
}

/* ============================================================
   14. Admin — flashcard_add.php
   ============================================================ */
body.admin-page { display: block; padding: 20px; }

.admin-container {
    max-width: 800px;
    margin: 50px auto;
    background: var(--white);
    padding: 30px;
    border-radius: 10px;
    box-shadow: 0 4px 15px rgba(0,0,0,0.1);
    text-align: center;
}

.admin-container textarea {
    width: 100%;
    height: 100px;
    margin: 10px 0;
    padding: 10px;
    border: 1px solid #ccc;
    border-radius: 5px;
    font-family: var(--font-stack);
}

.format-hint {
    font-size: 0.85em;
    color: var(--grey-text);
    background: #f9f9f9;
    padding: 10px;
    border-left: 4px solid var(--blue);
    margin-bottom: 20px;
    text-align: left;
}

.type-selector { display: flex; gap: 20px; justify-content: center; margin-bottom: 30px; }

.type-btn {
    flex: 1;
    padding: 15px;
    border: 3px solid #eee;
    border-radius: 12px;
    cursor: pointer;
    text-align: center;
    font-weight: bold;
    font-size: 1.2rem;
    transition: 0.2s;
    color: #aaa;
    background: #fafafa;
}
.type-btn.active { border-color: var(--blue); color: var(--blue); background: var(--white); box-shadow: 0 4px 12px rgba(0,123,255,0.15); }
.type-btn:hover:not(.active) { background: #f0f0f0; }

/* ============================================================
   15. Stories List — stories_list.php
   ============================================================ */
body.stories-page {
    font-family: var(--font-stack);
    background-color: #f4f7f6;
    color: #333;
    margin: 0;
    padding: 20px;
    display: block;
    min-height: 100vh;
    width: auto;
    align-items: unset;
}

.stories-container { max-width: 1100px; margin: 0 auto; }
.stories-container h1 { color: var(--navy); text-align: center; margin-bottom: 30px; font-size: 1.8em; }

.stories-controls {
    background: var(--white);
    padding: 20px;
    border-radius: 10px;
    box-shadow: 0 4px 15px rgba(0,0,0,0.05);
    margin-bottom: 25px;
}

.stories-control-row { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 20px; }
.stories-lang-group { display: flex; gap: 20px; align-items: flex-start; flex-wrap: wrap; }
.stories-lang-unit { display: flex; flex-direction: column; gap: 5px; flex: 0 1 auto; min-width: 180px; }
.stories-lang-unit label { font-size: 0.75em; font-weight: bold; color: #7f8c8d; text-transform: uppercase; white-space: nowrap; }
.stories-toggle-switch { display: flex; align-items: center; gap: 10px; font-weight: bold; cursor: pointer; color: var(--navy); font-size: 0.9em; }
.stories-toggle-switch input { width: 18px; height: 18px; cursor: pointer; }
.stories-lang-select { padding: 10px; border-radius: 5px; border: 1px solid var(--border); font-family: var(--font-stack); font-size: 0.95em; width: 100%; }

.stories-list-header { display: grid; grid-template-columns: 80px 1fr 1fr; gap: 15px; padding: 12px 20px; font-weight: bold; background: var(--navy); color: var(--white); border-radius: 8px 8px 0 0; font-size: 0.9em; }

.story-item { display: grid; grid-template-columns: 80px 1fr 1fr; gap: 15px; padding: 15px 20px; background: var(--white); border-bottom: 1px solid #eee; transition: all 0.2s; align-items: center; text-decoration: none; color: inherit; }
.story-item:hover { background: #f8f9fa; }
.story-item:last-child { border-radius: 0 0 8px 8px; border-bottom: none; }

.difficulty-badge { background: var(--red); color: var(--white); padding: 4px 8px; border-radius: 15px; font-size: 0.75em; text-align: center; font-weight: bold; display: inline-block; white-space: nowrap; }
.story-title-text { font-weight: 600; font-size: 1em; word-break: break-word; }
.story-secondary-col { color: #7f8c8d; border-left: 2px solid #f4f7f6; padding-left: 12px; }
.hide-col { display: none !important; }
.full-width-grid { grid-template-columns: 80px 1fr !important; }

@media (max-width: 600px) {
    body.stories-page { padding: 10px; }
    .stories-container h1 { font-size: 1.5em; margin-bottom: 20px; }
    .stories-controls { padding: 15px; }
    .stories-control-row { flex-direction: column; align-items: stretch; }
    .stories-lang-group { flex-direction: column; width: 100%; }
    .stories-lang-unit { width: 100%; min-width: 0; }
    .stories-lang-unit label { white-space: normal; }
    .stories-list-header { display: none; }
    .story-item { grid-template-columns: 60px 1fr !important; padding: 15px; gap: 10px; }
    .story-secondary-col { grid-column: 2; border-left: none; padding-left: 0; margin-top: -5px; font-size: 0.9em; }
}

/* ============================================================
   16. Story View — story_view.php
   ============================================================ */
body.story-view-page {
    font-family: var(--font-stack);
    line-height: 1.6;
    color: #333;
    max-width: 1400px;
    margin: 0 auto;
    padding: 20px;
    background-color: #f4f7f6;
    display: block;
    width: auto;
    align-items: unset;
}

.story-back-link { display: inline-block; margin-bottom: 20px; text-decoration: none; color: var(--blue); font-weight: bold; }

.story-container { display: grid; grid-template-columns: 1fr 1fr; gap: 40px; background: var(--white); padding: 30px; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); transition: all 0.3s ease; }

.story-column-controls { padding-bottom: 15px; border-bottom: 2px solid #eee; margin-bottom: 20px; }
.story-lang-select { width: 100%; padding: 10px; border-radius: 5px; border: 1px solid var(--border); font-size: 1em; font-family: var(--font-stack); margin-bottom: 10px; }
.story-toggle-group { display: flex; gap: 15px; font-size: 0.85em; color: var(--grey-text); height: 35px; align-items: center; }
.story-toggle-group label { display: flex; align-items: center; cursor: pointer; white-space: nowrap; font-weight: 500; }
.story-toggle-group input { margin-right: 8px; cursor: pointer; width: 18px; height: 18px; }
.story-column-title { font-size: 1.8em; font-weight: 700; color: var(--navy); margin-bottom: 20px; position: relative; padding-right: 40px; min-height: 2.2em; display: flex; align-items: flex-start; font-family: var(--font-stack); }
.paragraph-row { display: contents; }
.p-box { margin-bottom: 30px; font-size: 1.15em; text-align: justify; position: relative; padding-right: 45px; border-bottom: 1px solid #f9f9f9; padding-bottom: 15px; font-family: var(--font-stack); }
.extra-field { display: block; font-size: 0.9em; color: #5d6d7e; margin-top: 6px; font-style: normal; font-weight: 400; font-family: var(--font-stack); }
.tts-icon { cursor: pointer; color: var(--blue); transition: color 0.2s; font-size: 1.2rem; }
.tts-icon:hover { color: var(--blue-dark); }
.title-tts { position: absolute; right: 5px; top: 10px; }
.para-tts { position: absolute; right: 5px; top: 5px; }
.story-full-width-grid { grid-template-columns: 1fr !important; }

@media (max-width: 900px) {
    .story-container { grid-template-columns: 1fr; gap: 20px; }
}

/* ============================================================
   17. Category Selection Mode Toggle — category_selection.php
   ============================================================ */
#mode-toggle-wrap { display: flex; flex-direction: column; align-items: center; gap: 10px; margin-bottom: 10px; }

.mode-toggle-group {
    display: flex;
    background: var(--white);
    padding: 4px;
    border-radius: 50px;
    box-shadow: 0 4px 10px rgba(0,0,0,0.1);
    border: 1px solid var(--border);
    gap: 0;
}

.mode-toggle-btn {
    padding: 10px 22px;
    border-radius: 40px;
    text-decoration: none;
    font-weight: bold;
    font-size: 0.95rem;
    color: #888;
    transition: all 0.2s;
    display: flex;
    align-items: center;
    gap: 7px;
    white-space: nowrap;
}
.mode-toggle-btn:hover { color: var(--navy); }
.mode-toggle-btn.active { background: var(--blue); color: var(--white); box-shadow: 0 2px 8px rgba(0,123,255,0.3); }

.mode-description { color: #888; font-size: 0.9rem; margin: 0; }

/* ============================================================
   18. Learning Mode — flashcard_learn.php (Browse + Quiz + Results)
   ============================================================ */
body.learn-page {
    padding: 20px;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
}

/* type-d-loading / type-e-loading / type-pbq-loading / lesson-learn-loading
   each invented their own loading-div id instead of reusing #learn-loading
   (already used by flashcard_learn.php / language_gym.php / memory_game.php),
   so none of them ever got this rule — they rendered as plain top-aligned
   block content instead of vertically centered, which is why the spinner
   looked like it was sitting under/behind the site header. Grouped in here
   rather than duplicated, matching the "single source of truth" rule at
   the top of this file. */
#learn-loading,
#type-d-loading,
#type-e-loading,
#type-pbq-loading,
#lesson-learn-loading {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 15px;
    min-height: 60vh;
    color: var(--grey-text);
    font-size: 1.1rem;
}

/* Shared header row (browse phase and quiz phase) */
#learn-header {
    width: 100%;
    max-width: var(--card-max-w);
    display: flex;
    align-items: center;
    gap: 12px;
    padding-top: 40px;
}

/* Back link — icon, no underline */
.header-icon-btn {
    color: #888;
    font-size: 1rem;
    text-decoration: none;
    padding: 6px 8px;
    border-radius: 6px;
    transition: color 0.2s, background 0.2s;
    white-space: nowrap;
}
.header-icon-btn:hover { color: var(--navy); background: #f0f0f0; }

/* ---------------------------------------------------------------
   Browse phase
--------------------------------------------------------------- */
#learn-browse {
    width: 100%;
    max-width: var(--card-max-w);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 16px;
}

#browse-progress-bar-wrap {
    flex: 1;
    height: 6px;
    background: var(--grey-light);
    border-radius: 4px;
    overflow: hidden;
    margin: 0 12px;
}

#browse-progress-bar {
    height: 100%;
    width: 0%;
    background: var(--blue);
    border-radius: 4px;
    transition: width 0.4s ease;
}

#browse-counter { font-size: 0.85rem; color: #888; white-space: nowrap; }

#browse-card-wrap {
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 16px;
}

/* Navigation button row: ← 🔊 → */
#browse-btn-row {
    display: flex;
    justify-content: center;
    gap: 16px;
    width: var(--card-w);
    max-width: var(--card-max-w);
}

/* browse-nav-btn extends icon-btn — kept as alias for clarity */
.browse-nav-btn { /* inherits .icon-btn */ }

/* Language bar + phonetic toggles — browse phase */
#learn-language-bar {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
    margin-top: 6px;
}

#browse-toggle-container,
#quiz-toggle-container {
    display: flex;
    gap: 12px;
    flex-wrap: wrap;
    justify-content: center;
}

/* ---------------------------------------------------------------
   Quiz phase
--------------------------------------------------------------- */
#learn-quiz {
    width: 100%;
    max-width: var(--card-max-w);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 20px;
}

/* type-d-screen / type-e-screen / type-pbq-screen / lesson-learn-screen
   all wrap the exact same header/card-wrap/language-bar triad as
   #learn-quiz above, but were never given this same container rule —
   they fell back to default block stacking with a 0px gap between the
   header row and the card, which is what actually caused the "no space
   under the progress bar" bug. Same values as #learn-quiz on purpose:
   this is the same shape of screen, just with different card-wrap
   contents. Don't diverge the gap value here without also revisiting
   #learn-quiz, or the two will drift apart again. */
#type-d-screen,
#type-e-screen,
#type-pbq-screen,
#lesson-learn-screen {
    width: 100%;
    max-width: var(--card-max-w);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 20px;
}

#learn-progress-bar-wrap {
    flex: 1;
    height: 8px;
    background: #e0e0e0;
    border-radius: 4px;
    overflow: hidden;
}

#learn-progress-bar {
    height: 100%;
    width: 0%;
    background: var(--blue);
    border-radius: 4px;
    transition: width 0.4s ease;
}

#learn-counter { font-size: 0.85rem; color: #888; white-space: nowrap; }

#learn-card-wrap {
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 16px;
}

/* 2×2 option grid */
#learn-options {
    width: var(--card-w);
    max-width: var(--card-max-w);
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
}

.learn-option-btn {
    padding: 16px 10px;
    font-size: 1rem;
    font-family: var(--font-stack);
    font-weight: bold;
    background: var(--white);
    color: var(--navy);
    border: 2px solid var(--border);
    border-radius: 10px;
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s, transform 0.1s;
    text-align: center;
    line-height: 1.3;
    word-break: break-word;
}
.learn-option-btn:hover:not(:disabled) {
    background: #f0f7ff;
    border-color: var(--blue);
    transform: translateY(-2px);
}
.learn-option-btn:disabled { cursor: default; }
.learn-option-btn.option-correct { background: #d4edda; border-color: var(--green); color: #155724; }
.learn-option-btn.option-wrong   { background: #f8d7da; border-color: var(--red);   color: #721c24; }

/* Feedback strip */
#learn-feedback {
    width: var(--card-w);
    max-width: var(--card-max-w);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 12px;
}

#learn-feedback-inner {
    display: flex;
    align-items: center;
    gap: 12px;
    font-size: 1.2rem;
    font-weight: bold;
    font-family: var(--font-stack);
}

.feedback-correct { color: var(--green); font-size: 1.4rem; }
.feedback-wrong   { color: var(--red);   font-size: 1.4rem; }

/* Feedback action row — word + TTS button + Next button */
#learn-feedback-actions {
    display: flex;
    align-items: center;
    gap: 12px;
}

#learn-next-btn {
    padding: 12px 32px;
    background: var(--blue);
    color: var(--white);
    border: none;
    border-radius: 8px;
    font-size: 1rem;
    font-weight: bold;
    cursor: pointer;
    transition: background 0.2s;
}
#learn-next-btn:hover { background: var(--blue-dark); }

/* ---------------------------------------------------------------
   Results screen
--------------------------------------------------------------- */
#learn-results {
    width: 100%;
    max-width: var(--card-max-w);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 24px;
    padding-top: 60px;
}

#results-header { text-align: center; }
#results-header h1 { color: var(--navy); margin-bottom: 10px; }

#results-score-wrap { font-size: 3.5rem; font-weight: bold; line-height: 1; }
#results-score-denom { font-size: 1.8rem; color: #888; font-weight: normal; }

#results-score-num.score-high { color: var(--green); }
#results-score-num.score-mid  { color: #fd7e14; }
#results-score-num.score-low  { color: var(--red); }

#results-summary { font-size: 1.1rem; color: #555; text-align: center; }
#results-missed-section { width: 100%; }
#results-missed-section h2 { color: var(--red); font-size: 1rem; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 12px; }
#results-missed-list { display: flex; flex-direction: column; gap: 10px; }

.missed-card-item { display: flex; align-items: center; gap: 14px; background: var(--white); border: 1px solid #f8d7da; border-radius: 8px; padding: 10px 14px; }
.missed-card-item img { width: 56px; height: 40px; object-fit: cover; border-radius: 5px; flex-shrink: 0; }
.missed-card-item span { font-weight: bold; font-size: 1.05rem; color: var(--navy); font-family: var(--font-stack); }
.missed-card-item em { font-style: normal; color: #888; font-size: 0.9rem; margin-left: auto; }

#results-actions { display: flex; gap: 14px; flex-wrap: wrap; justify-content: center; padding-bottom: 40px; }

#results-retry-btn {
    padding: 14px 28px;
    background: var(--blue);
    color: var(--white);
    border: none;
    border-radius: 8px;
    font-size: 1rem;
    font-weight: bold;
    cursor: pointer;
    transition: background 0.2s;
}
#results-retry-btn:hover { background: var(--blue-dark); }

#results-browse-btn {
    padding: 14px 28px;
    background: var(--white);
    color: var(--navy);
    border: 2px solid var(--border);
    border-radius: 8px;
    font-size: 1rem;
    font-weight: bold;
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    transition: border-color 0.2s, color 0.2s;
}
#results-browse-btn:hover { border-color: var(--blue); color: var(--blue); }

/* ============================================================
   19. Level-up banner
   ============================================================ */
#results-levelup {
    background: linear-gradient(135deg, var(--blue), var(--blue-dark));
    color: var(--white);
    padding: 14px 24px;
    border-radius: 10px;
    font-size: 1.1rem;
    font-weight: bold;
    text-align: center;
    width: 100%;
    max-width: var(--card-max-w);
    box-shadow: 0 4px 15px rgba(0,123,255,0.3);
}

/* ============================================================
   Lessons — lesson_list.php + lesson.php
   ============================================================ */

/* Lesson list grid */
#lesson-grid {
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 20px;
    padding: 20px;
    max-width: 900px;
    margin: 0 auto;
}

.lesson-card {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    width: 180px;
    padding: 24px 16px 20px;
    background: var(--white);
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: 0 2px 8px rgba(0,0,0,0.07);
    text-decoration: none;
    color: var(--navy);
    transition: box-shadow 0.15s, transform 0.15s;
    gap: 10px;
}
.lesson-card:hover {
    box-shadow: 0 4px 16px rgba(0,0,0,0.13);
    transform: translateY(-2px);
}
.lesson-card .lesson-number {
    font-size: 1.4rem;
    font-weight: 700;
    color: var(--blue);
}
.lesson-card .lesson-label {
    font-size: 0.9rem;
    color: var(--grey-text);
}

/* Mini progress bar on lesson list cards — HIDDEN for now (product
   owner decision, July 2026 session): the is_learned toggle this
   tracks isn't reachable from anything on lesson_list.php itself (only
   from Browse/quiz, elsewhere), so the number reads as disconnected
   from any action available on this screen. lesson_list.php's PHP
   still computes and renders $total/$learned/$pct and the two
   elements below unchanged — this is a display-only suppression, not
   a removal, so re-enabling later is just deleting this rule. */
.lesson-mini-bar,
.lesson-card .lesson-progress-text {
    display: none;
}

.lesson-mini-bar {
    width: 100%;
    height: 6px;
    background: var(--grey-light);
    border-radius: 3px;
    overflow: hidden;
    margin-top: 4px;
}
.lesson-mini-bar-fill {
    height: 100%;
    background: var(--blue);
    border-radius: 3px;
    transition: width 0.3s;
}
.lesson-card .lesson-progress-text {
    font-size: 0.78rem;
    color: var(--grey-text);
}

/* Full progress bar on lesson.php player */
#lesson-progress {
    display: flex;
    align-items: center;
    gap: 10px;
    justify-content: center;
    margin: 10px auto 0;
    max-width: 420px;
    padding: 0 20px;
}
#progress-bar-track {
    flex: 1;
    height: 8px;
    background: var(--grey-light);
    border-radius: 4px;
    overflow: hidden;
}
#progress-bar-fill {
    height: 100%;
    width: 0%;
    background: var(--blue);
    border-radius: 4px;
    transition: width 0.4s;
}
#progress-label {
    font-size: 0.82rem;
    color: var(--grey-text);
    white-space: nowrap;
}

/* Card counter */
#card-counter-wrap {
    text-align: center;
    margin: 6px 0 0;
    font-size: 0.85rem;
    color: var(--grey-text);
}
/* ============================================================
   11. Picture Book Reader — picture_book_view.php
   Reuses --card-* variables for consistent, mobile-responsive
   sizing with the existing flip-card (flashcard.php).
   ============================================================ */
.picture-book-page {
    width: var(--card-w);
    height: var(--card-h);
    max-width: var(--card-max-w);
    max-height: var(--card-max-h);
    margin: 0 auto;
    border: 2px solid var(--border);
    border-radius: var(--card-radius);
    padding: var(--card-img-padding);
    box-shadow: 0 4px 8px rgba(0,0,0,0.1);
    overflow: hidden;
    background: var(--white);
}

#page-image {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
    transition: opacity 0.5s ease-in-out;
}

#page-text {
    text-align: center;
    font-family: var(--font-stack);
    font-size: 1.6rem;
    font-weight: 600;
    color: var(--navy);
    max-width: var(--card-max-w);
    margin: 16px auto 10px;
    padding: 0 10px;
    transition: opacity 0.5s ease-in-out;
}

/* Stacked phonetic lines under the page text — same idea as Type E's
   #type-e-sentence-lines / script_lesson.js's #word: a flex-column
   wrapper holding zero or more lines, one per active toggle, NOT a
   single fixed-ID element holding one resolved string. The latter shape
   is what this used to be (#pb-phonetic-line as a lone <p>), which meant
   for Japanese specifically — the one language with two independent
   phonetic toggles (hiragana AND romaji) that can both be on at once —
   only whichever line pbResolvePhonetic() checked first could ever
   render, even with both toggles checked. Every other language here only
   ever has at most one phonetic line active, so the bug was invisible
   anywhere except ja-JP with both toggles on. */
.pb-phonetic-wrap {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    text-align: center;
    max-width: var(--card-max-w);
    margin: 0 auto 10px;
    padding: 0 10px;
    transition: opacity 0.5s ease-in-out;
}
.pb-phonetic-wrap p {
    margin: 0;
    font-family: var(--font-stack);
    font-size: 1rem;
    font-weight: 400;
    color: var(--grey-text);
}

.pb-title-phonetic-wrap {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    text-align: center;
    margin: -5px auto 15px;
    transition: opacity 0.5s ease-in-out;
}
.pb-title-phonetic-wrap p {
    margin: 0;
    font-family: var(--font-stack);
    font-size: 1.1rem;
    font-weight: 400;
    color: var(--grey-text);
}

#page-indicator {
    text-align: center;
    font-size: 0.85rem;
    color: var(--grey-text);
    margin-top: 4px;
}

/* ============================================================
   20. Type D — Multi-picture Selection (lesson_type_d.php)
   Image options grid: same outer width as #learn-options
   (var(--card-w)/var(--card-max-w)), but tiles are images, not text
   buttons, and the brief specifies 2-4 options (not always exactly 4),
   so this uses auto-fill instead of a fixed 2-column grid, matching the
   same pattern .category-grid / .category-checkbox-group already use.
   ============================================================ */
/* Type D: column layout, TTS below the prompt text — matches Type E's
   #type-e-sentence-wrap column treatment (product owner wants TTS
   consistently below the text on both screens now). picture_book_quiz.php
   keeps its own separate row rule below — not requested to change, and
   PBQ's question text isn't the same "answer this by picking an image"
   pattern Type D/E share, so no assumption made that it should follow. */
#type-d-prompt-wrap {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 12px;
    width: var(--card-w);
    max-width: var(--card-max-w);
}

#type-pbq-prompt-wrap {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 12px;
    width: var(--card-w);
    max-width: var(--card-max-w);
}

/* Stacked prompt lines — native word always first, then any active
   phonetic line underneath. Same structure and styling as Type E's
   #type-e-sentence-lines / #type-e-sentence-lines p. picture_book_quiz.php
   (#type-pbq-prompt-text) uses this identical structure for its question
   text — added here, was missing when the type-pbq- prefix was first
   chosen, which left the quiz's question text completely unstyled. */
#type-d-prompt-text,
#type-pbq-prompt-text {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    text-align: center;
}

#type-d-prompt-text p,
#type-pbq-prompt-text p {
    margin: 0;
    color: var(--navy);
    font-family: var(--font-stack);
    font-weight: bold;
    font-size: 1.3rem;
    line-height: 1.3;
}

/* Scoped to #type-d-screen specifically, not the shared #learn-image-wrap
   rule itself (used by Type E / Lesson Learn / Picture Book Quiz / Browse
   /Quiz too) — position:relative here only affects Type D's instance,
   giving #type-d-image-placeholder below a containing block to fill. */
#type-d-screen #learn-image-wrap {
    position: relative;
}

/* Gray fill + question mark, shown while #learn-image is blank/unanswered
   — a plain white box (the wrap's own default background) read as "still
   loading" rather than "nothing to show yet," since it's the same white
   as the page background around it. inset uses --card-img-padding, NOT
   0 — an absolutely positioned element's containing block is the nearest
   positioned ancestor's PADDING box, not its content box, so inset:0
   would sit flush with the wrap's border and ignore the padding gap the
   real #learn-image sits inside (confirmed by comparing rendered
   screenshots: the placeholder had no border gap, the real photo did).
   --card-img-padding on all four sides reproduces that same gap. */
#type-d-image-placeholder {
    position: absolute;
    inset: var(--card-img-padding);
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--grey-light);
    border-radius: var(--card-radius);
    color: var(--grey-text);
    font-size: 3rem;
    transition: opacity 0.8s ease;
}

/* Reveal fade doubled from the shared #learn-image rule's 0.4s (used
   as-is by Type E / Lesson Learn / Picture Book Quiz / Browse+Quiz,
   unchanged) — Type D's reveal is a slower, more deliberate "here's the
   answer" moment, not the quick card-to-card transition those other
   screens need, so it gets its own longer duration rather than changing
   the shared base rule under it. */
#type-d-screen #learn-image {
    transition: opacity 0.8s ease;
}

/* Fixed 2-column grid, matching #learn-options/.learn-options-grid's
   established pattern — was repeat(auto-fill, minmax(120px, 1fr)), which
   let the column count drift with container width/option count instead
   of staying a predictable 2x2. Options can still be 2-4 per the brief
   (not always exactly 4): 2 options fills one row, 3 fills row one plus
   a single tile on row two — same variable-count behavior
   .learn-options-grid already handles for Type E's 2-3 text options,
   just applied here to image tiles instead of text. */
#type-d-options {
    width: var(--card-w);
    max-width: var(--card-max-w);
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
}

.type-d-option-img-btn {
    aspect-ratio: 4 / 3;
    border: 2px solid var(--border);
    border-radius: 10px;
    overflow: hidden;
    cursor: pointer;
    padding: 0;
    background: var(--white);
    transition: background 0.15s, border-color 0.15s, transform 0.1s;
}
.type-d-option-img-btn img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.type-d-option-img-btn:hover:not(:disabled) {
    border-color: var(--blue);
    transform: translateY(-2px);
}
.type-d-option-img-btn:disabled { cursor: default; }
.type-d-option-img-btn.option-correct { border-color: var(--green); }
.type-d-option-img-btn.option-wrong   { border-color: var(--red); }

/* ============================================================
   20b. Language Gym Visual (language_gym_visual.php), July 2026
   Image-option grid for the word->image direction. Mirrors
   #type-d-options exactly (same width/columns/gap) — a deliberate
   id-scoped duplicate rather than a shared class, since #type-d-options
   is scoped to Lessons' Type D specifically and this is a distinct
   page (same reasoning as #home-selection / #game-selection each
   keeping their own copy of an identical padding rule above). The
   tile buttons themselves DO reuse .type-d-option-img-btn directly,
   unduplicated — that class was already page-agnostic (no id-scoping
   in its own selector), so sharing it needs no new rule at all.
   ============================================================ */
#gymvisual-image-options {
    width: var(--card-w);
    max-width: var(--card-max-w);
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
}

/* Text prompt above the image grid — mirrors #type-d-prompt-wrap's
   column layout (TTS below the text, not beside it) exactly, same
   reasoning as that rule's own comment. */
#gymvisual-prompt-wrap {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 12px;
    width: var(--card-w);
    max-width: var(--card-max-w);
}

/* MISSED IN THE INITIAL BUILD: the wrapper's layout was here, but the
   actual text styling for the word itself was not — #gymvisual-prompt-text
   rendered as default unstyled browser text (small, black, no
   font-family) instead of matching #type-d-prompt-text p's look.
   Confirmed via a real screenshot comparison against lesson_type_d.php,
   not caught before delivery. Copied verbatim from that rule now. */
#gymvisual-prompt-text {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    text-align: center;
}
#gymvisual-prompt-text p {
    margin: 0;
    color: var(--navy);
    font-family: var(--font-stack);
    font-weight: bold;
    font-size: 1.3rem;
    line-height: 1.3;
}

/* Also missed initially: Type D's image-placeholder/reveal treatment
   was dropped entirely rather than adapted, and that omission was
   never actually surfaced as a decision — confirmed after a direct
   screenshot comparison. #learn-image-wrap is scoped via the
   .gymvisual-page body class (not a bare #learn-image-wrap rule),
   since that id is also used by language_gym.php — an unscoped rule
   here would silently apply position:relative there too. Same
   reasoning as #type-d-screen's own identical scoping decision,
   applied via a body class instead of a wrapper id since this page
   has no unique per-screen wrapper equivalent to #type-d-screen. */
.gymvisual-page #learn-image-wrap {
    position: relative;
}
#gymvisual-image-placeholder {
    position: absolute;
    inset: var(--card-img-padding);
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--grey-light);
    border-radius: var(--card-radius);
    color: var(--grey-text);
    font-size: 3rem;
    transition: opacity 0.8s ease;
}
/* Same slower, deliberate reveal duration as Type D's own override —
   see that rule's comment for why 0.8s instead of the shared 0.4s. */
.gymvisual-page #learn-image {
    transition: opacity 0.8s ease;
}

/* ============================================================
   20d. Language Gym Visual — Two-column layout (>=900px) and
   landscape-phone variant. Mirrors Type D/E's own section 22/22b
   treatment exactly (same breakpoints, same grid columns/gaps) —
   missing entirely from the initial build, confirmed by a direct
   screenshot comparison against lesson_type_d.php at the same
   viewport width: Type D adopts a two-column layout at >=900px,
   this page stayed single-column stacked at every width.

   Scoped via .gymvisual-page rather than a dedicated #screen
   wrapper id, unlike Type D/E's own #type-d-screen/#type-e-screen
   scoping — this page has no equivalent; its outer container
   (#learn-quiz) is shared verbatim with language_gym.php, so
   scoping through THAT id would leak these rules onto that page's
   own single-column layout too. Every selector below goes through
   .gymvisual-page specifically, never the bare shared ids
   (#learn-card-wrap, #learn-image-wrap, #learn-options) — same
   reasoning as Type D/E's own scoping comment. Confirmed
   language_gym.php, flashcard_learn.php, lesson_learn.php, and
   picture_book_quiz.php are unaffected, since none of them carry
   this class.

   Both directions handled in one ruleset: image_to_word only
   populates column 1 row 1 (image) and column 2 row 1 (text
   options) — no row 2 content, which CSS grid doesn't require.
   word_to_image additionally places its prompt text in column 1
   row 2, the same slot #type-d-prompt-wrap occupies. #learn-options
   and #gymvisual-image-options share the same column 2 row 1 slot —
   safe, since direction determines which one is actually visible;
   an assigned grid slot on a hidden element has no effect.
   ============================================================ */
@media (min-width: 900px) {
    .gymvisual-page #learn-quiz {
        max-width: 1000px;
    }

    .gymvisual-page #learn-card-wrap {
        display: grid;
        grid-template-columns: auto 1fr;
        column-gap: 32px;
        row-gap: 16px;
        align-items: start;
        width: 100%;
    }

    .gymvisual-page #learn-image-wrap {
        grid-column: 1;
        grid-row: 1;
    }

    .gymvisual-page #gymvisual-prompt-wrap {
        grid-column: 1;
        grid-row: 2;
    }

    .gymvisual-page #learn-options,
    .gymvisual-page #gymvisual-image-options {
        grid-column: 2;
        grid-row: 1;
        align-self: end;
        width: 100%;
        max-width: none;
    }
}

@media (max-width: 899px) and (min-width: 480px) and (orientation: landscape) {
    .gymvisual-page #learn-card-wrap {
        display: grid;
        grid-template-columns: auto 1fr;
        column-gap: 20px;
        row-gap: 8px;
        align-items: start;
        width: 100%;
    }

    .gymvisual-page #learn-image-wrap {
        grid-column: 1;
        grid-row: 1;
        width: auto;
        height: 55vh;
        max-height: 260px;
        aspect-ratio: 4 / 3;
    }

    .gymvisual-page #gymvisual-prompt-wrap {
        grid-column: 1;
        grid-row: 2;
        width: auto;
        max-width: 260px;
    }

    .gymvisual-page #learn-options,
    .gymvisual-page #gymvisual-image-options {
        grid-column: 2;
        grid-row: 1;
        align-self: end;
        width: 100%;
        max-width: none;
    }
}

/* ============================================================
   21. Type E — Picture-supported Cloze (lesson_type_e.php)
   Sentence-with-gap line, sits between the image and the option
   buttons. Options below reuse .learn-option-btn and #learn-options'
   grid rule directly via .learn-options-grid (identical shape/size,
   no new visual design needed since it's the same text-button pattern).
   ============================================================ */
/* Column, not row: TTS sits BELOW the sentence text, not beside it —
   originally a deliberate deviation from Type D's row layout, confirmed
   with product owner specifically for Type E. Type D has since been
   switched to the same column layout too (see #type-d-prompt-wrap above)
   for consistency between the two — picture_book_quiz.php's
   #type-pbq-prompt-wrap is the one still row-layout now, not requested
   to change. */
#type-e-sentence-wrap {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 12px;
    width: var(--card-w);
    max-width: var(--card-max-w);
}

/* Stacked sentence lines — native sentence always first, then any
   combination of hiragana/romaji/pinyin/romanization underneath when
   their toggle is checked. Matches #word's structure and styling
   exactly (lesson.php's createCard()), not #browse-word's lighter
   p:not(:first-child) sub-line treatment (style.css section 22b) —
   Type E is a Lesson-family sibling, and lesson.php itself doesn't use
   that lighter style either. */
#type-e-sentence-lines {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    text-align: center;
}

#type-e-sentence-lines p {
    margin: 0;
    color: var(--navy);
    font-family: var(--font-stack);
    font-weight: bold;
    font-size: 1.3rem;
    line-height: 1.3;
}

/* Pre-answer phonetic-line gap (July 2026 session, follow-up) — the
   known/fixed portion of a pinyin/hiragana/romaji/romanization line is
   visible pre-answer now (inherits color/weight/size from the p rule
   above); only the gap ITSELF is invisible, via color, not display or
   visibility, so it still occupies its normal inline width and doesn't
   shift the surrounding text or collapse the line's layout. */
.type-e-gap-invisible {
    color: transparent;
}

.learn-options-grid {
    width: var(--card-w);
    max-width: var(--card-max-w);
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
}

/* Stacked-line option buttons (native word + any active phonetic line
   underneath) — originally built for Type E only, per product owner
   decision, as a deliberate deviation from the Vocabulary quiz
   (script_learn.js), which at the time never applied phonetic toggles to
   its option buttons at all (always showed card.target_word as plain
   text, with no toggle UI on the quiz screen and no phonetic data even
   fetched for distractor options). That gap was fixed later this same
   session — fetch_lesson.php now fetches phonetics for every option
   (correct + all distractors), and script_learn.js reuses these same
   classes for its own option buttons. No longer Type-E-exclusive; kept
   the same class names rather than duplicating them, since the visual
   behavior needed is identical in both places. */
.type-e-option-stack {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
}

.type-e-option-line {
    display: block;
}

/* Phonetic sub-line — same relationship to its parent button as
   .cat-phonetic has to .category-card: smaller, lighter, normal weight.
   Stays a fixed neutral gray regardless of correct/wrong state — per
   product owner decision, only the main native word changes color when
   the answer is revealed, matching .cat-phonetic's own fixed-color
   precedent rather than inheriting the parent's state color. */
.type-e-option-phonetic {
    font-size: 0.85em;
    color: #888;
    font-weight: normal;
    line-height: 1.2;
    font-family: var(--font-stack);
}
.learn-option-btn.option-correct .type-e-option-phonetic,
.learn-option-btn.option-wrong   .type-e-option-phonetic { color: #888; }

/* ============================================================
   22b. Type D / Type E — Landscape phone variant of the two-column
   layout (same concept as section 22 below, adapted for a landscape
   phone's real constraint: limited HEIGHT — as low as ~320px on some
   devices — not limited width like desktop.

   The image uses aspect-ratio: 4/3 rather than the shared var(--card-w)/
   var(--card-h) matched-vw pair, specifically so width and height can't
   drift out of proportion the way an earlier mobile-only attempt at
   resizing this box did — aspect-ratio guarantees they stay locked
   together regardless of which dimension (height, here, driven by vh)
   is the binding constraint. This technique isn't new to the file —
   it's the same one already used for Type D's own option tiles
   (.type-d-option-img-btn), just applied here for the first time to
   #learn-image-wrap itself.

   Exact vh/px values below are a first pass, not verified against a
   real device — I have no way to render this here. If the image reads
   too large/small on an actual phone, these two numbers (height: 55vh,
   max-height: 260px) are the only things that should need adjusting.

   Same scoping caveats as section 22: every selector goes through
   #type-d-screen/#type-e-screen, never the bare shared ids, for the
   same reasons documented there. ============================================================ */
@media (max-width: 899px) and (min-width: 480px) and (orientation: landscape) {
    #type-d-screen #learn-card-wrap,
    #type-e-screen #learn-card-wrap {
        display: grid;
        grid-template-columns: auto 1fr;
        column-gap: 20px;
        row-gap: 8px;
        align-items: start;
        width: 100%;
    }

    #type-d-screen #learn-image-wrap,
    #type-e-screen #learn-image-wrap {
        grid-column: 1;
        grid-row: 1;
        width: auto;
        height: 55vh;
        max-height: 260px;
        aspect-ratio: 4 / 3;
    }

    /* #type-d-prompt-wrap / #type-e-sentence-wrap are unique to their
       own file already, same as in section 22. max-width is capped to
       roughly match the shrunk image's rendered width above (so text
       wraps to a similar column, not the 600px desktop/portrait width)
       — approximate on purpose, this doesn't need to be pixel-exact. */
    #type-d-prompt-wrap,
    #type-e-sentence-wrap {
        grid-column: 1;
        grid-row: 2;
        width: auto;
        max-width: 260px;
    }

    /* Same fill + bottom-anchor-to-image treatment as the desktop rule
       in section 22 (grid-row:1 only, not spanning into row 2, so
       align-self:end anchors to the image specifically). */
    #type-d-options,
    #type-e-options {
        grid-column: 2;
        grid-row: 1;
        align-self: end;
        width: 100%;
        max-width: none;
    }
}

/* ============================================================
   22. Type D / Type E — Desktop two-column layout (≥900px)
   Image + prompt/sentence text stay stacked exactly as they are today
   (product owner decision); only the options grid moves out to a
   second column beside them, using the blank left/right space desktop
   has instead of stacking options below the fold — this was requiring
   a scroll to reach the answers even on desktop, not just mobile.

   Reuses story_view.php's existing 900px image+text breakpoint
   (.story-container, confirmed at line ~902) rather than inventing a
   new one. Below 900px both screens are completely untouched — same
   single-column stack as before this change.

   Every selector below is scoped through #type-d-screen/#type-e-screen
   specifically, NOT the bare shared ids/classes (#learn-card-wrap,
   #learn-image-wrap, .learn-options-grid), because those are also used
   by flashcard_learn.php, language_gym.php, lesson_learn.php, and
   picture_book_quiz.php — confirmed by checking every consumer before
   writing this, not assumed. None of those four pages are affected by
   anything in this section. */
@media (min-width: 900px) {
    #type-d-screen,
    #type-e-screen {
        max-width: 1000px;
    }

    #type-d-screen #learn-card-wrap,
    #type-e-screen #learn-card-wrap {
        display: grid;
        grid-template-columns: auto 1fr;
        column-gap: 32px;
        row-gap: 16px;
        align-items: start;
        width: 100%;
    }

    #type-d-screen #learn-image-wrap,
    #type-e-screen #learn-image-wrap {
        grid-column: 1;
        grid-row: 1;
    }

    /* #type-d-prompt-wrap / #type-e-sentence-wrap are unique to their
       own file already (confirmed), so no #type-d-screen/#type-e-screen
       prefix is needed for correct scoping — kept bare for consistency
       with how the rest of this file already references them. */
    #type-d-prompt-wrap   { grid-column: 1; grid-row: 2; }
    #type-e-sentence-wrap { grid-column: 1; grid-row: 2; }

    /* Fill the second grid column instead of the fixed var(--card-w)/
       var(--card-max-w) sizing those rules use for the single-column
       mobile layout — targeted by id (higher specificity than the
       .learn-options-grid class #type-e-options also carries) so this
       cannot leak onto picture_book_quiz.php's #type-pbq-options, which
       shares that same class.

       grid-row: 1 (not spanning into row 2) + align-self: end anchors
       the options block to the bottom of the IMAGE specifically, not
       the bottom of the whole left-hand stack (image + prompt/sentence
       text). This relies on the image (up to 450px, var(--card-max-h))
       always being taller than the options grid in row 1's shared
       track — true for both Type D's 2x2 image tiles (~279px) and
       Type E's text buttons (~150px) — so row 1's auto height is set
       by the image, and options simply sits flush against that track's
       bottom edge. Row 2 (prompt-wrap/sentence-wrap) is a separate
       track entirely and is untouched by this either way. */
    #type-d-options,
    #type-e-options {
        grid-column: 2;
        grid-row: 1;
        align-self: end;
        width: 100%;
        max-width: none;
    }
}

/* End-of-lesson screen (Type D / Type E / picture book quiz / Lesson
   Learn) — reuses #learn-results' layout pattern (centered column, gap
   24px) since there is no score to show (stateless), just a completion
   message and two actions. picture_book_quiz.php (#type-pbq-*) was
   missing from every rule below — it rendered with zero styling
   (default browser button/link chrome) until this was caught and
   fixed; lesson_learn.php (#lesson-learn-*) is added at the same time
   it's introduced, not after the fact. */
#type-d-complete,
#type-e-complete,
#type-pbq-complete,
#lesson-learn-complete {
    width: 100%;
    max-width: var(--card-max-w);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 24px;
    padding-top: 60px;
    text-align: center;
}

/* Shared end-of-session action row: retry + exit, icon-only with
   aria-label instead of visible text — a step further than
   .page-exit-nav's icon+label style, since a two-button pair reads
   clearly from the icons alone (rotate = retry, exit-bracket = leave)
   without needing a caption for either. Used by lesson_learn.php,
   lesson_type_d.php, lesson_type_e.php, picture_book_quiz.php's
   completion screens. Previously four page-specific ID lists styled as
   solid pill buttons with hardcoded English text ("Start Over"/
   "Try Again", "Back to Lesson(s)"/"Back to Book") — replaced with
   shared classes so the four pages can't drift from each other the way
   their old hardcoded text already had (singular "Lesson" on two pages,
   plural on two others). Retry keeps its own fa-rotate-right icon; exit
   reuses the same fa-right-from-bracket + fa-flip-horizontal as
   .page-exit-nav, for the same "one exit control app-wide" reason. */
.session-complete-actions {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 32px;
}
.complete-action-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: none;
    padding: 0;
    cursor: pointer;
    text-decoration: none;
}
.complete-action-btn:hover .exit-bracket { background: var(--blue); border-color: var(--blue); }
.complete-action-btn:hover .exit-bracket i { color: var(--white); }



/* Lesson card action links — added when lesson_steps introduced
   per-lesson extensions. The card itself is now a plain div (was an
   anchor); each action is its own link inside it. */
.lesson-card-link {
    width: 100%;
    text-align: center;
    padding: 8px 0;
    border-radius: 8px;
    font-size: 0.85rem;
    font-weight: bold;
    text-decoration: none;
    transition: background 0.15s, border-color 0.15s;
}

.lesson-link-learn {
    background: var(--blue);
    border: 1px solid var(--blue);
    color: var(--white);
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
}
.lesson-link-learn:hover { background: var(--blue-dark); border-color: var(--blue-dark); }

.lesson-link-browse {
    border: 1px solid var(--border);
    color: var(--navy);
}
.lesson-link-browse:hover { background: #f0f7ff; border-color: var(--blue); }

.lesson-link-extensions {
    position: relative;
    overflow: hidden;
    border: 1px solid var(--blue);
    color: var(--blue);
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
}
.lesson-link-extensions:hover { background: #f0f7ff; }

/* Accuracy bar — thin strip along the inside top edge of a Type D/E
   Extension button, showing the learner's most-recent-completed-session
   accuracy as a green/red split (green = correct share, red = the rest).
   Hidden by default; lesson_list.php's paintExtensionAccuracyBars() shows
   it and sets the gradient only when a stored score exists for that
   lesson+extension-type. overflow:hidden on .lesson-link-extensions
   clips this to the button's existing 8px border-radius, so no separate
   radius is needed here. New pattern — see lesson_list.php's own comment
   for why no existing accuracy/score-persistence pattern was available
   to copy instead. */
.ext-accuracy-bar {
    display: none;
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
    height: 4px;
}

/* "Test Yourself" button — picture_book_view.php, free-reading
   (entry=book) end-of-book offer only. Shape borrowed from
   #type-d-back-btn/#type-e-back-btn (padding/radius/flex layout), but
   colored like .lesson-link-extensions (blue border + text) since this
   is a forward-moving invitation to do something next, not a neutral
   "go back" action like those two buttons are. */
#test-yourself-wrap {
    width: 100%;
    text-align: center;
    margin-top: 16px;
}
#test-yourself-btn {
    padding: 14px 28px;
    background: var(--white);
    color: var(--blue);
    border: 2px solid var(--blue);
    border-radius: 8px;
    font-size: 1rem;
    font-weight: bold;
    text-decoration: none;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    transition: background 0.15s;
}
#test-yourself-btn:hover { background: #f0f7ff; }
/* ============================================================
   Global Header — top-padding override
   Deliberately placed at the END of this file. Several page-type
   rules above (.nav-page, .player-page, and any future one) set a
   padding-top intended to clear the OLD, absolutely-positioned
   .user-corner. Once a page adopts the in-flow .jr-header instead,
   that top padding is dead space with nothing left to clear.
   :has(.jr-header) has the SAME specificity as a plain class selector
   like .nav-page or .player-page (per CSS spec, :has() takes on the
   specificity of its argument — here .jr-header, one class), so this
   rule must come after every page-type rule in source order to win;
   moving it here (rather than using !important) keeps normal cascade
   rules intact and means no future page-type rule silently re-wins
   by accident just by being added lower in the file than an
   !important override would have been.

   This exact rule was accidentally dropped for one delivery round
   (the picture_book_view.php exit-icon fix) because that round's
   working copy of style.css was started from an older, pre-rollout
   snapshot instead of the corrected header-rollout output — caught
   when a screenshot showed a visible gap above the header on
   picture_book_view.php that didn't appear on mode_selection.php.
   Re-added here, verified as the only real difference between the
   stale and correct versions via a full diff before treating this as
   the complete fix. ============================================================ */
body:has(.jr-header) { padding-top: 0; }

/* .mode-card applied to a <button> (the Memory tile, which now opens
   the difficulty overlay instead of navigating directly) needs its
   native button chrome stripped — anchors and divs don't have this
   problem, which is why no equivalent reset existed before. Visual
   result is identical to the existing anchor-based mode-cards. */
button.mode-card {
    font-family: inherit;
    border: 2px solid transparent;
    cursor: pointer;
}

/* ============================================================
   GAME SELECTION — game_selection.php's difficulty overlay.
   No existing modal/overlay pattern anywhere in this codebase to reuse
   (checked directly before writing this) — genuinely new UI. Opened by
   clicking the Memory tile; closed by backdrop click or the X, never by
   picking a difficulty (that navigates away instead).
   ============================================================ */
#difficulty-overlay {
    position: fixed;
    top: 0; left: 0; width: 100%; height: 100%;
    z-index: 1000;
    align-items: center;
    justify-content: center;
}

#difficulty-overlay-backdrop {
    position: absolute;
    top: 0; left: 0; width: 100%; height: 100%;
    background: rgba(0, 0, 0, 0.45);
}

#difficulty-overlay-panel {
    position: relative;
    background: var(--white);
    border-radius: 16px;
    padding: 32px 28px;
    max-width: 360px;
    width: 90vw;
    box-shadow: 0 10px 40px rgba(0,0,0,0.25);
    text-align: center;
}

#difficulty-overlay-close {
    position: absolute;
    top: 12px; right: 12px;
    background: none;
    border: none;
    font-size: 1.2rem;
    color: var(--grey-text);
    cursor: pointer;
    padding: 6px;
}
#difficulty-overlay-close:hover { color: #000; }

#difficulty-overlay-title { margin: 0 0 20px; font-size: 1.3rem; }

#difficulty-overlay-options {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.difficulty-option-btn {
    padding: 14px;
    border-radius: 10px;
    border: 2px solid var(--grey-light);
    background: var(--white);
    font-size: 1.1rem;
    font-weight: 600;
    cursor: pointer;
    transition: all 0.15s;
}
.difficulty-option-btn:hover {
    border-color: var(--blue);
    background: var(--blue);
    color: var(--white);
}

/* ============================================================
   MEMORY GAME — memory_game.php / script_memory_game.js

   Per-cell flip mechanic reuses the SAME proven transform approach as
   .flip-card-inner (rotateY + backface-visibility: hidden, two stacked
   absolutely-positioned faces) — not a new mechanic invented from
   scratch. Deliberately DIFFERENT transition duration (0.4s, not the
   single-hero-card's 0.8s): a grid of many small cards flipping reads
   better snappier than one large card's slower flip — stated deviation,
   not a copy-paste oversight (Standards Rule 1/8).
============================================================ */

#memory-grid-wrap {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 16px;
    padding: 16px;
}

#memory-grid {
    display: grid;
    gap: 10px;
    width: var(--card-w);
    max-width: var(--card-max-w);
    /* Matches #learn-options' exact width pattern (see style.css's own
       Type D comment: "same outer width as #learn-options
       (var(--card-w)/var(--card-max-w))") — this grid is image tiles,
       same situation Type D's comment already describes, not a new
       case. Previously hardcoded width:100%/max-width:600px, which
       never picked up the responsive 90vw basis var(--card-w) actually
       provides — that's why the grid read as cramped rather than
       matching the main flashcard's real footprint. */
}

/* 4 rows x 3 columns = 12 cards (6 pairs) */
.memory-grid-easy { grid-template-columns: repeat(3, 1fr); grid-template-rows: repeat(4, 1fr); }
/* 4 rows x 4 columns = 16 cards (8 pairs) — unchanged, already square */
.memory-grid-medium { grid-template-columns: repeat(4, 1fr); grid-template-rows: repeat(4, 1fr); }
/* 5 rows x 4 columns = 20 cards (10 pairs) */
.memory-grid-hard { grid-template-columns: repeat(4, 1fr); grid-template-rows: repeat(5, 1fr); }

.memory-card {
    aspect-ratio: 4 / 3; /* landscape, per product owner — was 3:4 (portrait) */
    perspective: 1000px;
    cursor: pointer;
}

.memory-card-matched { cursor: default; opacity: 0.5; transition: opacity 0.3s; }
/* opacity 0.5 + transition is new this round — per product owner, matched
   cards should fade to 50% AFTER the success pulse below finishes, not
   instantly; the transition gives that fade its own brief, visible motion
   too, rather than an abrupt opacity snap. */

@keyframes memory-success-pulse {
    0%   { transform: scale(1);    box-shadow: 0 0 0 0 rgba(40, 167, 69, 0.6); }
    40%  { transform: scale(1.06); box-shadow: 0 0 0 8px rgba(40, 167, 69, 0.35); }
    100% { transform: scale(1);    box-shadow: 0 0 0 0 rgba(40, 167, 69, 0); }
}

.memory-card-success {
    animation: memory-success-pulse 0.5s ease-out;
    /* Duration MUST match the 500ms setTimeout in checkForMatch() that
       removes this class and applies memory-card-matched — the two are
       sequenced, not simultaneous, and need to agree on timing or one
       will visibly cut the other off. */
}
.memory-card-success .memory-card-back { border-color: var(--green); }

.memory-card-inner {
    position: relative;
    width: 100%;
    height: 100%;
    transition: transform 0.4s;
    transform-style: preserve-3d;
}

.memory-card-flipped .memory-card-inner { transform: rotateY(180deg); }

.memory-card-front,
.memory-card-back {
    position: absolute;
    top: 0; left: 0; width: 100%; height: 100%;
    backface-visibility: hidden;
    border-radius: 8px;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
}

/* Face-down generic card back — same blue/white palette as the rest of
   the app (var(--blue)), not a new color introduced for this page. */
.memory-card-front {
    background: var(--blue);
    border: 2px solid var(--blue-dark);
}
.memory-card-front::after {
    content: '?';
    color: #fff;
    font-size: 1.8rem;
    font-weight: 700;
    opacity: 0.85;
}

.memory-card-back {
    background: #fff;
    border: 2px solid #ccc;
    transform: rotateY(180deg);
}

.memory-card-back img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.memory-word-content {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    padding: 4px;
    text-align: center;
}

.memory-word-main { font-size: 0.95rem; font-weight: 600; }
.memory-word-phonetic { font-size: 0.75rem; color: var(--grey-text); }

.memory-card-matched .memory-card-back {
    border-color: var(--green);
}

#memory-win-overlay {
    position: fixed;
    top: 0; left: 0; width: 100%; height: 100%;
    background: rgba(0, 0, 0, 0.35);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 28px;
    z-index: 999;
    /* pointer-events:none REMOVED this round — this overlay now contains
       real buttons (Play Again / Exit), so it can no longer be click-
       through. Previously correct when this was icon-only ("celebration
       only — never blocks re-viewing the grid underneath"); that reasoning
       no longer applies now that there's something to actually click. */
}

#memory-win-icon {
    font-size: 6rem;
    /* Brighter, more saturated green than var(--green) (#28a745) — that
       value read as muted against this overlay's semi-transparent dark
       backdrop layered over the busy grid behind it. This is a
       deliberately different, more vivid green specifically for this
       celebratory moment, not a change to the shared --green variable
       used elsewhere (which stays as-is for its existing, subtler uses). */
    color: #2ecc71;
    filter: drop-shadow(0 4px 16px rgba(0,0,0,0.5));
}

#memory-win-actions {
    display: flex;
    align-items: center;
    gap: 32px;
}

/* Play Again matches Exit's icon-circle pattern, including the same
   vertical icon-over-label stacking (#memory-exit-link has this via
   flex/column; this rule was missing it entirely until now, which is
   why the "Again" label wasn't appearing under its own icon — it was
   just sitting in the button's default inline/block flow with no
   stacking applied at all). */
#memory-play-again-btn {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    background: none;
    border: none;
    padding: 0;
    cursor: pointer;
}
#memory-play-again-label { font-size: 0.85rem; font-weight: 600; color: var(--white); }

/* Both icon circles DOUBLED in size for this overlay specifically — NOT
   a change to the shared --icon-btn-size variable (52px), which every
   .page-exit-nav instance (picture_book_view.php, lesson.php,
   category_selection.php, flashcard_selection.php) also depends on.
   Scoped here via the parent ids so those other pages are completely
   unaffected. */
#memory-win-actions .exit-bracket {
    width: calc(var(--icon-btn-size) * 2);
    height: calc(var(--icon-btn-size) * 2);
}
#memory-win-actions .exit-bracket i { font-size: 2.2rem; } /* was 1.1rem on the shared rule — doubled to match */

/* #memory-exit-link reuses .page-exit-nav's own layout pattern
   (vertical icon-over-label, same as picture_book_view.php etc.) rather
   than a new layout — only the link's own id differs, since it points to
   game_selection.php instead of picture_book_list.php. */
#memory-exit-link {
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    text-decoration: none;
    color: var(--white);
}
#memory-exit-link:hover .exit-bracket { background: var(--blue); border-color: var(--blue); }
#memory-exit-link:hover .exit-bracket i { color: var(--white); }
#memory-exit-link:hover #memory-exit-label { color: var(--blue); }
#memory-exit-label { font-size: 0.85rem; font-weight: 600; color: var(--white); }

#memory-play-again-btn:hover .exit-bracket { background: var(--blue); border-color: var(--blue); }
#memory-play-again-btn:hover .exit-bracket i { color: var(--white); }
#memory-play-again-btn:hover #memory-play-again-label { color: var(--blue); }

/* ============================================================
   22. Lesson Learn — Simultaneous Image+Word (lesson_learn.php)
   Image container reuses #learn-image-wrap/#learn-image verbatim — no
   new rule needed, see section 7/18's existing rules for those ids.
   Word block below is new: unlike Type D's #type-d-prompt-text (a small
   prompt sitting above an option grid), the word here IS the screen's
   main content, so it's sized larger — between Type D's 1.3rem prompt
   and the flip-card's 2.4rem #word p, since this is read at a glance
   alongside the image rather than as the sole focus of a flipped card.
   TTS previously lived inside this wrap, next to the text (Type D/E's
   placement) — moved into #lesson-learn-btn-row instead, matching
   lesson.php's #button-container convention. #lesson-learn-word-wrap now
   holds only #lesson-learn-word-lines, so the row/gap layout it used to
   need for two side-by-side children is gone too. */
#lesson-learn-word-wrap {
    width: var(--card-w);
    max-width: var(--card-max-w);
}

#lesson-learn-word-lines {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    text-align: center;
}

#lesson-learn-word-lines p {
    margin: 0;
    color: var(--navy);
    font-family: var(--font-stack);
    font-weight: bold;
    font-size: 1.8rem;
    line-height: 1.3;
}

/* Phonetic lines (2nd+ <p>) read lighter than the native-script line
   (1st <p>) — same visual hierarchy as #browse-word p:not(:first-child)'s
   relationship to #browse-word p (section 22b), adapted to navy-on-white
   instead of white-on-dark since this block sits on the page background,
   not inside a dark flip-card-back panel. */
#lesson-learn-word-lines p:not(:first-child) {
    font-weight: 400;
    font-size: 1.1rem;
    opacity: 0.75;
}

/* Manual nav row — reuses .icon-btn directly (same circular button base
   as every other icon button in the app), just a centered flex row,
   matching lesson.php's #button-container layout INCLUDING TTS living
   in this row (previous -> start-over -> TTS -> next) — this page has
   no mark-learned button (stateless, see file header comment), so it's
   the only difference from lesson.php's five-button row.
   No margin-top here: #learn-card-wrap (the parent) is already a flex
   column with gap:16px between #learn-image-wrap / #lesson-learn-word-wrap
   / this row, so an extra margin-top here would just double up on that
   gap — which is exactly the bug that made this page's internal spacing
   inconsistent (28px/32px instead of a uniform 16px) before this fix. */
#lesson-learn-btn-row {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 16px;
}

/* ============================================================
   22b. flashcard_learn.php Learn-mode Browse phase — simultaneous
   image+word, same pattern as section 22 (lesson_learn.php) above,
   moved off the flip-card (.flip-card-front/back) it used to share
   with flashcard.php's browse mode. #browse-image-wrap/#browse-image
   are byte-for-byte the same rules as #learn-image-wrap/#learn-image
   (section 7/18), just a distinct id since #learn-image-wrap is
   already used by this same page's #learn-quiz screen. #browse-word-wrap/
   #browse-word are byte-for-byte the same rules as #lesson-learn-word-wrap/
   #lesson-learn-word-lines (section 22) for the identical reason. */
#browse-image-wrap {
    width: var(--card-w);
    height: var(--card-h);
    max-width: var(--card-max-w);
    max-height: var(--card-max-h);
    border: 2px solid #ccc;
    border-radius: var(--card-radius);
    padding: var(--card-img-padding);
    overflow: hidden;
    box-shadow: 0 4px 8px rgba(0,0,0,0.1);
    background: var(--white);
}

#browse-word-wrap {
    width: var(--card-w);
    max-width: var(--card-max-w);
}

#browse-word {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    text-align: center;
}

#browse-word p {
    margin: 0;
    color: var(--navy);
    font-family: var(--font-stack);
    font-weight: bold;
    font-size: 1.8rem;
    line-height: 1.3;
}

#browse-word p:not(:first-child) {
    font-weight: 400;
    font-size: 1.1rem;
    opacity: 0.75;
}

/* End-of-lesson screen styling lives in section 20/21's shared
   comma-group (#type-d-complete, #type-e-complete, #type-pbq-complete,
   #lesson-learn-complete and their *-actions/*-retry-btn/*-back-btn
   siblings) — NOT duplicated here. See that block for the actual rules.
   This avoids exactly the trap section 12 of the addendum describes:
   adding a same-shape rule in a new place instead of extending the one
   canonical group, which would silently diverge the next time that
   group is edited.
   ============================================================ */