/* =========================================================================
   Speaking practice (F0: section scaffold + tab activation).

   Reuses Writing's task-menu, workspace and source-block classes (applied in
   markup), so this file only carries what's genuinely Speaking-specific: the
   section tab treatment for the speaking shell, the context dot, and the
   transport-row placeholder that F2 fills with the recorder/timer.

   NOTE: the section-tab rules below mirror the writing-section-shell block in
   app-shell.css. Unifying section-tab styling across sections is the tracked
   CSS reorg (section-shell R3–R6) — deferred; duplicated minimally here for now.
   ========================================================================= */

.section-tab-group--speaking {
    display: inline-flex;
    align-items: center;
    position: relative;
}

/* Speaking primary-tab affordances (chevron transition, dropdown underline, chevron rotate) now come
   from the unified base .section-tab-primary / .section-tab-chevron rules in app-shell.css. */

/* Task surface — same content inset as Writing (#writingTaskSurface). Without this the instruction
   and passage hug the card edge; the horizontal padding is dropped on mobile (card supplies the gutter). */
#speakingTaskSurface {
    padding: var(--space-20) var(--space-32) 0;
}

@media (max-width: 767px) {
    #speakingTaskSurface {
        padding-left: 0;
        padding-right: 0;
    }
}

.speaking-answer-zone {
    margin-top: var(--space-16);
}

/* Attempt history — uses the shared .question-attempts-* classes; only the top divider/margin is
   section-specific (mirrors .writing-attempts-panel). */
.speaking-attempts-panel {
    margin: 0;
    padding: var(--space-14) 0 0;
    border-top: var(--border-width-default) solid var(--border-subtle);
}

/* Calibration-only raw-metrics readout (shown with ?diag). Deliberately plain/muted — a dev tool. */
.speaking-diag {
    margin-top: var(--space-16);
    padding: var(--space-10) var(--space-12);
    border: var(--border-default-width) dashed var(--border-default);
    border-radius: var(--radius-card);
    font-size: var(--font-size-082);
    color: var(--text-secondary);
}

.speaking-diag > summary {
    cursor: pointer;
    color: var(--text-muted);
    letter-spacing: var(--tracking-eyebrow);
    text-transform: uppercase;
    font-size: var(--font-size-072);
}

.speaking-diag-head {
    margin: var(--space-10) 0 var(--space-4);
    color: var(--text-muted);
    letter-spacing: var(--tracking-eyebrow);
    text-transform: uppercase;
    font-size: var(--font-size-072);
}

.speaking-diag-list {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: var(--space-2) var(--space-12);
    margin: var(--space-6) 0 0;
    font-variant-numeric: tabular-nums;
}

.speaking-diag-list dt {
    grid-column: 1;
    color: var(--text-secondary);
}

.speaking-diag-list dd {
    grid-column: 2;
    margin: 0;
    color: var(--text-primary);
    text-align: right;
}

.speaking-diag-head {
    grid-column: 1 / -1;
}

/* Small muted clarifier under the criteria — the overall score is a blend, not a sum of the /90 rows. */
/* Completion screen vertical centering — same as #writingCompletion: no tall parent, so it takes the
   height itself and the shared .completion (justify-center) centers the content. */
#speakingCompletion {
    min-height: calc(100dvh - (var(--space-32) * 7));
}

/* Sits directly under the score row (right-aligned, under "Score X/90") so the blend
   is explained at the point the number is read, not below the whole breakdown. */
.speaking-criteria-note {
    margin: var(--space-4) 0 var(--space-16);
    font-size: var(--font-size-082);
    color: var(--text-muted);
    line-height: 1.5;
    text-align: right;
}

/* Action row — shared .actions / .question-primary-actions (createPracticeActionRow),
   same as Writing/Reading. Mirrors .writing-actions, plus the grey top/bottom divider
   lines that delimit the submit band (same --border-subtle line language as the
   instruction and passage blocks). */
