/* =============================================================================
 * Cara Cartolina — Design System Foundation
 * -----------------------------------------------------------------------------
 * Phase 1a (vision: photo-ux-redesign-20260407)
 *
 * This file is the design vocabulary for the entire app: tokens, font loads,
 * universal resets, and the body baseline. It is intentionally minimal —
 * NO component styling lives here. Subsequent clusters (C/D/E/F/G) layer the
 * visual treatment back on, every rule referencing var(--token-name).
 *
 * Aesthetic: Organic Brutalism. Warm parchment, square corners, tonal layering
 * instead of borders, Noto Serif throughout. See vision §"Stitch design
 * references" for the full rationale.
 *
 * Constraints honored:
 *   CC-1  vanilla CSS only, no build step, no framework
 *   CC-2  every value outside :root references var(--token)
 *   CC-3  border-radius: 0 reset is SCOPED (not bare global *)
 *   CC-4  Material Symbols Outlined loaded for later phases
 * ============================================================================= */

/* === Font loads — self-hosted (was: Google Fonts @import). The @import chain
 * (styles.css → font CSS → woff2) rendered text in a fallback first, then
 * reflowed on font arrival (~52ms), which was the page's entire 0.06 CLS; it
 * also pulled 4 external requests from fonts.googleapis/gstatic on every load.
 * Self-hosting removes both. Noto Serif is a variable font — one file per
 * style/subset spans the 400-700 axis (font-weight range below); latin +
 * latin-ext cover it/pt/en/lt. Material Symbols is the full variable icon font,
 * NOT subset: a subset would need a build step (CC-1) and would silently tofu
 * any icon a future dev adds. index.html preloads notoserif-normal-latin so the
 * body font is present before first paint. Re-run scripts/fetch_fonts.py to
 * refresh the woff2 from Google and regenerate these blocks. === */
@font-face {
  font-family: 'Noto Serif';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('/static/fonts/notoserif-italic-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Noto Serif';
  font-style: italic;
  font-weight: 400;
  font-display: swap;
  src: url('/static/fonts/notoserif-italic-latinext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Noto Serif';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/static/fonts/notoserif-normal-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Noto Serif';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('/static/fonts/notoserif-normal-latinext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Material Symbols Outlined';
  font-style: normal;
  font-weight: 400;
  font-display: block;
  src: url('/static/fonts/material-symbols-outlined.woff2') format('woff2');
}
/* The Google @import shipped this helper class alongside the @font-face —
 * without it no element ever requests the icon family and every icon renders
 * as its raw ligature name in body serif. Canonical Google block, plus the
 * vertical-align that Google omits (inline icons baseline-align next to text;
 * flex-rendered icons are unaffected — flex centers via align-items). */
.material-symbols-outlined {
  font-family: 'Material Symbols Outlined';
  font-weight: normal;
  font-style: normal;
  font-size: 24px;
  line-height: 1;
  letter-spacing: normal;
  text-transform: none;
  display: inline-block;
  white-space: nowrap;
  word-wrap: normal;
  direction: ltr;
  vertical-align: middle;
  -webkit-font-feature-settings: 'liga';
  -webkit-font-smoothing: antialiased;
}

/* === Tokens === */
:root {
    /* Surfaces — warm parchment palette, light to dark */
    --background:                #FDF9F1;
    --surface:                   #FDF9F1;
    --surface-container-lowest:  #FFFFFF;  /* inbound bubble */
    --surface-container-low:     #F7F3EB;
    --surface-container:         #F2EDE5;
    --surface-container-high:    #ECE8E0;  /* left-rail bg */
    --surface-container-highest: #E6E2DA;  /* vocab sidebar bg */
    --surface-variant:           #E6E2DA;
    --surface-dim:               #DDDAD2;  /* hover on light containers */

    /* Primary — Dusty Grape ink */
    --primary:                   #615A77;
    --primary-container:         #7A7291;  /* outbound bubble, gradient end */
    --on-primary:                #FFFFFF;

    /* Text on surfaces */
    --on-surface:                #1C1C17;  /* body — ink on paper, never pure black */
    --on-surface-variant:        #48454D;  /* subdued body */

    /* Outlines */
    --outline:                   #79767E;  /* metadata, captions */
    --outline-variant:           #CAC5CD;  /* "ghost border" 15% fallback */

    /* Type and shape */
    --font-serif:                'Noto Serif', serif;
    --radius:                    0;        /* global. no exceptions. */
}

/* === Universal reset (global half) =========================================
 * Margin/padding/box-sizing/font-family are universally safe to reset on every
 * element (matches Eric Meyer / normalize.css / Tailwind preflight). The
 * legacy stylesheet at line 14–19 already did this globally.
 * ========================================================================= */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    font-family: var(--font-serif);
}

/* === Universal reset (scoped half) =========================================
 * border-radius: 0 is scoped to our top-level wrappers (#app-container and
 * .welcome-screen) per Decision 12 + CC-3. A bare global * { border-radius: 0 }
 * would clobber browser-default chrome we don't own (native <select> dropdowns,
 * scrollbar pseudo-elements, focus rings). Scoping reaches every element we
 * render without touching browser chrome.
 * ========================================================================= */
#app-container *,
.welcome-screen * {
    border-radius: var(--radius);
}

/* Defensive rule (round 19 P62): #floating-translate-btn lives inside
 * #app-container today (frontend/index.html:33), so the scoped reset above
 * already covers it. But if a future phase moves the button into document.body
 * to escape z-index/clipping, the scoped reset would no longer reach it and
 * native browser-default rounded corners would return as a visible regression.
 * This rule is a no-op today, safety later. */
#floating-translate-btn {
    border-radius: var(--radius);
}

/* === Body baseline =========================================================
 * Solid parchment background (no more bg-cartolina.jpg — that file stays on
 * disk per Decision 5 but is no longer referenced). Ink-on-paper text color.
 * height/overflow preserved from the legacy layout so the existing flex
 * shell in #app-container still fills the viewport.
 * ========================================================================= */
html,
body {
    height: 100%;
    overflow: hidden;
    background-color: var(--background);
    color: var(--on-surface);
    font-family: var(--font-serif);
}

/* === Typography utility classes (Phase 7c — Cluster C) =====================
 * Stitch "Scholarly Monolith" type scale. Deferred from Phase 1a per its
 * IMPLEMENTATION_NOTES; landed here now that 7c gives them visible context.
 *
 * Reusable across surfaces: 7c uses .body via direct rules on chat bubbles
 * (className is set by JS, can't retrofit class="body" under Constraint 20),
 * 8a will use .headline for gallery section headers, 11a for dictionary
 * entries, 13b for welcome page copy. Numeric values from
 * stitch/learning_interface_vanilla_css/code.html lines 140–268.
 * ========================================================================= */
.display {
    font-size: 2.25rem;
    line-height: 1.15;
    font-weight: 700;
    letter-spacing: -0.025em;
}

.headline {
    font-size: 1.875rem;
    line-height: 1.25;
    font-weight: 700;
    letter-spacing: -0.015em;
}

.body {
    font-size: 1.125rem;
    line-height: 1.625;
    font-weight: 400;
    letter-spacing: 0;
}

.label {
    font-size: 0.75rem;
    line-height: 1.4;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.1em;
}

/* ---------------------------------------------------------------------------
 * Component styling for gallery grid, welcome page is still ABSENT —
 * those clusters (D/E/F/G) restore them. Phase 7a lays down the structural
 * shell, 7b adds routing + responsive media queries, 7c (this phase) lands
 * the chat bubble layer + typography polish.
 * ---------------------------------------------------------------------------
 */

