/* ============================================================
   Learner stage geometry — docs/learner-stage-redesign-plan.md section 5.1.
   The stage is a CONTENT-HEIGHT card of at most --tp-stage-max, centred on the
   --page-bg canvas. Supersedes the 2026-08-19 fixed 65vw / 85dvh work order.
   ============================================================ */
:root {
    --tp-stage-max: 1160px;
    --tp-stage-gutter: clamp(12px, 3vw, 32px);   /* page to card; brief 12.6 asks 24-40px on desktop */
    --tp-stage-pad: clamp(16px, 2.6vw, 28px);     /* card internal padding; 20-32px on desktop */
    --tp-stage-gap: 20px;                          /* header to content, content to nav */
    --tp-video-max-h: min(62dvh, 640px);
    --tp-graphic-max-h: min(68dvh, 720px);
}

/* ============================================================
   Learner surface — My Training, the course player, the block
   players and the certificate. Ported alongside the pages from
   the main application, rewritten against the split tokens in
   bunkersafe-split.css rather than the legacy --pf-modern-* set.

   Linked AFTER bunkersafe-split.css in App.razor, so anything
   here that shares a name with a tm-* rule wins by source order.
   ============================================================ */

/* ---- My Training ---- */
.tl-course-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
    gap: 12px;
}

/* RadzenCard's own default is a white surface with a faint shadow, same as the
   tm-panel it sits inside — so a required-training row was nearly invisible
   against its own container. A tinted surface + border gives it a visible edge.
   One rule, three places: required/additional courses (tl-course-stack) and the
   certificate tiles (tl-course-grid) all use this class. */
.tl-course-card {
    display: flex;
    flex-direction: column;
    gap: 8px;
    background: var(--page-bg);
    border: 1px solid #DCE3EA;
}

/* Outstanding courses are full-width stretch cards, not tiles: thumbnail column,
   then title, progress and actions stacked beside it. Certificates keep the
   tl-course-grid tiles above. */