.speaking-actions {
    justify-content: flex-end;
    flex-wrap: nowrap;
    margin-top: var(--space-16);
    /* Symmetric padding keeps the buttons off both the top divider and the score panel below.
       Only a TOP border — the graded feedback's result-row carries its own top border, so a
       bottom border here would double up into one thick line right above the score panel. */
    padding: var(--space-14) 0;
    border-top: var(--border-width-default) solid var(--border-subtle);
}

/* Drop the base margin-left:auto so Previous sits immediately left of Check answer
   (the guidance hint's margin-right:auto keeps the keyboard hint on the far left). */
.speaking-actions .question-primary-actions {
    margin-left: 0;
}

#speakingNextButton .question-nav-arrow {
    font-size: 0.8125rem;
    transition: transform var(--motion-duration-fast) var(--motion-ease-micro);
}

#speakingNextButton:hover .question-nav-arrow {
    transform: translateX(var(--space-2));
}

@media (pointer: coarse), (max-width: 767px) {
    .speaking-actions .question-keyboard-hint {
        display: none !important;
    }
}

/* ---- Mobile layout hardening ----
   On phones the whole action row becomes a clean full-width vertical stack (Previous / Check answer /
   Next), rather than the desktop right-aligned cluster which rendered crooked on a narrow screen (Previous
   floating beside a half-width stacked cluster). Also stops the nowrap row from forcing horizontal scroll. */
@media (max-width: 767px) {
    .speaking-actions {
        flex-direction: column;
        align-items: stretch;
        gap: var(--space-10);
    }

    .speaking-actions .question-primary-actions {
        flex-direction: column;
        align-items: stretch;
        width: 100%;
        gap: var(--space-10);
    }

    .speaking-actions .btn-primary,
    .speaking-actions .practice-prev-button {
        width: 100%;
        justify-content: center;
        margin: 0;
    }

    /* Owner: drop "Record again" on phones to de-clutter the action stack. */
    #speakingRecordAgainButton {
        display: none;
    }

    /* Let the answer zone + stage shrink inside the card so long transcript words or a
       wide control can't push past the card edge (→ body horizontal scroll). */
    .speaking-answer-zone,
    .speaking-audio-stage {
        min-width: 0;
    }

    /* The mic chooser exists for desktops where the OS silently switches to an absent input; phones have
       a single mic, so it's just noise (and its labels only populate post-permission). Hide the whole
       chooser (label + select); the separate "we can't hear you" warning still shows and still matters. */
    .speaking-mic-controls {
        display: none;
    }
}

/* Mic chooser (helps when macOS auto-switches to an absent device) + "can't hear you" hint. */
.speaking-mic-controls {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-8);
    margin-top: var(--space-12);
    font-size: var(--font-size-082);
    color: var(--text-secondary);
}

.speaking-mic-select {
    font: inherit;
    color: var(--text-primary);
    background: var(--bg-surface);
    border: var(--border-default-width) solid var(--border-default);
    border-radius: var(--radius-button);
    padding: var(--space-4) var(--space-8);
    max-width: 60%;
}

.speaking-mic-warning {
    margin-top: var(--space-10);
    font-size: var(--font-size-082);
    color: var(--danger-text);
    text-align: center;
}

/* =========================================================================
   Recording stage (F2): a bordered box (like the passage block) that runs the
   exam flow — PREPARATION countdown + muted mic → RECORDING + blue pulsing mic
   → player. F4 renders the transcription + feedback in this same box.
   ========================================================================= */
/* The bordered box look, shared by the Read Aloud stage and the Repeat Sentence
   tracks frame (one declaration, not two copies). Per-element padding/layout
   below. */
.speaking-audio-stage {
    border: var(--border-default-width) solid var(--border-default);
    border-radius: var(--radius-card);
    /* This box is UI chrome, not selectable copy — clicking it must never place a text caret. */
    user-select: none;
}

.speaking-audio-stage {
    padding: var(--space-28) var(--space-20);
    text-align: center;
}

.speaking-stage-live {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-14);
}