/* =============================================================================
 * Cluster C — Phase 7a structural shell
 * -----------------------------------------------------------------------------
 * Stitch Organic Brutalism: 80px fixed left rail, main canvas reserves space
 * via margin-left/right, vocab sidebar fixed on the right (40px collapsed
 * baseline, 256px expanded), chat header with persona name + user menu, mic
 * relocated into the chat input footer.
 *
 * All values reference var(--token) per CC-2. Border-radius: 0 is inherited
 * from Phase 1a's scoped #app-container * reset (no per-element radius rules).
 * ============================================================================= */

/* === App container shell ============================================== */
#app-container {
    height: 100%;
    overflow: hidden;
}

#main-layout {
    height: 100%;
    position: relative;
}

/* === Left rail ======================================================== */
.side-rail {
    position: fixed;
    left: 0;
    top: 0;
    height: 100%;
    width: 80px;
    background-color: var(--surface-container-high);
    display: flex;
    flex-direction: column;
    align-items: center;
    padding: 32px 0;
    z-index: 50;
}

.rail-nav {
    display: flex;
    flex-direction: column;
    width: 100%;
    gap: 24px;
}

.rail-footer {
    margin-top: auto;
    width: 100%;
}

.rail-icon {
    width: 100%;
    padding: 12px 0;
    background: transparent;
    border: none;
    border-left: 4px solid transparent;
    cursor: pointer;
    color: var(--on-surface);
    opacity: 0.7;
    display: flex;
    align-items: center;
    justify-content: center;
    font-family: var(--font-serif);
    transition: background-color 0.2s, transform 0.1s, opacity 0.2s;
}

.rail-icon .material-symbols-outlined {
    font-size: 28px;
}

.rail-icon:hover {
    background-color: var(--surface-dim);
    opacity: 1;
}

.rail-icon:active {
    transform: scale(0.95);
}

.rail-icon.active {
    opacity: 1;
    color: var(--primary-container);
    background-color: var(--surface-dim);
    border-left-color: var(--primary-container);
}

/* === Main canvas (chat surface) ======================================= */
.main-canvas {
    margin-left: 80px;
    /* Baseline = vocab sidebar collapsed (40px). Expanded variant below. */
    margin-right: 40px;
    height: 100%;
    background-color: var(--surface);
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

/* When the vocab sidebar is expanded, the canvas reserves the full 256px.
 * :has() supported in Chrome 105+, Safari 15.4+, Firefox 121+ — verified in
 * the smoke checklist (§9 step 8). */
#app-container:has(#vocab-sidebar:not(.collapsed)) .main-canvas {
    margin-right: 256px;
}

/* === Chat header (persona titles + user menu) =========================
 * Phase 7c D6: typography polish to match Stitch reference (code.html
 * lines 140–168). Three numeric overrides on top of 7a's structural rules:
 *   padding 24px 32px → 32px (uniform)
 *   align-items center → flex-end (titles + avatar baseline-aligned)
 *   h1 1.875rem → 2.25rem; letter-spacing -0.01em → -0.025em
 */
.chat-header {
    padding: 12px 24px;
    background-color: transparent;
    display: flex;
    justify-content: flex-end;
    align-items: center;
    flex-shrink: 0;
}

/* User menu — preserve inline backgroundImage written by /api/auth/me handler.
 * Do NOT set background-image here (Gotcha 5 / scout brief item 5). */
.chat-header #user-menu-wrapper {
    position: relative;
}

.chat-header #user-avatar-btn {
    width: 44px;
    height: 44px;
    background-color: var(--surface-container-high);
    background-size: cover;
    background-position: center;
    border: 2px solid var(--outline-variant);
    color: var(--on-surface);
    font-family: var(--font-serif);
    font-size: 1.05rem;
    font-weight: 600;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: border-color 0.15s;
}

.chat-header #user-avatar-btn:hover {
    border-color: var(--primary-container);
}

.chat-header #user-menu {
    position: absolute;
    right: 0;
    top: calc(100% + 8px);
    min-width: 200px;
    padding: 16px;
    background-color: var(--surface-container-lowest);
    box-shadow: 0 4px 12px rgba(28, 28, 23, 0.1);
    z-index: 100;
}

.chat-header #user-menu-name {
    color: var(--on-surface);
    font-weight: 600;
    margin-bottom: 12px;
    padding-bottom: 12px;
    border-bottom: 1px solid var(--outline-variant);
}

.chat-header #user-menu-logout {
    width: 100%;
    padding: 10px 16px;
    background-color: var(--primary);
    color: var(--on-primary);
    border: none;
    cursor: pointer;
    font-family: var(--font-serif);
    font-size: 0.85rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    transition: background-color 0.15s;
}

.chat-header #user-menu-logout:hover {
    background-color: var(--primary-container);
}

/* === Chat history fills the space between header and input footer =====
 * Phase 7c: gap added so message blocks breathe (Stitch uses 48px on the
 * .chat-area; 40px reads slightly tighter against our narrower max-width
 * but still gives the bubbles room — see Implementer's Latitude). */
#chat-history {
    flex: 1;
    overflow-y: auto;
    padding: 32px 48px;
    display: flex;
    flex-direction: column;
    gap: 40px;
}

/* === Chat input footer (mic relocated here from sticky toolbar) ======= */
#chat-input-zone {
    padding: 24px 32px;
    background-color: var(--surface-container-high);
    display: flex;
    align-items: center;
    gap: 16px;
    flex-shrink: 0;
}

#chat-input-zone #chat-input {
    flex: 1;
    padding: 16px 24px;
    background-color: var(--surface-container-lowest);
    border: none;
    outline: none;
    color: var(--on-surface);
    font-family: var(--font-serif);
    font-size: 1.05rem;
    resize: none;
    overflow-y: hidden;
    min-height: 56px;
    max-height: 160px;
    line-height: 1.4;
}

#chat-input-zone #chat-input::placeholder {
    color: var(--outline);
    font-style: italic;
}

#chat-input-zone #send-btn {
    padding: 16px 32px;
    background-color: var(--primary);
    color: var(--on-primary);
    border: none;
    cursor: pointer;
    font-family: var(--font-serif);
    font-size: 0.9rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.1em;
    transition: background-color 0.15s;
}

#chat-input-zone #send-btn:hover {
    background-color: var(--primary-container);
}

/* Mic button: square icon button matching the send button visual weight */
#chat-input-zone #mic-btn {
    width: 56px;
    height: 56px;
    padding: 0;
    background-color: var(--surface-container-lowest);
    color: var(--on-surface);
    border: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: background-color 0.15s, color 0.15s;
}

#chat-input-zone #mic-btn .material-symbols-outlined {
    font-size: 26px;
}

#chat-input-zone #mic-btn:hover {
    background-color: var(--surface-dim);
}

/* Mic state classes (Gotcha 3 / CC-16): voice_recording.js (6a/6b) + boot.js (6c) mutate
 * classList to reflect live-session state. Without these rules voice mode still WORKS but looks
 * broken. 6c (KD-2, DESIGN.md static liveness — the vision's no-wave ruling): the captions are
 * the primary liveness signal, so the mic states are calm + STATIC. The gentle pulse is reserved
 * for .learner-floor, where it is MEANINGFUL "your voice is being captured" feedback, not ambient
 * decoration over the whole session. Legacy .processing/.playing kept (no current writer). */