.tl-course-stack {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.tl-course-stack .tl-course-card {
    display: grid;
    grid-template-columns: 112px minmax(0, 1fr);
    gap: 16px;
    align-items: center;
}

.tl-course-thumb img,
.tl-course-thumb-fallback {
    width: 112px;
    height: 112px;
    border-radius: var(--radius);
    object-fit: cover;
}

.tl-course-thumb-fallback {
    display: flex;
    align-items: center;
    justify-content: center;
    background: var(--pastel-blue);
    color: var(--pastel-blue-ink);
    font-weight: 700;
    letter-spacing: .04em;
    text-align: center;
    padding: 6px;
}

.tl-course-body {
    display: flex;
    flex-direction: column;
    gap: 8px;
    min-width: 0;
}

.tl-course-progress {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.tl-course-actions {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 10px;
}

.tl-course-result {
    font-size: .82rem;
    color: #6B7A89;
}

.tl-course-result strong { color: var(--deep-harbor); }

@media (max-width: 560px) {
    .tl-course-stack .tl-course-card { grid-template-columns: 1fr; }

    .tl-course-thumb img,
    .tl-course-thumb-fallback {
        width: 100%;
        height: 132px;
    }
}

.tl-course-head {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 8px;
}

.tl-course-title {
    font-weight: 600;
    color: var(--deep-harbor);
}

.tl-progress-track {
    height: 8px;
    border-radius: 999px;
    background: #E4EAF0;
    overflow: hidden;
}

.tl-progress-fill {
    height: 100%;
    border-radius: 999px;
    background: var(--coastal-teal);
    transition: width .25s ease;
}

.tl-cert-lines {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 2px 10px;
    margin: 0;
    font-size: .82rem;
}

.tl-cert-lines dt { color: #6B7A89; }
.tl-cert-lines dd { margin: 0; font-weight: 600; color: var(--deep-harbor); }

/* ---- Course player shell ---- */
.tp-page {
    padding: 18px;
    max-width: 1500px;
    margin: 0 auto;
}

.tp-shell {
    display: flex;
    flex-direction: column;
    gap: 14px;
}

/* Fullscreen targets the stage (which wraps the blurred background), so the shell
   only has to stop constraining width. */
.tp-fullscreen { max-width: none; }

.tp-hero {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 16px;
    flex-wrap: wrap;
    padding: 16px 18px;
    border-radius: var(--radius);
    background: var(--sea-white);
    box-shadow: var(--shadow);
    border-bottom: 3px solid var(--signal-gold);
}

.tp-hero-main {
    flex: 1 1 320px;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.tp-hero-side {
    display: flex;
    flex-direction: column;
    gap: 10px;
    align-items: flex-end;
}

.tp-progress-card {
    min-width: 200px;
    padding: 10px 12px;
    border: 1px solid #E3E8ED;
    border-radius: 10px;
}

.tp-layout {
    display: grid;
    grid-template-columns: minmax(220px, 280px) minmax(0, 1fr) minmax(220px, 300px);
    gap: 14px;
    align-items: start;
}

.tp-journey-panel,
.tp-progress-panel,
.tp-content-panel {
    background: var(--sea-white);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    padding: 14px;
}

/* S4 (learner-stage redesign): the .tp-content-panel:fullscreen rule was deleted with
   the rest of the --tp-panel-bg system. The stage is the fullscreen element and the
   panel is a Sea White card inside it. */

.tp-panel-head,
.tp-panel-header-soft {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 10px;
    margin-bottom: 10px;
}

.tp-panel-header-kicker {
    font-size: .7rem;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: #6B7A89;
}

.tp-panel-header-title-row {
    display: flex;
    align-items: center;
    gap: 6px;
    font-weight: 600;
    color: var(--deep-harbor);
}

.tp-header-badge {
    font-size: .72rem;
    padding: 2px 8px;
    border-radius: 999px;
    background: var(--pastel-blue);
    color: var(--pastel-blue-ink);
}

/* S3 (learner-stage redesign): the 240px floor on the body is gone. A fixed minimum is
   exactly what leaves an empty field under a short component (brief section 3). */

.tp-block-stage {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.tp-block-title {
    font-size: 1.05rem;
    font-weight: 600;
    color: var(--deep-harbor);
}

/* No border and no padding: every block now renders its own RadzenCard, so this wrapper
   was drawing a second box around it — the white outline around each component in the
   player. It stays as a positioning hook only. */
.tp-block-card { padding: 0; }

.tp-nav-bar {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 8px;
    flex-wrap: wrap;
    margin-top: 14px;
    padding-top: 12px;
    border-top: 1px solid #E3E8ED;
}

/* Holds everything that is neither Previous nor Next, so the bar keeps its
   Previous-left / Next-right shape however many middle controls there are. */
.tp-nav-bar-mid {
    display: flex;
    align-items: center;
    gap: 8px;
}

.tp-callout {
    padding: 10px 12px;
    border-left: 3px solid var(--coastal-teal);
    background: #F4FAFB;
    border-radius: 6px;
}

/* Remember This / Standard Teaching Card / Operational Principle.
   --tp-card-accent-explicit / --tp-card-bg-raw / --tp-card-icon-size / --tp-icon-stroke are
   written inline per block by StyledCardBlockPlayer.razor (validated hex / clamped px).

   --tp-card-accent is derived HERE, not passed in, and every downstream rule reads only
   this derived value (never --tp-card-accent-explicit or --tp-card-bg-raw directly):
     1. an explicit author accent wins outright;
     2. otherwise, with a background set but no accent, resolving straight to that
        background made the heading underline and the icon collapse into their own card
        colour — measured contrast 1.00 at every accent, invisible by construction, not
        merely low-contrast. Darkening it toward deep-harbor keeps the "icon follows the
        card colour" intent while keeping it visible against that same card;
     3. with neither set, this still resolves (via the coastal-teal fallback) — a card with
        nothing set looks like a plain styled card, not broken.
   --tp-card-icon-ink is now --tp-card-accent ITSELF. It used to darken the accent 30/70
   toward deep-harbor, which is why an author who set a green accent got a navy-green glyph
   and a navy-green rule that plainly were not the colour they picked. That darkening existed
   to clear the 3:1 graphical-object floor against the old chip, which was a 14% tint of the
   accent (glyph-on-chip measured 2.65:1). The chip is no longer accent-tinted — it is a
   shade of the CARD background (--tp-card-icon-bg below) — so the accent no longer sits on a
   tint of itself and the reason for the second pass is gone. One name now drives the
   underline, the left rule, the tile edge and the glyph: what the author picks is what all
   four are.

   --tp-card-edge is the author's left-rule width, written inline per block. 0 is a legal
   authored value (no rule), which is why the fallback lives in the var() default and not in
   a truthiness test.

   --tp-card-icon-bg shades the tile slightly darker than the card so the icon reads as a
   raised chip rather than a hole punched in the surface. Derived from --tp-card-bg-raw, so
   it tracks whatever background the author sets; on a card with no background set it darkens
   the default white surface by the same amount, which is what makes the tile visible at all
   on a plain card. */
.tp-styled-card {
    padding: 22px 24px;
    --tp-card-accent: var(--tp-card-accent-explicit, color-mix(in srgb, var(--tp-card-bg-raw, var(--coastal-teal)) 65%, var(--deep-harbor)));
    --tp-card-icon-ink: var(--tp-card-accent);
    --tp-card-icon-bg: color-mix(in srgb, var(--tp-card-bg-raw, var(--sea-white)) 92%, var(--deep-harbor));
}

/* Per the design sheet ("Component Examples"), a teaching card is not a full-bleed slab:
   it is a bounded, readable measure. Its WIDTH is now set with the quiz shell further down
   (one rule for every non-Card component); this keeps only the centring. */
.tp-styled-card {
    margin-inline: auto;
}

/* CardBlockPlayer renders BOTH Card and TextNarrative, so the wide measure cannot live on this
   base class — TextNarrative is prose and keeps the bounded 760px. */
.tp-player-card {
    width: 100%;
    max-width: 760px;
    margin-inline: auto;
}

/* Everything that is NOT a plain Card — the styled-card family and the quiz/assessment/
   examination shell — is a component, not a page: it should read as a Radzen card sitting on
   the player surface, not as a slab spanning it. 65% of the panel on desktop, widening as the
   panel itself narrows, because a percentage of an already-narrow panel is not a card, it is a
   squeeze. Overrides the styled cards' earlier 760px cap: at a 1164px panel that cap WAS ~65%,
   but at smaller panels it silently became 100% and the family lost its shape. */
/* S5 (learner-stage redesign): the 65 / 80 / 100 percent tiers are gone. The stage itself
   is now the bounded measure (max 1160px), so a percentage of it made the question card a
   thin strip inside an otherwise empty screen — the defect brief 12.3 names. Every block
   card fills the stage body; prose keeps a readable measure INSIDE the card instead. */
.tp-styled-card,
.tp-quiz-shell {
    width: 100%;
    max-width: 100%;
    margin-inline: 0;
}

/* The Card block only, and it is not a styling preference — it is what the block IS. A Card
   commonly carries an uploaded slide or infographic with its explanation beneath, and a 760px
   measure shrank a full-width slide until its own body text was unreadable. Text wants a
   bounded measure; a picture wants the room. 90% rather than 100% keeps the card reading as a
   card (the panel's surface still shows around it) instead of a full bleed. */
.tp-player-card--wide {
    width: 90%;
    max-width: 90%;
}

/* Below the tablet tier the panel is already 97vw and there is no slack to give away: 90% of a
   ~360px body is ~324px, so the mobile card would surrender 10% of the only width it has. The
   wide measure buys room only where there is room to buy. */
@media (max-width: 767px) {
    .tp-player-card--wide {
        width: 100%;
        max-width: 100%;
    }
}

/* Two columns, three rows, via grid-template-areas rather than flex + DOM order — the
   previous flex layout put the header's underline wherever the TEXT ended (two lines of
   body copy pushed it well past the icon's bottom edge) and left the takeaway icon
   vertically misaligned against its own text. Column 1 is EXACTLY the icon's width (not a
   flex column that merely happens to match it), and the header/takeaway rows are pinned to
   at least the icon's own height via minmax's floor, so the underline sits at the icon's
   bottom edge by construction, not by the text happening to be short enough. minmax's
   ceiling is "auto", not the icon height itself: a wrapping heading or a long takeaway must
   be free to grow the row rather than clip.
   column-gap is what enforces the gap between icon and text — comfortably clears the 4px
   floor, reusing the 12px already used elsewhere in this card family rather than inventing
   a second gap constant. */
.tp-styled-card-grid {
    display: grid;
    grid-template-columns: var(--tp-card-icon-size, 48px) 1fr;
    grid-template-areas:
        "icon   header"
        ".      body";
    grid-template-rows:
        minmax(var(--tp-card-icon-size, 48px), auto)
        auto;
    column-gap: 12px;
}

/* Swaps the WHOLE area map rather than appending a row — grid-area placement does not care
   about DOM order, so the takeaway markup being present or absent in the .razor is what
   decides whether this class is applied, and the areas below decide where things land. No
   numeric grid-row bookkeeping to keep in step with that. */
.tp-styled-card-grid--with-takeaway {
    grid-template-areas:
        "icon    header"
        ".       body"
        "divider divider"
        "icon2   takeaway";
    grid-template-rows:
        minmax(var(--tp-card-icon-size, 48px), auto)
        auto
        auto
        minmax(var(--tp-card-icon-size, 48px), auto);
}

/* Centred in BOTH axes of its cell — the cell is exactly the icon's own footprint (column 1
   width, row height floored at the icon's height), so this centres against the icon's own
   bounding box, not against a taller neighbour. */
.tp-styled-card-icon-slot {
    grid-area: icon;
    display: flex;
    align-items: center;
    justify-content: center;
}

.tp-styled-card-icon-slot--takeaway {
    grid-area: icon2;
}

/* Border width == --tp-icon-stroke, the SAME constant passed to TrainingCardIcon as
   DeviceStroke — the circle's edge and the glyph's line are one value by construction, not
   two numbers that happen to agree (A3). */
.tp-styled-card-icon {
    display: inline-flex;
    flex-shrink: 0;
    align-items: center;
    justify-content: center;
    width: var(--tp-card-icon-size, 48px);
    height: var(--tp-card-icon-size, 48px);
    /* Rounded SQUARE, not a circle: the design sheet draws every card glyph in a soft-
       cornered square tile. A near-white fill (not the old 14% accent tint) keeps the tile
       reading as an outline the way the reference does. */
    border-radius: 14px;
    background: var(--tp-card-icon-bg, var(--sea-white));
    border: var(--tp-icon-stroke, 2px) solid var(--tp-card-accent);
    color: var(--tp-card-icon-ink);
}

/* Fills its cell (grid's default block-stretch already gives it the full row height) and
   centres its own text within that height with flex, so the border-bottom always lands at
   the BOTTOM of the row — which is the icon's own bottom edge, per the row's minmax floor. */
.tp-styled-card-heading {
    grid-area: header;
    display: flex;
    align-items: center;
    justify-content: flex-start;
    font-weight: 700;
    font-size: 1.15rem;
    line-height: 1.3;
    border-bottom: 2px solid var(--tp-card-accent);
    color: var(--deep-harbor);
    text-align: left;
}

.tp-styled-card-body {
    grid-area: body;
    align-self: center;
    line-height: 1.55;
    color: var(--deep-harbor);
    text-align: left;
}

.tp-styled-card-body > :first-child { margin-top: 0; }
.tp-styled-card-body > :last-child { margin-bottom: 0; }

.tp-styled-card-divider {
    grid-area: divider;
    margin: 6px 0;
    border: none;
    border-top: 1px solid var(--tp-card-hairline, #E3E8ED);
}

/* Same centring reasoning as the heading, and the same weight bump the takeaway always
   carried — it reads as the card's conclusion, matching "Key takeaway: ..." in the design
   sheet. Vertical centring now falls out of the grid cell instead of a flex override on a
   shared row class. */
.tp-styled-card-takeaway {
    grid-area: takeaway;
    align-self: center;
    font-weight: 600;
    line-height: 1.5;
    color: var(--deep-harbor);
    text-align: left;
}

.tp-completion {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 10px;
    padding: 24px 12px;
    text-align: center;
}

.tp-completion-title {
    font-size: 1.25rem;
    color: var(--deep-harbor);
    max-width: 640px;
}

/* ---- Course journey ---- */
.tp-journey-module { margin-bottom: 6px; }

.tp-journey-module-toggle,
.tp-journey-lesson {
    display: flex;
    align-items: center;
    gap: 6px;
    width: 100%;
    padding: 7px 9px;
    border: 1px solid transparent;
    border-radius: 8px;
    background: none;
    font: inherit;
    text-align: left;
    cursor: pointer;
    color: var(--anchor-gray);
}

.tp-journey-module-toggle {
    font-weight: 600;
    color: var(--deep-harbor);
}

.tp-journey-module-toggle:hover,
.tp-journey-lesson:hover:not(:disabled) { background: #F2F6FA; }

.tp-journey-module-toggle.is-open .tp-mod-chevron { transform: rotate(90deg); }

.tp-mod-chevron {
    margin-left: auto;
    transition: transform .15s ease;
}

.tp-journey-lessons { padding-left: 10px; }

.tp-journey-lesson.is-current {
    border-color: var(--coastal-teal);
    background: #F2F9FA;
    box-shadow: inset 3px 0 0 var(--coastal-teal);
}

.tp-journey-lesson.is-completed { color: var(--pastel-green-ink); }

.tp-journey-lesson:disabled {
    opacity: .55;
    cursor: not-allowed;
}

.tp-lesson-status-icon {
    display: inline-flex;
    flex: 0 0 auto;
}

/* ---- Lesson progress ---- */
.tp-progress-lesson-label {
    font-weight: 600;
    color: var(--deep-harbor);
    margin-bottom: 8px;
}

.tp-progress-group { margin-top: 10px; }

.tp-progress-group-head {
    display: flex;
    justify-content: space-between;
    font-size: .78rem;
    color: #6B7A89;
    margin-bottom: 4px;
}

.tp-lesson-progress-bar {
    height: 7px;
    border-radius: 999px;
    background: #E4EAF0;
    overflow: hidden;
}

.tp-lesson-progress-fill {
    height: 100%;
    border-radius: 999px;
    background: var(--coastal-teal);
}

.tp-lesson-progress-fill--course { background: var(--signal-gold); }

.tp-progress-block-list {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.tp-progress-block-item {
    display: flex;
    align-items: center;
    gap: 7px;
    padding: 5px 7px;
    border-radius: 6px;
    font-size: .82rem;
}

.tp-progress-block-item.is-current {
    background: #F2F9FA;
    font-weight: 600;
}

.tp-progress-block-item.is-completed { color: var(--pastel-green-ink); }

.tp-block-status {
    width: 14px;
    height: 14px;
    flex: 0 0 auto;
}

.tp-block-status--complete { color: var(--pastel-green-ink); }
.tp-block-status--inprogress { color: var(--coastal-teal); }
.tp-block-status--locked,
.tp-block-status--notstarted { color: #9AA7B4; }

.tp-progress-block-copy {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* ---- Quiz and assessment ---- */
.tp-quiz-question {
    padding: 10px 0;
    border-bottom: 1px solid #EDF1F5;
}

.tp-quiz-question:last-child { border-bottom: none; }

.tp-quiz-prompt {
    font-weight: 600;
    color: var(--deep-harbor);
    margin-bottom: 6px;
}

.tp-quiz-options {
    display: flex;
    flex-direction: column;
    gap: 5px;
}

/* Border is transparent at rest — a static light-grey (#DCE3EA) edge read as a stark white
   outline on the styled cards' tinted/dark surfaces, boxing in every option row. Hover and
   selected keep a real, meaningful border (the accent colour); resting rows are separated
   by spacing and background alone. */
.tp-quiz-option {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 7px 10px;
    border: 1px solid transparent;
    background: color-mix(in srgb, currentColor 4%, transparent);
    border-radius: 8px;
    cursor: pointer;
}

.tp-quiz-option:hover { border-color: var(--tp-quiz-accent, var(--coastal-teal)); }

.tp-quiz-option.is-selected {
    border-color: var(--tp-quiz-accent, var(--coastal-teal));
    background: color-mix(in srgb, var(--tp-quiz-accent, var(--coastal-teal)) 10%, white);
}

.tp-quiz-option input[type="radio"],
.tp-quiz-option input[type="checkbox"] {
    accent-color: var(--tp-quiz-accent, var(--coastal-teal));
}

/* Practice Quiz / Assessment / Examination icon + coloured left border. --tp-quiz-accent,
   --tp-quiz-border-width and --tp-quiz-pad are written inline ONLY when the author has
   configured this block's style (accent validated hex, width clamped 1-10 on save); unset,
   the border is zero-width and transparent, so every block that never opted in renders
   exactly as it did before this existed. */
/* --tp-quiz-icon-ink darkens the accent for the glyph only, same ratio and same reason as
   --tp-card-icon-ink on the styled-card family (Phase 1): the chip is a 14% tint of the
   accent — identical formula to the styled card's — so the glyph-on-chip contrast fails the
   same way (measured 2.65:1 for the plain default teal) unless the glyph reads a separately
   darkened value. Border and header text keep reading --tp-quiz-accent directly. */
.tp-quiz-shell {
    border-left: var(--tp-quiz-border-width, 0) solid var(--tp-quiz-accent, transparent);
    border-radius: 6px;
    padding-left: var(--tp-quiz-pad, 0);
    /* The accent ITSELF, not a darkened partner. The 30/70 mix toward deep-harbor meant an
       author who set an orange accent got a muddy navy-orange glyph that plainly was not the
       colour they chose — the same defect already corrected on the styled cards. */
    --tp-quiz-icon-ink: var(--tp-quiz-accent, var(--coastal-teal));
}

.tp-quiz-shell-header {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-bottom: 10px;
    color: var(--tp-quiz-accent, var(--coastal-teal));
}

/* Size and border now follow the same author-configurable contract as the styled-card
   icons (Phase 1/2): --tp-quiz-icon-size (34px default, matching the pre-existing hardcoded
   diameter) and --tp-icon-stroke (the same 2px constant passed to TrainingCardIcon as
   DeviceStroke, so the circle's edge and the glyph's line agree by construction). */
.tp-quiz-shell-icon {
    display: inline-flex;
    flex-shrink: 0;
    align-items: center;
    justify-content: center;
    width: var(--tp-quiz-icon-size, 34px);
    height: var(--tp-quiz-icon-size, 34px);
    border-radius: 999px;
    background: color-mix(in srgb, var(--sea-white) 92%, var(--deep-harbor));
    border: var(--tp-icon-stroke, 2px) solid var(--tp-quiz-accent, var(--coastal-teal));
    color: var(--tp-quiz-icon-ink, var(--tp-quiz-accent, var(--coastal-teal)));
}

.tp-quiz-shell-title {
    font-weight: 700;
    color: var(--deep-harbor);
}

.tp-quiz-result {
    padding: 10px 12px;
    border-radius: 8px;
    font-weight: 600;
}

.tp-quiz-result--pass { background: var(--pastel-green); color: var(--pastel-green-ink); }
.tp-quiz-result--fail { background: var(--pastel-red); color: var(--pastel-red-ink); }

/* ---- Flash cards ---- */
.tp-flashcard-scene {
    perspective: 1200px;
    margin: 10px 0;
}

.tp-flashcard-inner {
    position: relative;
    min-height: 200px;
    transition: transform .5s;
    transform-style: preserve-3d;
    cursor: pointer;
}

.tp-flashcard-inner.is-flipped { transform: rotateY(180deg); }

.tp-flashcard-face {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    padding: 16px;
    border: 1px solid #DCE3EA;
    border-radius: 12px;
    background: var(--sea-white);
    backface-visibility: hidden;
    text-align: center;
}

.tp-flashcard-face--back {
    transform: rotateY(180deg);
    background: #F4FAFB;
}

.tp-flashcard-label {
    font-size: .7rem;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: #6B7A89;
}

.tp-flashcard-text {
    font-size: 1rem;
    color: var(--anchor-gray);
}

.tp-flashcard-image {
    max-width: 100%;
    max-height: 130px;
    border-radius: 8px;
}

.tp-flashcard-flip-hint,
.tp-flashcard-lock-msg {
    font-size: .78rem;
    color: #6B7A89;
}

/* ---- Video and slides ---- */
.tp-video-container video { width: 100%; border-radius: 8px; }

.tp-avatar-container video {
    width: 100%;
    max-width: 320px;
    border-radius: 8px;
}

.tp-video-complete-badge {
    display: inline-block;
    margin-top: 8px;
    padding: 3px 10px;
    border-radius: 999px;
    background: var(--pastel-green);
    color: var(--pastel-green-ink);
    font-size: .78rem;
    font-weight: 600;
}

.tp-slide-presentation {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.tp-slide-title {
    font-size: 1.05rem;
    font-weight: 600;
    color: var(--deep-harbor);
}

.tp-slide-layout {
    display: grid;
    gap: 12px;
}

.tp-slide-body { min-width: 0; }

/* ---- Certificate ---- */
.tc-shell {
    max-width: 820px;
    margin: 0 auto;
    padding: 28px 32px;
    background: var(--sea-white);
    border: 2px solid var(--signal-gold);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    text-align: center;
}

/* Brand lockup: mark and wordmark share one optical baseline, with the descriptor
   hanging under the wordmark only — the same rule the app shell's brand follows. */
.tc-brand {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 12px;
    padding-bottom: 14px;
    margin-bottom: 18px;
    border-bottom: 1px solid #E3E8ED;
}

.tc-brand-mark { width: 48px; height: 48px; object-fit: contain; }
.tc-brand-copy { text-align: left; }

.tc-brand-name {
    font-size: 1.25rem;
    font-weight: 700;
    letter-spacing: .02em;
    color: var(--deep-harbor);
    line-height: 1.1;
}

.tc-brand-sub {
    font-size: .74rem;
    text-transform: uppercase;
    letter-spacing: .08em;
    color: #6B7A89;
}

.tc-title {
    font-size: 1.5rem;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--deep-harbor);
    margin-bottom: 18px;
}

/* Details on the left, QR pinned right — a verifier scans without reading anything. */
.tc-main {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 148px;
    gap: 20px;
    align-items: center;
    margin-top: 14px;
}

.tc-qr { margin: 0; text-align: center; }
.tc-qr img { width: 132px; height: 132px; }

.tc-qr figcaption {
    font-size: .72rem;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: #6B7A89;
    margin-top: 4px;
}

.tc-status-valid { color: #1B7F4B; }
.tc-status-invalid { color: #B3261E; }

.tc-verify-form {
    display: flex;
    gap: 10px;
    flex-wrap: wrap;
    align-items: center;
}

.tc-verify-form .tm-input { flex: 1 1 260px; }

@media (max-width: 620px) {
    .tc-main { grid-template-columns: 1fr; justify-items: center; }
}

.tc-body h2 {
    font-size: 1.7rem;
    color: var(--deep-harbor);
    margin: 8px 0;
}

.tc-body h3 {
    font-size: 1.2rem;
    color: var(--coastal-teal);
    margin: 8px 0;
}

.tc-meta {
    font-size: .85rem;
    color: #6B7A89;
}

.tc-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
    gap: 12px;
    margin: 20px 0;
    text-align: left;
}

.tc-label {
    display: block;
    font-size: .7rem;
    text-transform: uppercase;
    letter-spacing: .05em;
    color: #6B7A89;
}

.tc-footer {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-top: 18px;
    padding-top: 14px;
    border-top: 1px solid #E3E8ED;
    text-align: left;
}

@media print {
    /* Only the certificate itself should reach the paper. */
    .bs-topbar,
    .bs-footer,
    .tc-no-print { display: none !important; }

    .tc-shell {
        border: none;
        box-shadow: none;
        max-width: none;
    }
}

@media (max-width: 1100px) {
    .tp-layout { grid-template-columns: 1fr; }
}

/* ============================================================
   Player: blurred background, intro/conclusion steps, sticky nav
   ============================================================ */

/* Fixed and behind everything. It is a sibling of the content rather than a
   parent background so the blur never reaches the lesson text. The slight
   scale hides the transparent fringe blur leaves at the edges. */
.tp-bg {
    position: fixed;
    inset: 0;
    z-index: 0;
    background-size: cover;
    background-position: center;
    filter: blur(8px) brightness(0.6);
    transform: scale(1.03);
    pointer-events: none;
}

.tp-page--has-bg {
    position: relative;
    z-index: 1;
}

/* Over a photograph the panels must stay opaque: text contrast has to come from
   the panel, never from the image behind it. */
.tp-page--has-bg .tp-hero,
.tp-page--has-bg .tp-journey-panel,
.tp-page--has-bg .tp-progress-panel,
.tp-page--has-bg .tp-content-panel {
    background: rgba(255, 255, 255, .98);
    box-shadow: 0 8px 30px rgba(11, 45, 77, .28);
}

.tp-intro,
.tp-conclusion {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 12px;
    padding: 8px 4px 4px;
    max-width: 760px;
}

.tp-intro-title {
    margin: 0;
    font-size: 1.5rem;
    color: var(--deep-harbor);
}

.tp-prose { line-height: 1.55; }
.tp-prose img { max-width: 100%; height: auto; border-radius: 6px; }

.tp-result-table {
    width: 100%;
    border-collapse: collapse;
    font-size: .9rem;
}

.tp-result-table th,
.tp-result-table td {
    text-align: left;
    padding: 8px 10px;
    border-bottom: 1px solid #E3E8ED;
}

.tp-result-table th { color: #6B7A89; font-weight: 600; }

/* S8 (learner-stage redesign): the sticky rule moved to the stage block at the end of
   this file, where the nav is the last child of a content-height card. .is-collapsed was
   deleted outright — no control has rendered that class since the collapse toggle went. */

/* Interactive card widgets */
.tp-card-visual {
    max-width: 100%;
    border-radius: 4px;
    margin-bottom: .75rem;
}

.tp-widget { margin-top: 12px; display: flex; flex-direction: column; gap: 8px; }
.tp-widget-prompt { margin-top: 10px; font-weight: 600; color: var(--deep-harbor); }

/* --tp-item-bg is written inline per widget (block-scoped — one colour for every item in
   an Interactive Card). Left unset, items shade a step darker than the card's own
   background so they read as distinct surfaces even with nothing authored. */
.tp-reveal-panel {
    background: var(--tp-item-bg, color-mix(in srgb, var(--tp-card-bg, #F4FAFB) 88%, #0B2D4D));
    border: 1px solid #E3E8ED;
    border-radius: 8px;
    overflow: hidden;
}

.tp-reveal-panel.is-open {
    border-color: var(--coastal-teal);
    box-shadow: 0 1px 3px rgba(11, 45, 77, .12);
}

.tp-reveal-head {
    display: flex;
    justify-content: space-between;
    align-items: center;
    width: 100%;
    gap: 8px;
    padding: 10px 12px;
    background: none;
    border: 0;
    font: inherit;
    font-weight: 600;
    color: var(--deep-harbor);
    text-align: left;
    cursor: pointer;
}

.tp-reveal-head:hover {
    background: rgba(11, 45, 77, .04);
}

.tp-reveal-body { padding: 0 12px 12px; }

.tp-hotspot-tabs { display: flex; flex-wrap: wrap; gap: 6px; }

.tp-hotspot-dot {
    width: 32px;
    height: 32px;
    border-radius: 999px;
    border: 1px solid var(--coastal-teal);
    background: var(--sea-white);
    color: var(--deep-harbor);
    font-weight: 600;
    cursor: pointer;
}

.tp-hotspot-dot.is-active {
    background: var(--coastal-teal);
    color: var(--sea-white);
}

.tp-hotspot-body {
    background: var(--tp-item-bg, color-mix(in srgb, var(--tp-card-bg, #F4FAFB) 88%, #0B2D4D));
    border: 1px solid #E3E8ED;
    border-radius: 8px;
    padding: 12px;
}

.tp-quiz-number { margin-right: 4px; font-weight: 600; }

/* ---- Paper runner: one question at a time ---- */
.tp-paper-progress {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin: 10px 0 14px;
}

.tp-paper-progress-label {
    font-size: .82rem;
    font-weight: 600;
    color: var(--deep-harbor);
}

/* A single fill says how far through the paper the learner is — never whether an answer
   was right. No result exists until the whole paper is submitted. Shared by the runner
   (Assessment/Examination) and the quiz's one-question-per-page flow via PaperProgressBar. */
.tp-paper-progress-track {
    position: relative;
    height: 8px;
    border-radius: 999px;
    background: #D7DEE5;
    overflow: hidden;
}

.tp-paper-progress-fill {
    position: absolute;
    inset: 0 auto 0 0;
    width: 0%;
    border-radius: 999px;
    /* Follows the block's accent where there is one — the bar belongs to the quiz it is
       counting, so a hardcoded teal put a teal progress bar on an orange paper. Falls back
       to teal outside a styled block. */
    background: var(--tp-quiz-accent, var(--coastal-teal));
    transition: width .25s ease;
}

.tp-paper-nav {
    display: flex;
    justify-content: space-between;
    gap: 8px;
    margin-top: 14px;
}

/* ---- End-of-course result (not passed) ---- */
.tp-final-scores {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin: 12px 0;
}

/* The expandable per-question review under a final-score row. Indented and left-ruled so
   it reads as belonging to the row above it, not as another row in the list. */
.tp-final-summary {
    margin: 2px 0 8px 12px;
    padding: 8px 0 0 12px;
    border-left: 2px solid #DCE3EA;
}

.tp-final-score-row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 12px;
    flex-wrap: wrap;
    padding: 8px 10px;
    border: 1px solid #DCE3EA;
    border-radius: 8px;
    background: var(--sea-white);
}

@media (prefers-reduced-motion: reduce) {
    .tp-nav-bar { transition: none; }
}

@media (max-width: 640px) {
    .tp-page { padding: 10px; }
    .tp-intro-title { font-size: 1.25rem; }
    .tp-nav-bar { gap: 6px; }
}

/* The verification form is a plain GET form (no Blazor circuit on a public page), so its
   submit button is a bare <button> and needs the button styling written out. */
.tm-btn-verify {
    padding: 8px 18px;
    border: 0;
    border-radius: var(--radius);
    background: var(--coastal-teal);
    color: var(--sea-white);
    font: inherit;
    font-weight: 600;
    cursor: pointer;
}

.tm-btn-verify:hover { filter: brightness(1.05); }

/* Public (anonymous) header — PublicLayout, used by certificate verification. */
.bs-public-header {
    display: flex;
    align-items: center;
    padding: 12px 20px;
    background: var(--sea-white);
    border-bottom: 3px solid var(--signal-gold);
}

.bs-public-brand {
    display: flex;
    align-items: center;
    gap: 10px;
    font-weight: 700;
    font-size: 1.1rem;
    color: var(--deep-harbor);
    text-decoration: none;
}

.bs-public-brand img { width: 34px; height: 34px; object-fit: contain; }

.tc-verify-note { max-width: 820px; margin: 12px auto 0; text-align: center; }

/* ============================================================
   S7 + S4 + S10 (learner-stage redesign): the whole Focus-mode block was deleted —
   the harbour-gradient :fullscreen background, the fixed overflow-hidden stage, the
   .tp-page/.tp-shell/.tp-layout centring chain, the --tp-panel-bg panel/glass/bare
   fills, and the .tp-panel-head suppression. The stage is now a light scroll container
   holding a Sea White content-height card; see the block at the end of this file.
   ============================================================ */

.tp-conclusion-headline {
    font-size: 1.05rem;
    font-weight: 700;
    letter-spacing: .01em;
    margin: 10px 0 4px;
}

.tp-conclusion-headline.is-pass { color: #1B7F4B; }
.tp-conclusion-headline.is-fail { color: #B3261E; }

/* ---- Lesson conclusion verdict ----
   One mark, read before any words: the whole page is the author's closing message plus
   the scores that count, so the pass/fail signal has to carry on its own. */
.tp-verdict {
    width: 68px;
    height: 68px;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 2.1rem;
    line-height: 1;
    font-weight: 700;
    margin: 0 0 14px;
}

.tp-verdict.is-pass { background: var(--pastel-green); color: var(--pastel-green-ink); }
.tp-verdict.is-fail { background: var(--pastel-red); color: var(--pastel-red-ink); }

/* Mark + status copy on one row; the copy stacks under the mark on a phone. */
.tp-conclusion-hero {
    display: flex;
    align-items: center;
    gap: 18px;
    flex-wrap: wrap;
}

.tp-conclusion-hero .tp-verdict { margin: 0; flex: 0 0 auto; }

.tp-conclusion-copy { display: flex; flex-direction: column; gap: 2px; min-width: 0; }

.tp-conclusion-eyebrow {
    font-size: .72rem;
    font-weight: 700;
    letter-spacing: .12em;
    text-transform: uppercase;
}

.tp-conclusion-eyebrow.is-pass { color: var(--pastel-green-ink); }
.tp-conclusion-eyebrow.is-fail { color: var(--pastel-red-ink); }

.tp-conclusion-hero .tp-conclusion-headline {
    margin: 0;
    font-size: 1.5rem;
    line-height: 1.2;
}

.tp-conclusion-sub { margin: 0; color: var(--deep-harbor); opacity: .78; }

.tp-result-total { margin-top: 10px; }

/* ---- Quiz feedback page ----
   Shown after the learner answers and presses NEXT. Nothing here is scored; the colours
   say correct/incorrect, not pass/fail. */
.tp-quiz-foot { margin-top: 12px; }

.tp-quiz-feedback {
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.tp-quiz-verdict {
    border-radius: 10px;
    padding: 12px 14px;
    border-left: 4px solid transparent;
    background: var(--tp-card-bg, #F4FAFB);
}

.tp-quiz-verdict.is-correct {
    border-left-color: var(--pastel-green-ink);
    background: var(--pastel-green);
    color: var(--pastel-green-ink);
}

.tp-quiz-verdict.is-incorrect {
    border-left-color: var(--pastel-red-ink);
    background: var(--pastel-red);
    color: var(--pastel-red-ink);
}

.tp-quiz-verdict-head {
    display: flex;
    align-items: center;
    gap: 8px;
    font-weight: 700;
    letter-spacing: .04em;
    font-size: .82rem;
}

.tp-quiz-verdict-mark { font-size: 1.15rem; line-height: 1; }
.tp-quiz-verdict-prompt { margin-top: 6px; font-weight: 600; }

.tp-quiz-verdict-answers {
    margin: 8px 0 0;
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 2px 10px;
    font-size: .9rem;
}

.tp-quiz-verdict-answers dt { font-weight: 600; opacity: .78; }
.tp-quiz-verdict-answers dd { margin: 0; }

.tp-quiz-verdict-note {
    margin-top: 10px;
    padding-top: 8px;
    border-top: 1px solid rgba(0, 0, 0, .12);
}

@media (max-width: 560px) {
    .tp-quiz-verdict-answers { grid-template-columns: 1fr; }
    .tp-quiz-verdict-answers dt { margin-top: 4px; }
}

/* ---- Author-controlled card background ----
   --tp-card-bg is written inline per block; the fallback is the callout's original
   tint, so a card with nothing set looks exactly as it always did. */
.tp-callout { background: var(--tp-card-bg, #F4FAFB); }

.tp-content-panel .tp-callout--glass {
    background: var(--tp-card-bg, rgba(255, 255, 255, .72));
    backdrop-filter: blur(2px);
}

.tp-content-panel .tp-callout--bare {
    background: transparent;
    border-left-color: rgba(255, 255, 255, .5);
    color: var(--sea-white);
    text-shadow: 0 1px 3px rgba(0, 0, 0, .75);
}

/* S4 (learner-stage redesign): the three nav-bar fill variants went with the
   --tp-panel-bg system. The stage card is Sea White, so the nav is Sea White. */

/* Lesson editor: colour picker row */
.tm-panel-colour-row {
    display: flex;
    align-items: center;
    gap: 8px;
}

.tm-colour-input {
    width: 46px;
    height: 34px;
    padding: 2px;
    border: 1px solid #D3DCE5;
    border-radius: 6px;
    background: var(--sea-white);
    cursor: pointer;
}

/* ============================================================
   Player shell — constant size, internal scroll (Phase 6)
   The player must NOT resize when the block content changes: its
   dimensions come from the viewport, never from what is inside it,
   and overflow scrolls in the body region only. Percentages are of
   the SCREEN in fullscreen (where the stage fills it) and of the
   viewport otherwise, which is the same number to the learner.
   ============================================================ */
/* Two rows now: the brand/title/operator row unchanged in shape, and — when the current
   lesson's module has more than one lesson — a stepper row beneath it. Column, not the flex
   row this used to be directly, so the stepper can span the full header width instead of
   being squeezed into whatever space the title row leaves it. */
.tp-player-header {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: 8px 14px;
    border-radius: var(--radius);
    background: var(--sea-white);
    box-shadow: var(--shadow);
}

.tp-player-header-top {
    display: flex;
    align-items: center;
    gap: 12px;
}

.tp-player-brand { width: 32px; height: 32px; object-fit: contain; flex-shrink: 0; }

.tp-player-header-title { flex: 1 1 auto; min-width: 0; }

.tp-player-header-course {
    font-weight: 700;
    color: var(--deep-harbor);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.tp-player-header-lesson {
    font-size: .78rem;
    color: #6B7A89;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* Bounded so a large operator logo cannot drive the header's height — the header is part
   of the fixed shell, and a taller header would eat the content region. */
.tp-player-operator-logo {
    max-height: 34px;
    max-width: 132px;
    object-fit: contain;
    flex-shrink: 0;
}

.tp-player-operator-name {
    font-size: .8rem;
    font-weight: 600;
    color: var(--deep-harbor);
    flex-shrink: 0;
}

/* S1 + S2 (learner-stage redesign): the fixed-geometry block that lived here —
   .tp-stage--immersive .tp-page, the 65/80/97vw + 85dvh .tp-content-panel tiers, the
   safe-centred scrolling .tp-content-body, and the :fullscreen restatement of the same
   tiers — was deleted wholesale. Measured before removal: the 85dvh never applied at any
   viewport, because max-height:100% always won. The replacement is the stage block at the
   end of this file. See docs/learner-stage-redesign-plan.md section 3. */

/* Info dialog: the Contents panel inside it scrolls on its own so a long course cannot
   push the dialog's action buttons off screen. */
.tp-info-modal { max-width: min(720px, 94vw); }

.tp-info-contents {
    max-height: 46dvh;
    overflow-y: auto;
    margin: 12px 0;
}

/* ============================================================
   Block cards as RadzenCards
   StyledCardBlockPlayer / CardBlockPlayer / QuizBlockPlayer /
   ExamPaperRunner all render <RadzenCard> now, matching the
   home-page launcher tiles. These rules give the card family its
   surface and strip the chrome that used to be drawn twice.
   ============================================================ */

/* The authored per-block colour still wins; the fallback is the card tint the callout
   used, so nothing an author already styled changes appearance. Radzen's own border is
   removed — the shadow alone separates the card from the panel, exactly as on the home
   page, and a border here was the second outline around every component. */
.tp-styled-card.rz-card,
.tp-player-card.rz-card,
.tp-quiz-shell.rz-card {
    background: var(--tp-card-bg, var(--sea-white));
    /* A DEFINED edge plus a small shadow, per the design sheet — every card there is drawn
       with a visible hairline, not shadow alone. The earlier border:none left the card
       floating with no edge at all against a pale panel, which is what read as "no styling".
       The hairline follows the card's own accent at low opacity so it belongs to the card
       rather than being a generic grey applied on top of it. */
    border: 1px solid var(--tp-card-hairline, color-mix(in srgb, var(--tp-card-accent, var(--coastal-teal)) 26%, #DCE5EE));
    border-radius: var(--radius);
    box-shadow: 0 1px 2px rgba(11, 45, 77, .06), 0 6px 16px rgba(11, 45, 77, .06);
}

/* The design gives each teaching pattern a thick accent rule down its left edge — the one
   mark that tells the three card types apart at a glance (teal for Operational Principle,
   gold for Practice Quiz, and so on). It reads the same authored accent everything else
   on the card does, so an author sets one colour and the whole card follows. */
.tp-styled-card.rz-card {
    border-left: var(--tp-card-edge, 5px) solid var(--tp-card-accent);
}

/* The quiz/exam family keeps its authored accent as a left rule — its one piece of
   identity — layered on the card rather than replacing the card's own edge. */
.tp-quiz-shell.rz-card {
    border-left: var(--tp-quiz-border-width, 0) solid var(--tp-quiz-accent, transparent);
    background: var(--tp-card-bg, var(--sea-white));
    padding-left: var(--tp-quiz-pad, 16px);
}

/* Transparent/glass fills are author choices and must still beat the card's own surface,
   so they are qualified one level deeper to outrank the rules above. */
.tp-content-panel .tp-styled-card.rz-card.tp-callout--glass,
.tp-content-panel .tp-player-card.rz-card.tp-callout--glass {
    background: var(--tp-card-bg, rgba(255, 255, 255, .72));
    backdrop-filter: blur(2px);
}

.tp-content-panel .tp-styled-card.rz-card.tp-callout--bare,
.tp-content-panel .tp-player-card.rz-card.tp-callout--bare {
    background: transparent;
    box-shadow: none;
    color: var(--sea-white);
    text-shadow: 0 1px 3px rgba(0, 0, 0, .75);
}

/* The panel's own chrome. The player is one surface: the block card inside it already
   carries a card edge, so the panel does not need a second one around the stage. */
.tp-stage--immersive .tp-block-card,
.tp-stage:fullscreen .tp-block-card {
    border: none;
    padding: 0;
    background: transparent;
}

/* ============================================================
   Preview-only "this content is switched off" notice.
   Module preview deliberately renders inactive lessons and blocks (the builder edits them,
   so hiding them made preview look like it was replaying an older version). It has to say
   so. Deliberately NOT .rz-alert: .tp-stage:fullscreen hides those, and fullscreen is the
   normal playback state.
   ============================================================ */
.tp-inactive-note {
    display: block;
    margin: 0 0 12px;
    padding: 8px 12px;
    border: 1px solid var(--signal-gold, #C8961E);
    border-left: 4px solid var(--signal-gold, #C8961E);
    border-radius: var(--radius-sm, 6px);
    background: color-mix(in srgb, var(--signal-gold, #C8961E) 12%, var(--sea-white, #fff));
    color: var(--text-heading, #0B2D4D);
    font-size: .875rem;
    line-height: 1.4;
}

.tp-player-header-lesson .tp-inactive-badge { margin-left: 8px; vertical-align: middle; }

/* ============================================================
   Header lesson stepper — visibility for the one thing the immersive stage does not hide.
   Reuses the exact colour vocabulary LessonProgressPanel already established for
   completed/current/locked (--pastel-green-ink, --coastal-teal, #9AA7B4), so a learner who
   has seen that sidebar recognises the same states here without learning a new palette.
   overflow-x is the answer to "fits the screen size": a module can hold far more lessons
   than a ~350px mobile header can show as full-size dots, and clamping dot size instead
   would eventually make them untappable/unreadable — scrolling never does either. Hidden
   scrollbar because this is a status strip, not a control the learner is meant to drag.
   ============================================================ */
.tp-header-stepper {
    display: flex;
    align-items: center;
    overflow-x: auto;
    scrollbar-width: none;
    padding: 1px 2px;
    -webkit-overflow-scrolling: touch;
}

.tp-header-stepper::-webkit-scrollbar { display: none; }

.tp-header-step {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
}

.tp-header-step-dot {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    border: 2px solid #C7D1DB;
    color: #6B7A89;
    background: var(--sea-white);
    font-size: .62rem;
    font-weight: 700;
    line-height: 1;
}

/* Connector reads the segment it FOLLOWS, matching how a stepper's fill normally reads —
   the line between two completed steps is solid, the line leading into an unfinished step
   is not. */
.tp-header-step-connector {
    flex: 0 0 14px;
    height: 2px;
    background: #C7D1DB;
}

.tp-header-step-connector.is-completed { background: var(--pastel-green-ink); }

.tp-header-step.is-completed .tp-header-step-dot {
    border-color: var(--pastel-green-ink);
    background: var(--pastel-green-ink);
    color: var(--sea-white);
}

.tp-header-step.is-current .tp-header-step-dot {
    border-color: var(--coastal-teal);
    color: var(--coastal-teal);
    /* A visible ring, not just a border-colour change — the current step has to read as
       "you are here" at a glance among a row of otherwise near-identical dots. */
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--coastal-teal) 18%, transparent);
}

.tp-header-step.is-locked .tp-header-step-dot {
    border-color: #D7DEE5;
    color: #9AA7B4;
    background: #F3F5F7;
}


/* ============================================================
   LEARNER STAGE — docs/learner-stage-redesign-plan.md section 5.3.
   Replaces the 2026-08-19 work order (fixed 85dvh / 65vw / vertically centred /
   authored-colour panel). Phase 1: tokens, canvas, stage card, body, nav basics.
   The header zone and the per-type card rules land in phases 2 and 3.
   ============================================================ */

/* The root covers the app chrome, is the fullscreen element, and is now the ONLY
   scroller. Content-height cards mean the page itself can grow past the viewport,
   so something has to scroll; it is this, not the body region. */
.tp-stage--immersive {
    position: fixed;
    inset: 0;
    z-index: 5;
    overflow-y: auto;
    overflow-x: hidden;
    background: var(--page-bg);
    -webkit-overflow-scrolling: touch;
}

html:has(.tp-stage--immersive),
body:has(.tp-stage--immersive) { overflow: hidden; }

/* No geometry here on purpose: the stage already fills the screen in fullscreen, and
   the card inside it is sized by the same tokens either way. */
.tp-stage:fullscreen { background: var(--page-bg); }

/* An authored backdrop still shows, but lightened, so the canvas reads as light rather
   than as the dark harbour slab the old focus mode painted. */
.tp-stage--immersive .tp-bg {
    filter: blur(10px) brightness(1.05) saturate(.85);
    opacity: .35;
}

.tp-stage--immersive .tp-page {
    padding: 0;
    margin: 0;
    max-width: none;
    width: 100%;
    min-width: 0;
}

.tp-stage--immersive .tp-shell {
    display: flex;
    flex-direction: column;
    gap: 0;
    min-width: 0;
}

.tp-stage--immersive .tp-layout { display: block; }

.tp-stage--immersive .tp-progress-panel { display: none; }

/* THE STAGE CARD. Height follows content — no dvh, no minimum, no centring. */
.tp-stage--immersive .tp-content-panel {
    box-sizing: border-box;
    width: min(100% - 2 * var(--tp-stage-gutter), var(--tp-stage-max));
    max-width: var(--tp-stage-max);
    height: auto;
    min-height: 0;
    margin: var(--tp-stage-gutter) auto;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 0;
    background: var(--sea-white);
    border: 1px solid #DCE5EE;
    border-radius: var(--radius);
    box-shadow: 0 1px 2px rgba(11, 45, 77, .06), 0 10px 30px rgba(11, 45, 77, .08);
    overflow: visible;
}

/* Decision A: the authored lesson colour is an ACCENT, never a field. Phase 1 parks it
   on the card edge; phase 2 moves it onto the header zone. --tp-lesson-accent is written
   inline per lesson by CoursePlayer; unset, the brand teal stands in. */
.tp-stage--immersive .tp-content-panel {
    border-top: 5px solid var(--tp-lesson-accent, var(--coastal-teal));
}

/* The stage's own step bar. The child combinator matters: a quiz or exam renders its own
   PaperProgressBar deeper inside the card, and that one keeps the block's accent. */
.tp-stage--immersive .tp-content-panel > .tp-paper-progress {
    margin: 0;
    padding: 12px var(--tp-stage-pad) 0;
}

.tp-stage--immersive .tp-content-panel > .tp-paper-progress .tp-paper-progress-fill {
    background: var(--tp-lesson-progress, var(--coastal-teal));
}

/* Body: content starts at the TOP of the card and the stage scrolls when it is long. */
/* Top gap is the header-to-content gap, not the card's own padding: the header already
   ends in a hairline plus its own bottom padding, so a full pad here read as a hole. */
/* Bottom padding is the content-to-nav gap. It was 0, so a component's border sat flush
   on the nav bar's hairline and the two read as one box. */
.tp-stage-body {
    display: block;
    padding: var(--tp-stage-gap) var(--tp-stage-pad) var(--tp-stage-gap);
    min-width: 0;
}

.tp-stage-body img,
.tp-stage-body video,
.tp-stage-body iframe,
.tp-stage-body table,
.tp-stage-body pre { max-width: 100%; }

.tp-stage-body img { height: auto; }

.tp-stage--immersive .tp-block-stage { width: 100%; min-width: 0; }
.tp-stage--immersive .tp-block-card { width: 100%; min-width: 0; }

.tp-intro,
.tp-conclusion,
.tp-completion { max-width: 76ch; }

/* Nav: last child of a content-height card. Sticky costs nothing until the card is
   taller than the viewport, which is exactly the brief's condition for it. */
.tp-stage--immersive .tp-nav-bar {
    position: sticky;
    bottom: 0;
    z-index: 1;
    margin: 0;
    padding: 12px var(--tp-stage-pad) var(--tp-stage-pad);
    background: var(--sea-white);
    border-top: 1px solid #E3E8ED;
    border-radius: 0 0 var(--radius) var(--radius);
}

.tp-stage--immersive .tp-nav-bar .rz-button { min-height: 44px; }

/* ---- Header zone (decision D) --------------------------------------------------
   Course and module context, the lesson title, Step x of y and the progress bar, as
   ONE band at the top of the stage card. The authored lesson colour is the rule along
   its top edge and nothing else (decision A). */
/* Compact on purpose. Every pixel here pushes the learning component further down the
   card, and the brief's test is that content begins near the TOP of the stage. */
.tp-stage-header {
    display: flex;
    flex-direction: column;
    gap: 6px;
    padding: calc(var(--tp-stage-pad) - 10px) var(--tp-stage-pad) 8px;
    border-bottom: 1px solid #E3E8ED;
}

.tp-stage-context {
    display: flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    font-size: .74rem;
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: #6B7A89;
}

.tp-stage-context-course,
.tp-stage-context-module {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The course name is the anchor; the module may ellipsize away before it does. */
.tp-stage-context-course { color: var(--deep-harbor); flex: 0 1 auto; }
.tp-stage-context-module { flex: 0 1 auto; }
.tp-stage-context-sep { flex: 0 0 auto; opacity: .6; }

.tp-stage-context-right {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
    text-transform: none;
    letter-spacing: 0;
}

.tp-stage-step { font-size: .8rem; font-weight: 600; color: var(--deep-harbor); }

.tp-stage-title {
    margin: 0;
    font-size: 1.25rem;
    line-height: 1.3;
    font-weight: 700;
    color: var(--deep-harbor);
}

/* The step count already reads in the context line above, so the bar carries the bar
   only. Its own label would print the same number a second time. */
.tp-stage-header .tp-paper-progress { margin: 0; gap: 0; }
.tp-stage-header .tp-paper-progress-label { display: none; }
.tp-stage-header .tp-paper-progress-fill { background: var(--tp-lesson-progress, var(--coastal-teal)); }

/* ---- Navigation (decision E) ---------------------------------------------------
   Previous left, Exit Training and Info centred, Next right. Directly under the
   content: the card is content-height, so sticky does nothing at all until the card
   is taller than the viewport, which is exactly the brief's condition for it. */
.tp-stage--immersive .tp-nav-bar {
    position: sticky;
    bottom: 0;
    z-index: 1;
    margin: 0;
    padding: 12px var(--tp-stage-pad) var(--tp-stage-pad);
    background: var(--sea-white);
    border-top: 1px solid #E3E8ED;
    border-radius: 0 0 var(--radius) var(--radius);
}

.tp-stage--immersive .tp-nav-bar .rz-button { min-height: 44px; }

/* ---- App bar -------------------------------------------------------------------
   The brand, course, operator and module stepper row above the stage. It is app
   chrome, not the stage header, so it loses its card shape and becomes a plain bar. */
.tp-stage--immersive .tp-player-header {
    border-radius: 0;
    box-shadow: none;
    border-bottom: 1px solid #E3E8ED;
    padding: 6px var(--tp-stage-gutter);
    min-height: 44px;
    min-width: 0;
    background: var(--sea-white);
    justify-content: center;
}

@media (max-width: 767px) {
    .tp-stage-title { font-size: 1.1rem; }

    /* The module name goes first when space runs out — it is already spelled out in
       the app bar's stepper directly above. Course name and step count survive. */
    .tp-stage-context-module,
    .tp-stage-context-sep { display: none; }

    /* Middle group onto its own row so Previous and Next keep the ends of the bar
       instead of Next wrapping under them on its own. */
    .tp-stage--immersive .tp-nav-bar { gap: 6px; }
    .tp-stage--immersive .tp-nav-bar-mid { order: 3; width: 100%; justify-content: center; }

    .tp-stage--immersive .tp-player-header { padding: 6px 10px; }
    .tp-player-operator-logo { max-height: 26px; max-width: 92px; }
}


/* ============================================================
   PER-TYPE CARDS — learner-stage plan section 5.3, phase 3.
   One frame and one width for every block; only the INSIDE differs by type.
   ============================================================ */

.tp-stage-body .tp-player-card,
.tp-stage-body .tp-player-card--wide,
.tp-stage-body .tp-styled-card,
.tp-stage-body .tp-quiz-shell,
.tp-stage-body .tp-callout.tp-kc {
    width: 100%;
    max-width: 100%;
    margin-inline: 0;
    /* tp-kc is a plain div, not an rz- element, so Radzen's border-box reset misses it:
       width:100% PLUS its padding and 3px left rule pushed the interactive card ~30px
       past the player's right edge. */
    box-sizing: border-box;
}

/* Text wants a bounded measure, but the CARD no longer shrinks to provide it. */
.tp-stage-body .tp-player-card .tp-prose { max-width: 76ch; }

/* ---- Teaching graphic (brief 12.2) ----
   The authored image is the object of the step: full card width, aspect ratio preserved,
   never cropped, capped so it cannot push the navigation off a laptop screen. */
.tp-stage-body .tp-player-card--wide img,
.tp-stage-body .tp-card-visual {
    display: block;
    width: 100%;
    height: auto;
    max-height: var(--tp-graphic-max-h);
    object-fit: contain;
    margin-inline: auto;
    border-radius: 8px;
}

/* ---- Video (brief section 4) ----
   16:9 by construction, centred, and bounded by height rather than a flat 480px so it
   scales with the viewport it is being watched on. */
.tp-stage-body .tp-video-container {
    width: 100%;
    max-width: calc(var(--tp-video-max-h) * 16 / 9);
    margin-inline: auto;
}

.tp-stage-body .tp-video-container video {
    display: block;
    width: 100%;
    aspect-ratio: 16 / 9;
    max-height: var(--tp-video-max-h);
    background: var(--deep-harbor);
    border-radius: 8px;
}

/* ---- Knowledge check (decision G) ----
   Shared by QuizBlockPlayer, InteractiveCardBlockPlayer and ExamPaperRunner. */
.tp-kc { padding: var(--tp-stage-pad); }

.tp-kc-heading,
.tp-quiz-shell-title {
    font-size: 1.05rem;
    font-weight: 700;
    color: var(--deep-harbor);
}

.tp-kc-heading { margin-bottom: 12px; }

.tp-kc-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 20px;
}

.tp-kc-visual img {
    width: 100%;
    height: auto;
    border-radius: 8px;
}

/* Feedback opens here, directly below the control and inside the same card, so the
   learner's eye does not have to search for it. */
.tp-kc-response {
    grid-column: 1 / -1;
    display: flex;
    flex-direction: column;
    gap: 12px;
}

/* Image beside the scenario only where there is room for both to stay readable. Below
   this the grid collapses and source order puts the image above the scenario. */
@media (min-width: 900px) {
    .tp-kc.has-visual .tp-kc-grid { grid-template-columns: minmax(0, 1fr) minmax(240px, 36%); }
    .tp-kc.has-visual .tp-kc-visual { grid-column: 2; grid-row: 1; align-self: start; }
    .tp-kc.has-visual .tp-kc-scenario { grid-column: 1; grid-row: 1; }
}

/* ---- Answer targets (brief 12.3 and 12.4) ----
   Generous height and full-size type. The old rows were 37px with 16px text. */
.tp-stage-body .tp-quiz-prompt { font-size: 1.1rem; line-height: 1.45; }
.tp-stage-body .tp-quiz-options { gap: 10px; }

.tp-stage-body .tp-quiz-option {
    min-height: 48px;
    padding: 12px 14px;
    gap: 12px;
    font-size: 1rem;
}

/* flex-shrink:0 is load-bearing, not tidiness. The option row is a flex container, and at
   375px the wrapping answer text squeezed the control to a measured 13px wide against its
   20px height — an off-round, hard-to-hit target on exactly the device that needs it most. */
.tp-stage-body .tp-quiz-option input[type="radio"],
.tp-stage-body .tp-quiz-option input[type="checkbox"] {
    width: 20px;
    height: 20px;
    flex-shrink: 0;
}

.tp-kc-response .tm-input,
.tp-widget--dropdown .rz-dropdown { min-height: 44px; width: min(100%, 420px); }

.tp-stage-body .tp-reveal-head { min-height: 44px; }
.tp-stage-body .tp-paper-nav .rz-button { min-height: 44px; }

/* ---- Assessment and examination hierarchy (decision H) ---- */
.tp-exam-head .tm-library-title { font-size: 1.15rem; }
.tp-exam-attempt { color: var(--signal-gold); font-weight: 600; }

/* ---- Flash cards ---- */
.tp-flashcard-nav {
    display: flex;
    justify-content: center;
    gap: 1rem;
    margin-top: .75rem;
}

.tp-flashcard-nav .rz-button { min-height: 44px; }

/* ---- Authored block fills on a light stage (decision A) ----
   A "bare" card is transparent with white, shadowed text. That was legible over a
   background photograph; on a Sea White stage it is white on white. */
.tp-stage--immersive .tp-callout--bare,
.tp-stage:fullscreen .tp-callout--bare {
    background: var(--tp-card-bg, var(--sea-white));
    color: inherit;
    text-shadow: none;
    box-shadow: 0 1px 2px rgba(11, 45, 77, .06), 0 6px 16px rgba(11, 45, 77, .06);
}

.tp-stage--immersive .tp-content-panel .tp-styled-card.rz-card.tp-callout--bare,
.tp-stage--immersive .tp-content-panel .tp-player-card.rz-card.tp-callout--bare {
    background: var(--tp-card-bg, var(--sea-white));
    color: inherit;
    text-shadow: none;
}

/* S9: the blanket fullscreen alert suppression also hid the interactive card's own
   feedback, which is the one alert the learner must see. Scoped to the preview and error
   banners, which are direct children of the shell. */
.tp-stage:fullscreen .tp-shell > .rz-alert { display: none; }