.speaking-stage-head {
    font-size: var(--font-size-082);
    letter-spacing: var(--tracking-eyebrow);
    text-transform: uppercase;
    color: var(--text-secondary);
    font-variant-numeric: tabular-nums;
}

/* Mic is a real button: tap in prep to start early, tap while recording to stop. Clearly interactive
   (pointer + hover), not a disabled-looking grey. */
.speaking-stage-mic {
    position: relative;
    width: var(--space-64);
    height: var(--space-64);
    border-radius: var(--radius-pill);
    border: var(--border-width-default) solid var(--border-default);
    background: var(--bg-surface);
    color: var(--text-secondary);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    cursor: pointer;
    transition: color var(--motion-duration-fast) var(--motion-ease-standard),
        border-color var(--motion-duration-fast) var(--motion-ease-standard),
        box-shadow 80ms ease-out;
}

.speaking-stage-mic:hover {
    color: var(--accent-action);
    border-color: var(--accent-action);
}

.speaking-stage-mic:focus-visible {
    outline: var(--border-focus-width) solid var(--accent-action);
    outline-offset: var(--space-4);
}

.speaking-stage-mic svg {
    width: 44%;
    height: 44%;
}

.speaking-stage-label {
    font-size: var(--text-meta-size);
    color: var(--text-secondary);
}

/* Recording: mic goes to the action blue with (a) a voice-reactive glow whose spread follows the live
   input level (--speaking-level, set per-frame from the analyser) and (b) an obvious continuous ring. */
.speaking-audio-stage.is-recording .speaking-stage-head {
    color: var(--accent-action);
}

.speaking-audio-stage.is-recording .speaking-stage-mic {
    color: var(--accent-action);
    border-color: var(--accent-action);
    box-shadow: 0 0 0 calc(var(--space-2) + var(--speaking-level, 0) * 16px) var(--accent-action-subtle);
}

.speaking-audio-stage.is-recording .speaking-stage-mic::after {
    content: "";
    position: absolute;
    inset: calc(-1 * var(--space-3));
    border-radius: var(--radius-pill);
    border: var(--border-width-strong) solid var(--accent-action);
    animation: speaking-mic-ring var(--motion-duration-pulse) ease-out infinite;
}

@keyframes speaking-mic-ring {
    0% {
        transform: scale(1);
        opacity: 0.65;
    }
    100% {
        transform: scale(1.55);
        opacity: 0;
    }
}

/* State visibility. Optional blocks are hidden by default and shown per phase class. */
.speaking-stage-player,
.speaking-stage-loading,
.speaking-stage-result,
.speaking-stage-actions,
.speaking-stage-error {
    display: none;
}

/* live (mic + timer) only during prep / recording */
.speaking-audio-stage.is-recorded .speaking-stage-live,
.speaking-audio-stage.is-submitting .speaking-stage-live,
.speaking-audio-stage.is-result .speaking-stage-live,
.speaking-audio-stage.is-error .speaking-stage-live,
.speaking-audio-stage.is-submitfailed .speaking-stage-live {
    display: none;
}

/* the recording player: recorded + result */
.speaking-audio-stage.is-recorded .speaking-stage-player,
.speaking-audio-stage.is-result .speaking-stage-player {
    display: block;
}

/* =========================================================================
   Repeat Sentence layout. RS reuses Read Aloud's single bordered box and looks
   almost identical: the mic circle + "Preparation" countdown, then the sentence
   plays through the progress bar (#speakingPromptMount) with the play time
   counting down, then the mic circle + a live recording waveform while the
   learner repeats. RS-specific chrome: the progress bar (playback), the live
   recording waveform (.speaking-recording-wave), and the interactive sentence
   player which returns ONLY on the review screen (replay the original).
   ========================================================================= */
/* RS plays the sentence through the exam progress bar (#speakingPromptMount) during the listen phase; JS
   toggles it via [hidden], so there is no blanket hide here. The mic circle (.speaking-stage-live) shows
   throughout — like Read Aloud's prep — from the "Preparation" countdown through recording. */