#chat-input-zone #mic-btn.recording {
    background-color: var(--primary-container);
    color: var(--on-primary);
}

#chat-input-zone #mic-btn.processing {
    background-color: var(--surface-dim);
    color: var(--on-surface-variant);
    opacity: 0.65;
}

#chat-input-zone #mic-btn.playing {
    background-color: var(--primary);
    color: var(--on-primary);
}

/* Learner holds the floor → the mic is hot (forwarding). A gentle pulse here is functional
 * feedback ("you're being heard"). Stronger fill than the static .recording session state. */
#chat-input-zone #mic-btn.learner-floor {
    background-color: var(--primary);
    color: var(--on-primary);
    animation: cartolina-mic-pulse 1.4s ease-in-out infinite;
}

/* Muted → always-hot-mic forwarding paused. Dimmed + steady (no pulse). Placed AFTER
 * .learner-floor (equal specificity) so it wins when both are present (muted-with-floor). */
#chat-input-zone #mic-btn.muted {
    background-color: var(--surface-dim);
    color: var(--on-surface-variant);
    opacity: 0.65;
    animation: none;
}

/* Exhausted (6c) → entitled but the daily Live budget is spent (case-iii / mid-session drain).
 * Shown-but-disabled: subdued + not-allowed cursor. The warm explanation rides the title
 * attribute (t('voice.exhausted_hover')); window._voiceDisabled blocks the actual open. */
#chat-input-zone #mic-btn.exhausted {
    background-color: var(--surface-container-low);
    color: var(--on-surface-variant);
    opacity: 0.55;
    cursor: not-allowed;
}

@keyframes cartolina-mic-pulse {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.55; }
}

/* Voice hint span: hidden at REST (Decision 4). The DOM node MUST survive — voice_recording.js
 * writes to its .textContent / .classList; deleting the span would crash those call sites.
 * CSS display: none lets the rest-state writes no-op. 6c re-scopes the reveal: it is shown ONLY
 * during an active live session via #voice-hint.active. Revealing it unconditionally would paint
 * a cue line for every user — including non-voice / case-i-hidden ones — cluttering the input zone. */
#voice-hint {
    display: none;
}

/* 6c: the static liveness cue (KD-2, DESIGN.md). The .active class is toggled by
 * setActiveUi/setIdleUi/refreshLivenessCue in voice_recording.js. An italic Noto Serif line in
 * the Dusty-Grape `primary` accent — the minimal typographic reinforcement of the captions (the
 * captions are the PRIMARY liveness signal, vision §247). It reinforces, never occludes; no flex
 * container wraps the text (§7 display-model audit). */
#voice-hint.active {
    display: inline;
    font-family: var(--font-serif);
    font-style: italic;
    font-size: 0.9rem;
    color: var(--primary);
}

/* === Vocabulary sidebar (right, fixed; 40px collapsed baseline) ======= */
#vocab-sidebar {
    position: fixed;
    right: 0;
    top: 0;
    height: 100%;
    width: 256px;
    background-color: var(--surface-container-highest);
    display: flex;
    flex-direction: column;
    overflow: hidden;
    z-index: 40;
    transition: width 0.2s ease;
}

#vocab-sidebar.collapsed {
    width: 40px;
}

#vocab-toggle-btn {
    width: 100%;
    padding: 16px 0;
    background-color: var(--surface-container-high);
    color: var(--on-surface);
    border: none;
    cursor: pointer;
    font-family: var(--font-serif);
    font-size: 1.1rem;
    flex-shrink: 0;
    transition: background-color 0.15s;
}

#vocab-toggle-btn:hover {
    background-color: var(--surface-dim);
}

#vocab-sidebar.collapsed #vocab-toggle-btn {
    display: none;
}

/* Collapsed-state vertical label fills the strip and stays clickable. */
#vocab-collapsed-label {
    display: none;
}

#vocab-sidebar.collapsed #vocab-collapsed-label {
    display: flex;
    flex: 1;
    align-items: center;
    justify-content: center;
    writing-mode: vertical-rl;
    transform: rotate(180deg);
    font-family: var(--font-serif);
    font-size: 0.7rem;
    text-transform: uppercase;
    letter-spacing: 0.25em;
    color: var(--on-surface-variant);
    cursor: pointer;
    user-select: none;
}

#vocab-content {
    flex: 1;
    padding: 24px;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
}

#vocab-sidebar.collapsed #vocab-content {
    display: none;
}

#vocab-content .sidebar-header {
    margin-bottom: 32px;
}

#vocab-content .sidebar-header h2 {
    font-size: 1.25rem;
    font-weight: 700;
    color: var(--on-surface);
}

#vocab-list {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

#vocab-list .vocab-play {
    background: none;
    border: none;
    padding: 0;
    cursor: pointer;
    color: var(--on-surface-variant);
    font-size: 14px;
    line-height: 1;
    transition: color 0.12s;
}

#vocab-list .vocab-play:hover {
    color: var(--primary);
}

/* === Floating translate button (restyled, NOT moved — Decision 5) =====
 * NOTE: chat.js sets #floating-translate-btn style.display = 'block' inline, which
 * overrides any display value set in CSS. Don't bother with flex layout here — the
 * children render via inline flow with whitespace as separator, and the
 * .material-symbols-outlined vertical-align rule above handles alignment. */
#floating-translate-btn {
    padding: 10px 16px;
    background-color: var(--primary-container);
    color: var(--on-primary);
    border: none;
    cursor: pointer;
    font-family: var(--font-serif);
    font-size: 0.9rem;
    font-weight: 600;
    box-shadow: 0 2px 8px rgba(28, 28, 23, 0.18);
    transition: background-color 0.15s;
}

#floating-translate-btn:hover {
    background-color: var(--primary);
}

#floating-translate-btn .material-symbols-outlined {
    font-size: 20px;
    margin-right: 4px;
}

/* === Settings stub pane (Phase 7b — Cluster C) ========================== */
/* Phase 11a split this rule out of the old `#settings-view, #dictionary-view`
 * shared block — the Dictionary view now owns a two-column grid layout, so the
 * shared padding would collide with the grid. Settings stays on the stub pane. */
#settings-view {
    flex: 1;
    flex-direction: column;
    padding: 32px 48px;
    gap: 0;
    font-family: var(--font-serif);
    color: var(--on-surface);
    background-color: var(--surface);
    overflow-y: auto;
}

.settings-stub-title {
    font-family: var(--font-serif);
    font-size: 32px;
    margin: 0 0 24px 0;
    color: var(--on-surface);
}

.settings-stub-list {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 8px 24px;
    max-width: 480px;
    margin: 0 0 32px 0;
}

.settings-stub-list dt {
    font-weight: 600;
    opacity: 0.7;
}

.settings-stub-list dd {
    margin: 0;
}

.settings-stub-note {
    opacity: 0.6;
    font-size: 15px;
}

/* === Curiosita view (Phase r3 — remediation R6) ========================= */
#curiosita-view {
    flex: 1;
    flex-direction: column;
    padding: 32px 48px;
    overflow-y: auto;
    background-color: var(--surface);
    color: var(--on-surface);
}

#curiosita-list {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.curiosita-card {
    padding: 20px 24px;
    background-color: var(--surface-container-low);
    font-size: 15px;
    line-height: 1.6;
    animation: curiosita-fade-in 0.3s ease both;
}

.curiosita-card strong {
    color: var(--primary);
}

@keyframes curiosita-fade-in {
    from { opacity: 0; transform: translateY(6px); }
    to   { opacity: 1; transform: translateY(0); }
}