/* The interactive sentence player is shown ONLY on the review screen, so the learner can replay the original
   clip. During the attempt the sentence plays through the progress bar instead, so it stays hidden. */
.speaking-sentence-player {
    display: none;
}

.speaking-audio-stage.is-repeat-sentence.is-result .speaking-sentence-player {
    display: block;
}

/* Live recording waveform under the mic circle — shown only WHILE recording (createLiveWaveform bars). */
.speaking-recording-wave {
    display: none;
}

.speaking-audio-stage.is-repeat-sentence.is-recording .speaking-recording-wave {
    display: block;
    margin-top: var(--space-16);
}

/* Repeat Sentence is a compact audio task — narrow its content (instruction + box + feedback) into a centred
   column so it doesn't stretch empty across a wide desktop, but keep the side gaps modest (half the margin a
   flat 1100px cap would leave). calc((100% + 1100px)/2) = the width that halves the side margin at any card
   width; on narrow cards it exceeds 100% and simply falls back to full width. JS adds .is-repeat-sentence to
   #speakingWorkspace when RS is active; Read Aloud (a passage) keeps the full width. */
#speakingWorkspace.is-repeat-sentence #speakingTaskSurface {
    /* width:100% is required because the workspace is a column flexbox: with only auto side margins a flex
       item shrinks to its CONTENT width (so the narrow question box and the wider feedback grid would render
       at different widths). Forcing width:100% makes the surface fill the capped width on every phase. */
    width: 100%;
    max-width: calc((100% + 1100px) / 2);
    margin: 0 auto;
}

/* Touch-only "Play sentence & record" CTA under the sentence player. Visibility is JS-driven (shown only
   for the touch Repeat Sentence path — see speaking-state.js); this is just its layout. A comfortable
   full-width touch target directly under the player. (display:block + width:100% — it's a direct child of
   the block-flow stage now, so flex align-self no longer applies.) */
.speaking-sentence-start {
    display: block;
    width: 100%;
    margin-top: var(--space-8);
    min-height: var(--space-44, 2.75rem);
    padding: var(--space-10) var(--space-16);
}

.speaking-sentence-start[hidden] {
    display: none;
}

/* Phase eyebrow ABOVE the question card (outside it): "GET READY · 3" →
   "PLAYING · LISTEN CAREFULLY". Quiet, left-aligned; action blue while playing. */
.speaking-phase-eyebrow {
    display: none;
    margin: 0;
    font-size: var(--font-size-082);
    letter-spacing: var(--tracking-eyebrow);
    text-transform: uppercase;
    color: var(--text-secondary);
    font-variant-numeric: tabular-nums;
}

.speaking-audio-stage.is-repeat-sentence .speaking-phase-eyebrow {
    display: block;
}

.speaking-phase-eyebrow[hidden] {
    display: none;
}

.speaking-phase-eyebrow.is-active {
    color: var(--accent-action);
    font-weight: var(--font-weight-medium);
}

/* Inline lead-in variant: "The sentence: <text>" / "Your response: <words>" on a
   single line (used on the review screen to save vertical space). Sentence-case
   label at the text size; the text after it stays normal. */
.speaking-audio-stage.is-repeat-sentence .speaking-transcript-label--inline {
    display: inline;
    margin: 0;
    font-size: inherit;
    letter-spacing: normal;
    text-transform: none;
    font-weight: var(--font-weight-medium);
    color: var(--text-secondary);
}

/* =========================================================================
   Transcript (F4 Block B): the read passage annotated by alignment status,
   shown above the player in the result phase. Colours REUSE the app's result
   tokens so they match everywhere: omitted = incorrect red (--danger-text),
   misread/added = partial orange (--warning-accent), filler = accent blue.
   Hover detail (expected→actual) uses the shared InfoPopover (data-info-*).
   ========================================================================= */
.speaking-stage-transcript {
    display: none;
}

.speaking-audio-stage.is-result .speaking-stage-transcript {
    display: block;
    text-align: left;
    margin-bottom: var(--space-18);
}

/* Repeat Sentence renders its "Your response" in the feedback column (not the
   stage), so the in-stage transcript stays hidden even in the result phase. */
.speaking-audio-stage.is-repeat-sentence .speaking-stage-transcript {
    display: none;
}

/* Shared label style: Read Aloud's "Your reading" transcript header AND Repeat
   Sentence's "Your recording" track header (one rule, matched look). */
.speaking-transcript-label {
    margin: 0 0 var(--space-8);
    font-size: var(--font-size-072);
    font-weight: var(--font-weight-medium);
    letter-spacing: var(--tracking-eyebrow);
    text-transform: uppercase;
    color: var(--text-eyebrow);
}

.speaking-transcript-flow {
    margin: 0;
    font-size: var(--space-15);
    line-height: 1.9;
    color: var(--text-primary);
}

.speaking-word--matched {
    color: var(--text-primary);
}

.speaking-word--omitted {
    color: var(--danger-text);
    text-decoration: line-through;
    text-decoration-thickness: 1.5px;
}

.speaking-word--substituted,
.speaking-word--inserted {
    color: var(--warning-accent);
}

.speaking-word--filler {
    color: var(--accent-action);
}

/* Annotated words carry a hover popover — dotted underline hints "there's more"
   (omitted already reads via its line-through, so no extra underline there). */
.speaking-word--substituted[data-info-popover],
.speaking-word--inserted[data-info-popover],
.speaking-word--filler[data-info-popover] {
    cursor: help;
    text-decoration-line: underline;
    text-decoration-style: dotted;
    text-underline-offset: var(--space-3);
}

.speaking-word--omitted[data-info-popover] {
    cursor: help;
}

.speaking-word[data-info-popover]:focus-visible {
    outline: var(--border-focus-width) solid var(--accent-action);
    outline-offset: var(--space-2);
    border-radius: var(--space-2);
}

/* Colour key — only the statuses actually present are listed (built in JS). */
.speaking-transcript-legend {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-14);
    margin: var(--space-12) 0 0;
    font-size: var(--font-size-082);
    color: var(--text-secondary);
}

.speaking-legend-item {
    display: inline-flex;
    align-items: center;
    gap: var(--space-6);
}

.speaking-legend-dot {
    width: var(--space-8);
    height: var(--space-8);
    border-radius: var(--radius-pill);
    flex: 0 0 var(--space-8);
}

.speaking-legend-dot--omitted {
    background: var(--danger-text);
}

.speaking-legend-dot--substituted,
.speaking-legend-dot--inserted {
    background: var(--warning-accent);
}

.speaking-legend-dot--filler {
    background: var(--accent-action);
}

.speaking-audio-stage.is-submitting .speaking-stage-loading {
    display: block;
}

.speaking-audio-stage.is-result .speaking-stage-result {
    display: block;
    margin-top: var(--space-16);
    text-align: left;
}

/* actions: recorded (Submit + Record again), result, error, submit-failed */
.speaking-audio-stage.is-recorded .speaking-stage-actions,
.speaking-audio-stage.is-result .speaking-stage-actions,
.speaking-audio-stage.is-error .speaking-stage-actions,
.speaking-audio-stage.is-submitfailed .speaking-stage-actions {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    gap: var(--space-10);
    margin-top: var(--space-14);
}

/* Modest secondary — not a heavy primary button. */
.speaking-stage-again {
    font-weight: var(--font-weight-medium);
}

/* In the Repeat Sentence layout the stage state-slots sit below the mic. RS shows
   its graded feedback in #speakingResult and its actions in the shared row, so
   these slots stay empty — and their per-phase display:block + margins would open
   a gap under the mic once the result appears. Collapse them while empty (they're
   still shown when they DO carry content — the submit spinner / an error retry). */
.speaking-audio-stage.is-repeat-sentence .speaking-stage-loading:empty,
.speaking-audio-stage.is-repeat-sentence .speaking-stage-result:empty,
.speaking-audio-stage.is-repeat-sentence .speaking-stage-actions:empty {
    display: none;
}