/* === Responsive breakpoints (Phase 7b — Cluster C) ====================== */
/* 768px — Egle's iPad Mini landscape and similar narrow tablets.
 * Reclaim the canvas margin even when the vocab sidebar is expanded. */
@media (max-width: 768px) {
    #app-container:has(#vocab-sidebar:not(.collapsed)) .main-canvas {
        margin-right: 40px;
    }
}

/* 480px — small phones / iPad Mini portrait. Shrink the rail so the canvas
 * keeps usable width. */
@media (max-width: 480px) {
    .side-rail {
        width: 60px;
        padding: 24px 0;
    }
    .main-canvas {
        margin-left: 60px;
    }
    .rail-icon {
        padding: 10px 0;
    }
    .rail-icon .material-symbols-outlined {
        font-size: 24px;
    }
    #settings-view {
        padding: 24px 20px;
    }
    #curiosita-view {
        padding: 24px 20px;
    }
}

/* =============================================================================
 * Cluster C — Phase 7c chat bubble layer
 * -----------------------------------------------------------------------------
 * Stitch reference: stitch/learning_interface_vanilla_css/code.html lines
 * 169–228. Mapping (D1 — JS class names preserved, Stitch's class names are
 * NOT used because Constraint 20 forbids touching appendMessage's className
 * assignment beyond the three-attribute carve-out):
 *
 *   Stitch .message-block       → our .message     (flex-column wrapper)
 *   Stitch .bubble-inbound       → our .ai-message  (white parchment + ink border-left)
 *   Stitch .bubble-outbound      → our .user-message (Dusty Grape fill, on-primary text)
 *   Stitch .message-text         → direct rules on .message (single-element bubbles)
 *
 * Type scale: matches the .body utility above (1.125rem / 1.625 line-height).
 * Border-radius: inherited 0 from #app-container * scoped reset (CC-3).
 * ============================================================================= */
.message {
    display: flex;
    flex-direction: column;
    max-width: 42rem;
    padding: 32px;
    font-family: var(--font-serif);
    font-size: 1.125rem;
    line-height: 1.625;
    /* Word-break safety: long Italian words / URLs shouldn't blow the bubble. */
    overflow-wrap: break-word;
}

.message.ai-message {
    align-self: flex-start;
    background-color: var(--surface-container-lowest);
    color: var(--on-surface);
    border-left: 8px solid var(--primary-container);
}

.message.user-message {
    align-self: flex-end;
    background-color: var(--primary-container);
    color: var(--on-primary);
}

/* Live interim captions: transient, per-role (voice_recording.js R10). Muted italics —
 * visibly NOT a committed bubble — aligned to the same side as the role's bubbles so the
 * caption "becomes" its bubble where the eye already is. */
.live-interim {
    max-width: 42rem;
    padding: 8px 32px;
    font-family: var(--font-serif);
    font-size: 1.125rem;
    line-height: 1.625;
    font-style: italic;
    color: var(--on-surface-variant);
    overflow-wrap: break-word;
}

.live-interim-user {
    align-self: flex-end;
    text-align: right;
}

.live-interim-ai {
    align-self: flex-start;
}

/* === Voice-mode wave overlay (voice_wave.js) ================================
 * Ambient band across the bottom of the screen: pointer-events none so every
 * click (caption words, mic button, nav) passes through; a parchment fade
 * gives the hint line a legible base while the content above stays readable.
 * Revealed only during an active Live session (.active). */
#voice-wave-overlay {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    height: 180px;
    pointer-events: none;
    z-index: 40;
    display: none;
    background: linear-gradient(
        to bottom,
        rgba(253, 249, 241, 0) 0%,
        rgba(253, 249, 241, 0.55) 45%,
        rgba(253, 249, 241, 0.85) 100%
    );
}

#voice-wave-overlay.active {
    display: block;
}

#voice-wave-canvas {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
}

#voice-wave-hint {
    position: absolute;
    left: 0;
    right: 0;
    top: 8px;
    text-align: center;
    font-family: var(--font-serif);
    font-weight: 700;
    font-size: 1.5rem;
    color: var(--primary);
    /* Parchment halo: keeps the line legible over whatever chat content sits behind
     * the transparent top of the band (the gradient base only covers the bottom). */
    text-shadow:
        0 0 6px var(--background),
        0 0 14px var(--background),
        0 1px 2px var(--background);
}

/* The overlay's hint supersedes the footer's static cue while the wave is up —
 * two voices saying similar things in the same corner would just be noise. */
body:has(#voice-wave-overlay.active) #voice-hint {
    visibility: hidden;
}

/* The quiet exit line: always present while the wave is up, never competing with
 * the state hint above — small, muted, tucked at the band's base. */
#voice-wave-exit {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 6px;
    text-align: center;
    font-family: var(--font-serif);
    font-style: italic;
    font-size: 0.85rem;
    color: var(--outline);
}

/* Photo bubbles: keep them dignified — capped height, cover, no decorative
 * border. The <img> sits inside .user-message; its inherited 32px padding
 * gives the photo a parchment frame. */
.message .chat-photo {
    display: block;
    max-width: 100%;
    max-height: 320px;
    object-fit: cover;
}

/* Broken-image fallback (set by the round 22 P73 onerror retry handler). */
.message .chat-photo.broken-chat-image {
    max-height: none;
    padding: 16px;
    background-color: var(--surface-container-low);
    color: var(--on-surface-variant);
    font-style: italic;
}

/* Default greeting: Francesca's first hello sits a touch larger and italic —
 * the persona's voice should arrive warm before the conversation starts. */
.message.ai-message#default-greeting {
    font-size: 1.25rem;
    font-style: italic;
    color: var(--on-surface);
}

/* 2c: native-chrome word-hint — a subtle, non-bubble line under the greeting,
 * in the user's own language. Quiet by design (Scholarly Monolith restraint):
 * it frames the conversation without competing with Francesca's voice. Empty
 * until applyLocalization fills it via t('chat.word_hint'). */
.chat-hint {
    align-self: flex-start;
    margin: 0;
    color: var(--on-surface-variant);
    font-size: 0.8rem;
    font-style: italic;
}
.chat-hint:empty {
    display: none;
}

/* 2d: unsupported-pairing recovery — a calm, centered terminal empty state that
 * takes over #chat-history when the saved pairing can't be served (CC-12). Quiet
 * by design (Scholarly Monolith restraint): names the broken pair, hands the user
 * a single door back to /welcome. Not a toast, not a stack trace — "a friendly
 * error in the persona's voice" (FRAMEWORK §13). */
.pairing-recovery {
    margin: auto;
    max-width: 32rem;
    padding: 2rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1rem;
    text-align: center;
    color: var(--on-surface);
}
.pairing-recovery h2 {
    margin: 0;
    font-family: var(--font-serif);
    font-size: 1.3rem;
    font-weight: 600;
}
.pairing-recovery p {
    margin: 0;
    color: var(--on-surface-variant);
}
.pairing-recovery-cta {
    padding: 10px 20px;
    background-color: var(--primary);
    color: var(--on-primary);
    border: none;
    cursor: pointer;
    font-family: var(--font-serif);
    font-size: 0.85rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    transition: background-color 0.15s;
}
.pairing-recovery-cta:hover {
    background-color: var(--primary-container);
}