.speaking-audio-stage.is-error .speaking-stage-error,
.speaking-audio-stage.is-submitfailed .speaking-stage-error {
    display: block;
}

/* Environment/setup problems (insecure context, unsupported browser, mic permission) are not failures of
   the reading — render them as quiet grey caps (same treatment as the Words-to-watch list), not alarm-red.
   A genuine submit failure stays red (it has a real retry). */
.speaking-audio-stage.is-error .speaking-stage-error {
    color: var(--text-secondary);
    font-size: 0.71875rem;
    font-weight: var(--font-weight-medium);
    letter-spacing: 0.14em;
    text-transform: uppercase;
    line-height: 1.6;
}

.speaking-audio-stage.is-submitfailed .speaking-stage-error {
    color: var(--danger-text);
}

@media (prefers-reduced-motion: reduce) {
    .speaking-audio-stage.is-recording .speaking-stage-mic::after {
        animation: none;
        opacity: 0.5; /* keep a static ring so "recording" stays visible without motion */
    }
}


/* ── Repeat Sentence: prompt player ("listen" phase) ──────────────────────────────────────────────
   A NON-interactive now-playing bar. The prompt plays once (no seek/replay), so this shows progress
   only — there is no play/pause control. Calm action-blue fill, matching the recording accent. */
.speaking-prompt {
    display: flex;
    align-items: center;
    gap: var(--space-12);
    max-width: 44rem;
    margin: var(--space-16) auto 0;
}

/* JS toggles the bar via the [hidden] attribute; an explicit guard so the author display:flex above
   doesn't override it (author rules otherwise beat the UA [hidden] rule). */
.speaking-prompt[hidden] {
    display: none;
}

.speaking-prompt-icon {
    display: inline-flex;
    color: var(--accent-action);
    flex: 0 0 auto;
}

.speaking-prompt-icon svg {
    width: var(--space-20);
    height: var(--space-20);
}

.speaking-prompt-track {
    flex: 1 1 auto;
    height: var(--space-6, 6px);
    border-radius: var(--radius-pill);
    background: var(--border-subtle);
    overflow: hidden;
}

.speaking-prompt-fill {
    height: 100%;
    width: 0%;
    border-radius: var(--radius-pill);
    background: var(--accent-action);
    transition: width 120ms linear;
}

/* ── Repeat Sentence: the reference sentence, revealed only on the review screen ───────────────────
   Read Aloud shows its passage from the start; Repeat Sentence keeps the sentence hidden until scored,
   then reveals it here with a small caption so it reads as "the target", not an instruction. */
.speaking-source-label {
    display: block;
    font-size: var(--font-size-082);
    letter-spacing: var(--tracking-eyebrow);
    text-transform: uppercase;
    color: var(--text-secondary);
    margin-bottom: var(--space-6, 6px);
}

.speaking-source-sentence {
    display: block;
}

/* ── Read Aloud: "Listen to model" ────────────────────────────────────────────────────────────────
   Plays the pre-generated passage narration (task.modelAudioUrl). Sits INSIDE the recording frame as
   its last child, pinned to the bottom-right corner, and only while the learner is reading — the
   prep phase. It is absent during the take (so the model can never be heard mid-recording) and absent
   on the result screen (owner decision 2026-08-05). speaking-state.js empties the row for a task with
   no narration, so :not(:empty) keeps the box from growing a blank strip. Ghost/secondary look —
   bordered-box button tokens, not the accent-filled primary; font-weight stays at the medium ceiling. */
.speaking-model-narration-row {
    display: none;
}

.speaking-audio-stage.is-prep .speaking-model-narration-row:not(:empty) {
    display: flex;
    justify-content: flex-end;
    margin-top: var(--space-14);
}

.speaking-model-narration {
    display: inline-flex;
    align-items: center;
    gap: var(--space-4);
    padding: var(--space-6) var(--space-14);
    border: 1px solid var(--border-strong);
    border-radius: var(--space-7);
    background: var(--color-surface-0);
    color: var(--text-primary);
    font-size: var(--font-size-082);
    font-weight: var(--font-weight-medium);
    line-height: 1.2;
    cursor: pointer;
}

.speaking-model-narration:hover:not(:disabled) {
    border-color: var(--accent-action-soft-border);
    color: var(--accent-action);
}

.speaking-model-narration:focus-visible {
    outline: var(--border-focus-width) solid var(--accent-action);
    outline-offset: var(--space-4);
}

@media (prefers-reduced-motion: reduce) {
    .speaking-prompt-fill {
        transition: none;
    }
}

/* ── Silent-take explainer ────────────────────────────────────────────────────────────────────────
   Shown under a recording where no voice was detected (the take auto-stopped on leading silence). Calm
   and instructional — a secondary caption, deliberately NOT an alert colour. Same in RA and RS. */
.speaking-silence-note {
    margin: var(--space-14) 0 0;
    font-size: var(--font-size-082);
    line-height: var(--line-height-relaxed, 1.5);
    color: var(--text-secondary);
    text-align: left;
}

/* ── Describe Image: the prompt image ─────────────────────────────────────────────────────────────
   Sits in the source block, in the passage's slot, and takes the SAME hairline + --radius-card as
   .speaking-audio-stage below it, so the screen reads as two stacked panels of one column: what you
   look at, then what you say. Full width (not shrink-wrapped) for exactly that reason — the two box
   edges have to line up.

   Aspect ratio: the frame never gives the picture a box of its own. Charts, maps and photos arrive
   at wildly different ratios, so the <img> keeps its intrinsic ratio and is only CAPPED — bounded by
   the column in width and by --size-speaking-image-max-height (viewport-relative) in height. Nothing
   is ever cropped, stretched or upscaled, so axis labels and numbers stay as crisp as the source.
   The white mount keeps a transparent PNG legible whatever surface ends up behind the frame. */
.speaking-image-frame {
    margin: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    /* Matte between border and picture — enough to read as a mount, not as another card. */
    padding: var(--space-16);
    border: var(--border-default-width) solid var(--border-default);
    border-radius: var(--radius-card);
    background: var(--bg-surface);
}

.speaking-image-frame img {
    display: block;
    /* Flex items refuse to shrink below their intrinsic width unless told to; without this a wide
       chart would push the frame past the column instead of scaling down inside it. */
    min-width: 0;
    max-width: 100%;
    max-height: var(--size-speaking-image-max-height);
    /* Guard only: with both maxes and auto sizing the ratio is already preserved, but this keeps it
       preserved if the picture ever gets an explicit box. */
    object-fit: contain;
}

/* Phones: every pixel of width is chart legibility (the task surface already drops its side padding
   here), so the mount tightens to a thin matte. The height cap needs no override — it is already
   viewport-relative, so it scales itself down on a small phone. */
@media (max-width: 767px) {
    .speaking-image-frame {
        padding: var(--space-8);
    }
}

/* ── Describe Image: fact coverage ────────────────────────────────────────────────────────────────
   Covered / missed reuse the transcript legend's 8 px dot and the shared semantic result colours, so
   green and red mean here exactly what they mean everywhere else on a result screen. The chip needs a
   gap only because it now holds a dot next to its label. */
.speaking-legend-dot--covered {
    background: var(--success-text);
}

.speaking-legend-dot--missed {
    background: var(--danger-text);
}

.speaking-fact-chip {
    gap: var(--space-6);
}

/* A chip with an authored sentence behind it is focusable, so it gets the same branded ring the
   annotated transcript's popover words use — a keyboard learner reaching these must not drop to the
   browser default one section below where the pattern is already branded. */
.speaking-fact-chip[data-info-popover] {
    cursor: help;
}

.speaking-fact-chip[data-info-popover]:focus-visible {
    outline: var(--border-focus-width) solid var(--accent-action);
    outline-offset: var(--space-2);
}