/* 5c: demo chrome — the terminal CTA card + the usage meter (W15/W16). The card is a
 * calm, history-preserving takeover (appended to #chat-history, never wiping it) that
 * ALSO serves the entry-page resting state (demo_*.html reuses this class). Scholarly
 * Monolith restraint: a tonal shift for separation (no divider line), 0px corners, Noto
 * Serif, generous whitespace. The ONE primary door wears the Dusty-Grape gradient
 * (DESIGN.md §Signature Textures); the two secondary doors are subdued text links. */
.demo-cta-card {
    margin: 1.5rem auto;
    align-self: center;
    max-width: 32rem;
    padding: 2rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1.25rem;
    text-align: center;
    background-color: var(--surface-container-low);
    color: var(--on-surface);
}
.demo-cta-card h2 {
    margin: 0;
    font-family: var(--font-serif);
    font-size: 1.3rem;
    font-weight: 600;
}
.demo-cta-card p {
    margin: 0;
    color: var(--on-surface-variant);
    line-height: 1.5;
}
.demo-cta-actions {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0.9rem;
}
.demo-cta-primary {
    /* The single primary action — Dusty-Grape gradient, pressed-ink feel (DESIGN.md). */
    padding: 10px 24px;
    background: linear-gradient(var(--primary), var(--primary-container));
    color: var(--on-primary);
    font-family: var(--font-serif);
    font-size: 0.85rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    text-decoration: none;
    transition: filter 0.15s;
}
.demo-cta-primary:hover {
    filter: brightness(1.08);
}
.demo-cta-link {
    /* Secondary doors — subdued text, not a second button competing for the eye. */
    color: var(--on-surface-variant);
    font-family: var(--font-serif);
    font-size: 0.9rem;
    text-decoration: none;
}
.demo-cta-link:hover {
    color: var(--primary);
    text-decoration: underline;
}

/* 5c: the demo usage meter — a typographic "≈ N messages left" node in a calm corner
 * above the chat input (D5/CC-20). Screen-reader-native text (role="status"), no gauge.
 * Per-session only; never the global budget. */
.demo-meter {
    margin: 0 0 0.4rem 0;
    padding: 0 0.5rem;
    text-align: right;
    font-family: var(--font-serif);
    font-size: 0.8rem;
    color: var(--on-surface-variant);
}

/* Loading bubbles (sender === 'loading') get neither .ai-message nor
 * .user-message — they end up as bare .message. Keep them visible but
 * subdued so the cancel button still reads. */
.message.loading-message {
    background-color: var(--surface-container-low);
    color: var(--on-surface-variant);
    font-style: italic;
    align-self: flex-start;
}

/* 6c: live-session degrade notice (KD-5) — a calm, centered, NON-bubble system line that
 * appears in #chat-history when the live line goes bad (built bare by showLiveNotice: no
 * .ai-message fill, no .word spans, no data-role, no play button → never a persona turn, never
 * translatable). A warm dropped-call word in the learner's own language, not a stack trace
 * (FRAMEWORK §13). Quiet by design (Scholarly Monolith restraint). Plain textContent → no flex
 * container concern (§7). Overrides .message's bubble metrics (max-width / padding / align). */
.message.live-degrade-notice {
    align-self: center;
    max-width: 32rem;
    padding: 16px 24px;
    text-align: center;
    color: var(--on-surface-variant);
    font-style: italic;
    font-size: 0.95rem;
}

/* The one degrade notice that carries an action: the demo voice-taste end (session_end) offers a
 * login CTA. A real <a> (not a .word span → the translation click-handler ignores it), reusing the
 * demo primary-CTA look, with a calm "or keep chatting by text" tail below. */
.live-degrade-notice .live-notice-cta {
    display: inline-block;
    margin: 14px 0 8px 0;
    padding: 10px 24px;
    background: linear-gradient(var(--primary), var(--primary-container));
    color: var(--on-primary);
    font-family: var(--font-serif);
    font-size: 0.85rem;
    font-weight: 600;
    font-style: normal;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    text-decoration: none;
    transition: filter 0.15s;
}
.live-degrade-notice .live-notice-cta:hover {
    filter: brightness(1.08);
}
.live-degrade-notice .live-notice-tail {
    display: block;
    font-size: 0.9rem;
}

/* "<persona> is thinking …" line with animated dots (chat send loading bubble). */
.message.loading-message .thinking-dots {
    display: inline-flex;
    gap: 1px;
    margin-left: 2px;
}
.message.loading-message .thinking-dots span {
    animation: thinking-blink 1.2s infinite ease-in-out;
}
.message.loading-message .thinking-dots span:nth-child(2) { animation-delay: 0.2s; }
.message.loading-message .thinking-dots span:nth-child(3) { animation-delay: 0.4s; }

@keyframes thinking-blink {
    0%, 70%, 100% { opacity: 0.25; }
    35%           { opacity: 1; }
}

/* The curiosità fact below the thinking line — clearly labelled so it can't read as
 * the reply. Lingers, then fades, after the answer lands (timing in chat.js). */
.message.loading-message .curiosita-loading {
    margin-top: 8px;
    font-size: 0.9em;
}
.message.loading-message.curiosita-only .curiosita-loading {
    margin-top: 0;
}
.message.loading-message .curiosita-label {
    font-style: normal;
    font-weight: 600;
    color: var(--on-surface);
}

@media (prefers-reduced-motion: reduce) {
    .message.loading-message .thinking-dots span {
        animation: none;
        opacity: 0.7;
    }
}

/* Loading-cancel button (appended by appendMessage when opts.cancelable). */
.message .loading-cancel-btn {
    margin-left: 12px;
    background: transparent;
    border: none;
    color: var(--on-surface-variant);
    font-size: 1.25rem;
    cursor: pointer;
}

/* === Word interaction + translation tooltips (Phase r2) === */

/* Inner wrapper: restores block flow for word spans inside the flex .message.
 * .message has two flex children: .message-text + .bubble-play (or cancel btn).
 * Words flow inline inside this wrapper instead of stacking as flex items. */
.message .message-text {
    display: block;
}

/* 5b/D3: demo-mode affordance belt. The primary gate is a JS early-return at each
 * creation seam (voice.addPlayButton omits .bubble-play; gallery omits the upload
 * buttons from its template), so these elements are never built for a demo. This CSS
 * is belt-and-suspenders — it also suppresses any render path added later that forgets
 * the JS guard. body.demo-mode is set by boot's applyDemoAffordances(). */
body.demo-mode .bubble-play,
body.demo-mode #gallery-upload-device-btn,
body.demo-mode #gallery-add-photos-btn {
    display: none !important;
}

/* Play button: tertiary/ghost style per Stitch §5 — no container, serif text,
 * tonal color, underline appears on hover. */
.bubble-play {
    align-self: flex-start;
    margin-top: 12px;
    padding: 4px 0;
    background: transparent;
    border: none;
    color: var(--on-surface-variant);
    font-family: var(--font-serif);
    font-size: 0.875rem;
    cursor: pointer;
    text-decoration: none;
    transition: text-decoration 0.15s;
}

.bubble-play:hover {
    text-decoration: underline;
    text-decoration-thickness: 2px;
    text-underline-offset: 4px;
    color: var(--on-surface);
}

/* Synthesizing state: the TTS stream's first chunk is in flight (~1-2s). The old
 * opacity pulse was invisible in practice (Vinga, 2026-07-13) — a proper grape
 * spinner ring now marks the wait, and the button locks against double-fire. */
.bubble-play.loading {
    pointer-events: none;
    color: var(--primary);
}

.bubble-play.loading::after {
    content: '';
    display: inline-block;
    width: 0.9em;
    height: 0.9em;
    margin-left: 8px;
    border: 2px solid var(--outline-variant);
    border-top-color: var(--primary);
    border-radius: 50%;
    vertical-align: -0.15em;
    animation: bubble-play-spin 0.8s linear infinite;
}

@keyframes bubble-play-spin {
    to { transform: rotate(360deg); }
}

/* The slow-warmup hint ("{persona} is clearing her throat…") — appears beside the
 * spinner only when the first audio chunk takes >1.5s, removed the moment it plays. */
.bubble-play-warmup {
    margin-left: 10px;
    font-family: var(--font-serif);
    font-style: italic;
    font-size: 0.85rem;
    color: var(--primary);
}

/* Word spans: clickable vocabulary targets with subtle hover cue. */
.word {
    cursor: pointer;
    text-decoration: none;
    transition: text-decoration-color 0.12s ease;
}

.word:hover {
    text-decoration: underline;
    text-decoration-color: var(--primary);
    text-decoration-thickness: 2px;
    text-underline-offset: 3px;
    color: var(--primary);
}

.word:active {
    text-decoration: underline;
    text-decoration-color: var(--primary);
    text-decoration-thickness: 2px;
    text-underline-offset: 3px;
}

/* Translation tooltip: scholarly card floating near the clicked word.
 * Appended to document.body — needs explicit border-radius and high z-index. */
.translation-tooltip {
    position: fixed;
    z-index: 9999;
    min-width: 160px;
    max-width: 300px;
    padding: 16px 20px;
    background-color: var(--surface-container-lowest);
    color: var(--on-surface);
    border-radius: var(--radius);
    font-family: var(--font-serif);
    font-size: 1rem;
    line-height: 1.5;
    /* Dusty Grape atmospheric shadow — Stitch §4. Opacity-modified --primary;
     * can't be expressed as a token ref in vanilla CSS without color-mix(). */
    box-shadow: 0 20px 40px rgba(122, 114, 145, 0.10);
    opacity: 0;
    transition: opacity 0.15s ease;
}

.translation-tooltip .tooltip-content {
    margin-bottom: 8px;
}

.translation-tooltip .tooltip-close {
    position: absolute;
    top: 8px;
    right: 8px;
    padding: 2px 6px;
    background: transparent;
    border: none;
    border-radius: var(--radius);
    color: var(--on-surface-variant);
    font-size: 1.125rem;
    cursor: pointer;
    line-height: 1;
}

.translation-tooltip .tooltip-close:hover {
    color: var(--on-surface);
}

/* =============================================================================
 * Cluster D — Phase 8a gallery (bento grid + section headers + toolbar shell)
 * -----------------------------------------------------------------------------
 * Markup contract from frontend/gallery.js renderGallery():
 *   #gallery-view
 *     #gallery-toolbar-shell           (persistent — built once)
 *       button#gallery-add-photos-btn
 *       button#gallery-select-btn
 *       button#gallery-done-btn[hidden]
 *     #gallery-grid
 *       section.gallery-section
 *         h2.gallery-section-header.headline
 *         .gallery-grid
 *           .gallery-item-wrapper(.selected)
 *             img.gallery-item(.broken-image)[alt][data-photo-id]
 *
 * Every value uses var(--token) per CC-2. Border-radius is inherited as 0
 * from the #app-container * scoped reset (CC-3).
 * ============================================================================= */

#gallery-view {
    display: flex;
    flex-direction: column;
    gap: 24px;
    padding: 24px 32px 48px;
    overflow-y: auto;
    flex: 1 1 auto;
    background-color: var(--background);
    color: var(--on-surface);
}

#gallery-toolbar-shell {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 12px 16px;
    background-color: var(--surface-container-low);
    border-bottom: 1px solid var(--outline-variant);
}

.gallery-toolbar-btn {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    padding: 10px 16px;
    background-color: var(--surface-container-lowest);
    color: var(--on-surface);
    border: 1px solid var(--outline-variant);
    font-family: var(--font-serif);
    font-size: 1rem;
    font-weight: 500;
    cursor: pointer;
}

.gallery-toolbar-btn:hover {
    background-color: var(--surface-dim);
}

.gallery-toolbar-btn .material-symbols-outlined {
    font-size: 1.25rem;
    color: var(--primary);
}

/* Gallery "add" actions (Google Photos + device upload) — the Dusty-Grape
   gradient primary CTA from the gallery mockup (DESIGN.md "Buttons &
   Interaction"). Both add sources share it so the toolbar keeps its grape
   anchor even when one is hidden (Google Photos is premium-only), while
   select/done/delete stay paper-white secondary buttons. */
#gallery-add-photos-btn,
#gallery-upload-device-btn {
    background: linear-gradient(135deg, var(--primary), var(--primary-container));
    color: var(--on-primary);
    border: none;
    font-weight: 700;
}

#gallery-add-photos-btn:hover,
#gallery-upload-device-btn:hover {
    background: var(--primary-container);
}

#gallery-add-photos-btn .material-symbols-outlined,
#gallery-upload-device-btn .material-symbols-outlined {
    color: var(--on-primary);
}

/* Spinning icon for in-progress actions (e.g. device upload). The 0-radius
   rule (DESIGN.md, "no exceptions") rules out a round CSS border-spinner, so we
   rotate the Material 'progress_activity' glyph instead — consistent with the
   icon system. inline-block so the transform actually applies to the glyph. */
@keyframes cartolina-spin {
    to { transform: rotate(360deg); }
}

.material-symbols-outlined.is-spinning {
    display: inline-block;
    animation: cartolina-spin 0.8s linear infinite;
}

#gallery-done-btn[hidden],
#gallery-select-btn[hidden],
#gallery-delete-btn[hidden] {
    display: none;
}

/* Provenance credit under each artwork label (gallery.js renderArtGallery). A
   quiet line — license, plus the author where a CC reproduction requires it. */
.art-credit {
    display: block;
    margin-top: 0.15rem;
    font-size: 0.7em;
    opacity: 0.55;
    line-height: 1.2;
}

#bulk-delete-progress {
    font-family: var(--font-serif);
    font-size: 0.9rem;
    color: var(--on-surface);
}

#bulk-delete-notice {
    padding: 8px 12px;
    background-color: var(--surface-container-lowest);
    color: var(--on-surface);
    border-left: 3px solid var(--primary);
    font-family: var(--font-serif);
    font-size: 0.9rem;
}

#gallery-grid {
    display: flex;
    flex-direction: column;
    gap: 32px;
}

.gallery-section {
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.gallery-section-header {
    color: var(--on-surface);
    padding-bottom: 8px;
    border-bottom: 1px solid var(--outline-variant);
}

.gallery-section .gallery-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
    gap: 12px;
}

.gallery-item-wrapper {
    position: relative;
    aspect-ratio: 1 / 1;
    background-color: var(--surface-container);
    overflow: hidden;
    cursor: pointer;
    /* Bento scaling per Decision D2: keep ~thousand-photo galleries cheap to
     * re-render. Re-render every renderGallery() call is acceptable while
     * content-visibility: auto skips offscreen items. */
    content-visibility: auto;
    contain-intrinsic-size: 0 220px;
}

.gallery-item-wrapper:hover {
    outline: 2px solid var(--primary-container);
    outline-offset: -2px;
}

.gallery-item-wrapper.selected {
    outline: 3px solid var(--primary);
    outline-offset: -3px;
}

.gallery-item-wrapper.selected::after {
    content: 'check_circle';
    font-family: 'Material Symbols Outlined';
    font-size: 28px;
    position: absolute;
    top: 8px;
    right: 8px;
    color: var(--on-primary);
    background-color: var(--primary);
    width: 32px;
    height: 32px;
    display: flex;
    align-items: center;
    justify-content: center;
}

.gallery-item-wrapper img.gallery-item {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.gallery-item-wrapper img.gallery-item.broken-image {
    opacity: 0.35;
    filter: grayscale(1);
}

.gallery-item-wrapper:has(img.broken-image)::before {
    content: 'broken_image';
    font-family: 'Material Symbols Outlined';
    font-size: 40px;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    color: var(--on-surface-variant);
    pointer-events: none;
    z-index: 1;
}

/* =============================================================================
 * Cluster F — Phase 11a Dictionary view
 * -----------------------------------------------------------------------------
 * Two-column layout (list + detail card), 5000-row scalable via
 * content-visibility (PATTERNS § "Dictionary view CSS"), Latin section
 * labels (Definitio / Originis Contextus) per D10.
 *
 * Scoping: every `.vocab-item` and `.play-icon` rule is scoped to
 * `#vocab-list-container` so it does NOT collide with the legacy right-rail
 * `#vocab-list .vocab-item` renderer (G1).
 * ============================================================================= */
#dictionary-view {
    flex: 1;
    min-height: 0;
    background-color: var(--surface);
    color: var(--on-surface);
    overflow: hidden;
}

.dictionary-layout {
    display: grid;
    grid-template-columns: minmax(280px, 380px) 1fr;
    gap: 32px;
    height: 100%;
    padding: 32px 48px;
    box-sizing: border-box;
}

.dictionary-list-pane {
    display: flex;
    flex-direction: column;
    min-height: 0;
    min-width: 0;
}

.dictionary-search-wrap {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 10px 14px;
    background-color: var(--surface-container-low);
    margin-bottom: 16px;
}

.dictionary-search-wrap .material-symbols-outlined {
    font-size: 20px;
    color: var(--on-surface-variant);
}

#vocab-search {
    flex: 1;
    border: none;
    outline: none;
    background: transparent;
    font-family: var(--font-serif);
    font-size: 1rem;
    color: var(--on-surface);
}

#vocab-search::placeholder {
    color: var(--on-surface-variant);
    opacity: 0.7;
}

#vocab-list-container {
    flex: 1;
    overflow-y: auto;
    min-height: 0;
}

.vocab-section-header {
    font-family: var(--font-serif);
    font-size: 0.75rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--on-surface-variant);
    padding: 16px 4px 8px 4px;
    margin: 0;
}

.vocab-section-header:first-child {
    padding-top: 4px;
}

/* Round 15 P40 — skip layout/render for off-screen vocab items.
 * 60px is a safe estimate for .vocab-item height (text row + play icon);
 * actual size takes precedence once the row enters the viewport. */
#vocab-list-container .vocab-item {
    content-visibility: auto;
    contain-intrinsic-size: 0 60px;
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 12px;
    cursor: pointer;
    background-color: transparent;
    color: var(--on-surface);
    transition: background-color 0.12s;
}

#vocab-list-container .vocab-item:hover {
    background-color: var(--surface-container-low);
}

#vocab-list-container .vocab-item.selected {
    background-color: var(--surface-container);
}

#vocab-list-container .vocab-item[hidden] {
    display: none;
}

#vocab-list-container .vocab-word {
    flex: 1;
    font-family: var(--font-serif);
    font-size: 1.0625rem;
    font-weight: 500;
    color: var(--on-surface);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

#vocab-list-container .vocab-trans {
    font-family: var(--font-serif);
    font-size: 0.875rem;
    color: var(--on-surface-variant);
    font-style: italic;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 40%;
}

/* Round 15 P40 — list-side play icons hidden by default; Phase 11b adds
 * `.voices-loaded` to `#vocab-list-container` when a target-language voice
 * is confirmed available, and this rule reveals them in one DOM mutation. */
#vocab-list-container .play-icon {
    display: none;
    font-size: 18px;
    color: var(--on-surface-variant);
}

#vocab-list-container.voices-loaded .play-icon {
    display: inline-flex;
}

.vocab-empty-state {
    padding: 24px 12px;
    font-family: var(--font-serif);
    font-size: 0.9375rem;
    color: var(--on-surface-variant);
    font-style: italic;
    line-height: 1.5;
}

.vocab-empty-state .clear-search-link {
    display: inline-block;
    margin-top: 12px;
    color: var(--primary);
    text-decoration: underline;
    background: none;
    border: none;
    padding: 0;
    font-family: var(--font-serif);
    font-size: 0.9375rem;
    font-style: italic;
    cursor: pointer;
}

.vocab-has-more-banner {
    padding: 12px;
    margin-top: 8px;
    font-family: var(--font-serif);
    font-size: 0.8125rem;
    color: var(--on-surface-variant);
    background-color: var(--surface-container-low);
    font-style: italic;
    text-align: center;
}

/* === Detail card (right column) =========================================== */
.dictionary-detail-pane {
    min-width: 0;
    overflow-y: auto;
}

#vocab-detail-card {
    padding: 24px 28px;
    background-color: var(--surface-container-lowest);
    color: var(--on-surface);
    font-family: var(--font-serif);
    min-height: 100%;
    box-sizing: border-box;
}

.detail-empty-state {
    font-style: italic;
    color: var(--on-surface-variant);
    font-size: 1rem;
    padding: 24px 0;
}

.detail-word-row {
    display: flex;
    align-items: baseline;
    gap: 16px;
    margin-bottom: 24px;
}

.detail-word {
    font-family: var(--font-serif);
    font-size: 2.25rem;
    font-weight: 600;
    color: var(--on-surface);
    margin: 0;
}

.detail-play {
    background-color: var(--primary);
    color: var(--on-primary);
    border: none;
    width: 40px;
    height: 40px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    font-family: var(--font-serif);
    transition: background-color 0.12s;
}

.detail-play:hover {
    background-color: var(--primary-container);
}

.detail-play .material-symbols-outlined {
    font-size: 22px;
}

.detail-play[aria-disabled="true"] {
    background-color: var(--surface-container-low);
    color: var(--on-surface-variant);
    cursor: not-allowed;
}

.detail-play[aria-disabled="true"]:hover {
    background-color: var(--surface-container-low);
}

.detail-section-heading {
    font-family: var(--font-serif);
    font-size: 0.8125rem;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--on-surface-variant);
    margin: 24px 0 8px 0;
    font-style: italic;
}

.detail-definitio {
    font-family: var(--font-serif);
    font-size: 1.25rem;
    color: var(--on-surface);
    margin: 0 0 8px 0;
}

.originis-contextus {
    margin-top: 24px;
    padding-top: 16px;
    border-top: 1px solid var(--outline-variant);
}

.originis-contextus img {
    display: block;
    max-width: 100%;
    max-height: 280px;
    object-fit: cover;
    margin-top: 8px;
    background-color: var(--surface-container-low);
}

.originis-contextus .originis-caption {
    display: block;
    margin-top: 10px;
    font-family: var(--font-serif);
    font-size: 0.875rem;
    font-style: italic;
    color: var(--on-surface-variant);
}

/* Narrow-screen fallback — stack the two panes vertically. */
@media (max-width: 768px) {
    .dictionary-layout {
        grid-template-columns: 1fr;
        grid-template-rows: minmax(200px, 40%) 1fr;
        gap: 16px;
        padding: 20px 20px;
    }
}

/* =============================================================================
 * Cluster G — Phase 13b welcome page (bilingual)
 * -----------------------------------------------------------------------------
 * Scholarly Monolith front door. Full-bleed centered card, no left rail, warm
 * parchment radial wash, conversational sentence with two inline <select>
 * dropdowns rendered as italic underlined words (D6 — appearance: none + bottom
 * border only). Root wrapper class is `.welcome-screen` exactly so the
 * foundation's scoped `border-radius: var(--radius)` reset (lines 76–79) reaches
 * every form control inside without browser-default rounded chrome leaking back
 * (D5 — load-bearing selector contract).
 *
 * Owns scroll: html/body keep `overflow: hidden` from the foundation; the
 * welcome-screen takes `overflow-y: auto` so a tall card on short viewports
 * scrolls inside the wrapper instead of clipping at the body edge.
 *
 * All values reference var(--token). Zero hex outside :root (CC-2).
 * ============================================================================= */

.welcome-screen {
    min-height: 100vh;
    height: 100%;
    width: 100%;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    padding: 64px 32px 48px;
    background:
        radial-gradient(ellipse at 30% 20%, var(--surface-container-low) 0%, transparent 60%),
        radial-gradient(ellipse at 70% 80%, var(--surface-container) 0%, transparent 60%),
        var(--background);
}

/* The HTML `hidden` attribute must win over the component display rules below
   (e.g. .name-frozen-row sets display:flex) — otherwise the empty frozen-name
   row + its edit pencil leak onto a fresh new-user welcome. Scoped selector
   (0,2,0) outranks the class rules without needing !important; when JS clears
   `.hidden`, the attribute is gone so the element shows normally. */
.welcome-screen [hidden] {
    display: none;
}

.welcome-card {
    text-align: center;
    max-width: 720px;
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 48px;
}

.welcome-header {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    padding-top: 120px;
}

.welcome-logo {
    font-family: var(--font-serif);
    font-size: 3rem;
    line-height: 1.15;
    font-weight: 700;
    letter-spacing: -0.025em;
    color: var(--primary);
}

.welcome-form {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 40px;
    width: 100%;
}

.conversational-sentence {
    font-family: var(--font-serif);
    font-size: 1.5rem;
    line-height: 1.6;
    font-weight: 400;
    color: var(--on-surface);
    max-width: 640px;
}

.welcome-screen select {
    appearance: none;
    -webkit-appearance: none;
    -moz-appearance: none;
    background: none;
    background-color: transparent;
    border: none;
    border-bottom: 2px solid var(--primary);
    color: var(--primary);
    font-family: var(--font-serif);
    font-size: inherit;
    font-weight: 700;
    font-style: italic;
    cursor: pointer;
    padding: 0 8px;
    margin: 0 4px;
    outline: none;
    display: inline;
}

.welcome-screen select:focus {
    background-color: var(--surface-variant);
}

.welcome-screen select option {
    font-style: normal;
    color: var(--on-surface);
    background-color: var(--surface);
}

.field-group {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 16px;
    width: 100%;
    max-width: 420px;
}

.field-label {
    font-size: 0.75rem;
    line-height: 1.4;
    text-transform: uppercase;
    letter-spacing: 0.15em;
    color: var(--on-surface-variant);
    font-weight: 700;
}

.text-input {
    width: 100%;
    background-color: var(--surface-container);
    border: none;
    padding: 16px 20px;
    font-family: var(--font-serif);
    font-size: 1.25rem;
    color: var(--on-surface);
    outline: none;
    text-align: center;
    transition: background-color 0.2s, box-shadow 0.2s;
}

.text-input::placeholder {
    color: var(--outline);
    font-style: italic;
}

.text-input:focus {
    background-color: var(--surface);
    box-shadow: inset 0 -2px 0 var(--primary);
}

.name-frozen-row {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 12px;
    width: 100%;
    padding: 12px 0;
}

.name-frozen {
    font-family: var(--font-serif);
    font-size: 1.5rem;
    font-weight: 700;
    color: var(--on-surface);
}

.name-edit-btn {
    background: none;
    border: none;
    color: var(--on-surface-variant);
    cursor: pointer;
    padding: 4px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: color 0.2s;
}

.name-edit-btn:hover {
    color: var(--primary);
}

.name-edit-btn .material-symbols-outlined {
    font-size: 20px;
}

.name-frozen-label {
    font-size: 0.75rem;
    line-height: 1.4;
    text-transform: uppercase;
    letter-spacing: 0.15em;
    color: var(--on-surface-variant);
    font-weight: 700;
}

/* Onboarding hint — surfaces *why* "Let's Begin" stays disabled when the user
   has not yet picked a language (the inline dropdown is easy to miss). */
.welcome-hint {
    margin: -8px 0 0;
    font-family: var(--font-serif);
    font-style: italic;
    font-size: 0.9rem;
    line-height: 1.5;
    color: var(--on-surface-variant);
    text-align: center;
}

/* Returning-account row (2026-07-13, Vinga): must be properly visible, not a
 * whisper — Dusty Grape text at readable size, with a real (small) button. */
.welcome-login-row {
    margin: 20px 0 0;
    font-family: var(--font-serif);
    font-size: 1.05rem;
    line-height: 1.5;
    color: var(--primary);
    text-align: center;
}

.welcome-login-btn {
    display: inline-block;
    margin-left: 10px;
    padding: 10px 22px;
    background: linear-gradient(135deg, var(--primary), var(--primary-container));
    color: var(--on-primary);
    font-family: var(--font-serif);
    font-size: 0.9rem;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    text-decoration: none;
    cursor: pointer;
    transition: background-color 0.2s, transform 0.1s;
}

.welcome-login-btn:hover {
    background: var(--primary-container);
}

.welcome-login-btn:active {
    transform: scale(0.98);
}

.btn-primary {
    background: linear-gradient(135deg, var(--primary), var(--primary-container));
    color: var(--on-primary);
    border: none;
    padding: 20px 48px;
    font-family: var(--font-serif);
    font-size: 1.125rem;
    font-weight: 700;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    cursor: pointer;
    transition: opacity 0.2s, transform 0.1s, background-color 0.2s;
}

.btn-primary:hover:not(:disabled) {
    background: var(--primary-container);
}

.btn-primary:active:not(:disabled) {
    transform: scale(0.98);
}

.btn-primary:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

.about-section {
    max-width: 560px;
    color: var(--on-surface-variant);
    font-family: var(--font-serif);
    font-size: 1rem;
    line-height: 1.7;
    font-style: italic;
}

.welcome-footer {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 12px;
    width: 100%;
    padding-top: 48px;
    opacity: 0.5;
    font-size: 0.625rem;
    letter-spacing: 0.15em;
    text-transform: uppercase;
    font-weight: 700;
    color: var(--on-surface-variant);
}

@media (min-width: 768px) {
    .welcome-card {
        gap: 64px;
    }
    .conversational-sentence {
        font-size: 2.25rem;
        line-height: 1.55;
    }
    .welcome-logo {
        font-size: 3.5rem;
    }
    .welcome-footer {
        flex-direction: row;
        justify-content: space-between;
    }
}

@media (max-width: 480px) {
    .welcome-screen {
        padding: 32px 20px;
    }
    .welcome-card {
        gap: 36px;
    }
    .welcome-logo {
        font-size: 2.5rem;
    }
    .conversational-sentence {
        font-size: 1.25rem;
        line-height: 1.7;
    }
    .btn-primary {
        padding: 16px 32px;
        font-size: 1rem;
    }
}
