/* ═══════════════════════════════════════════════════════════════════
   shared/deck.css — the slide stage.

   The reset, the design tokens and the light/dark colour scheme used to
   live at the top of this file. They now live in shared/tokens.css,
   which EVERY page loads before this one — see the <head> of the four
   deck pages. Nothing here changed value; the block simply moved so the
   landing page can reach the tokens without also downloading 2.000
   lines of slide chrome it never uses.

   The one rule that stayed behind is `body { overflow: hidden }`: that
   is deck behaviour (the slide stage owns the viewport), not a token.
   ═══════════════════════════════════════════════════════════════════ */

body {
    overflow: hidden;
}

/* Page Header */
.page-header {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 70px;
    background: var(--bg-primary);
    border-bottom: 1px solid var(--rail-border);
    z-index: 2000;
    display: flex;
    align-items: center;
    padding: 0 40px;
}

.page-header-left {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: flex-start;
    height: 100%;
}

.page-header-wordmark {
    width: auto;
    font-size: 14px;
    font-weight: 600;
    letter-spacing: 1px;
    text-transform: uppercase;
    color: var(--text-primary);
    display: flex;
    align-items: center;
    justify-content: flex-start;
    height: 100%;
    text-decoration: none;
    transition: color 0.2s ease;
    white-space: nowrap;
}
.page-header-wordmark:visited {
    color: var(--text-primary);
}
@media (hover: hover) {
    .page-header-wordmark:hover {
        color: var(--accent-color);
    }
}

.page-header-divider {
    margin: 0 12px;
    color: var(--text-tertiary);
    font-weight: 300;
}

.page-header-subtitle {
    font-size: 12px;
    font-weight: 400;
    letter-spacing: 0.5px;
    color: var(--text-tertiary);
    white-space: nowrap;
}

.page-header-slide-counter {
    font-size: 12px;
    color: var(--text-tertiary);
    font-weight: 500;
    letter-spacing: 0.5px;
    white-space: nowrap;
}

.page-header-content {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    height: 100%;
}
.page-header-right {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 20px;
    height: 100%;
}

.page-header-button {
    padding: 6px 14px;
    background: transparent;
    color: var(--text-secondary);
    border: 1px solid var(--border-color);
    border-radius: 4px;
    font-size: 11px;
    font-weight: 600;
    cursor: pointer;
    transition: all 0.2s ease;
    font-family: "Spectral", serif;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    white-space: nowrap;
}
@media (hover: hover) {
    .page-header-button:hover {
        color: var(--text-primary);
        border-color: var(--text-secondary);
    }
}

.page-header-button.help-button {
    padding: 6px 10px;
    min-width: 32px;
    text-align: center;
}

.page-header-chapter {
    font-size: 24px;
    font-weight: 200;
    color: var(--text-primary);
}
.page-header-chapter-number {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 1.5px;
    color: var(--text-tertiary);
    font-weight: 600;
}

.page-header-controls {
    flex: 1;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: flex-end;
}

.floating-overview-button {
    display: none; /* moved to header as page-header-button */
}

/* ===== Stage: absolutely-positioned rails + content panes ===== */
.stage {
    position: absolute;
    top: 70px;
    left: 0;
    width: 100%;
    height: calc(100dvh - 70px);
    overflow: hidden;
    background: var(--bg-primary);
}

/* ----- Rails (one persistent element per chapter) ----- */
.rail {
    position: absolute;
    top: 0;
    height: 100%;
    width: 70px;
    padding: 60px 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-start;
    overflow-y: auto;
    background: var(--bg-primary);
    /* Rails are fixed side panels: always above the sliding content,
                   so a pane sweeps behind them and never casts onto them. */
    z-index: 10;
    cursor: pointer;
    /* --tint drives the pink overlay opacity (transitionable) */
    --tint: 0;
    opacity: 1;
    /* Borders are always present but transparent, coloured per role,
                   so border-color can transition (and be delayed) with the move
                   instead of snapping on instantly. */
    border-left: 1px solid transparent;
    border-right: 1px solid transparent;
    /* Position is animated via transform; width + appearance via the props below */
    transition:
        transform var(--dur) var(--ease),
        width var(--dur) var(--ease),
        opacity var(--dur) var(--ease),
        border-color var(--dur) var(--ease),
        box-shadow var(--dur) var(--ease);
    will-change: transform;
}

.rail::before {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(
        135deg,
        var(--sidebar-bg) 0%,
        rgba(var(--accent-rgb), 0.05) 100%
    );
    opacity: var(--tint);
    transition: opacity var(--dur) var(--ease);
    pointer-events: none;
    z-index: 0;
}

.rail > * {
    position: relative;
    z-index: 1;
}

/* Role appearances — the ONLY place visual state is defined.
               Changing a rail's role class transitions every property at once. */
.rail.left {
    --tint: 1;
    opacity: 0.7;
    border-right-color: var(--rail-border);
    box-shadow: inset 3px 0 8px rgba(var(--shadow-rgb), 0.15);
}
.rail.current {
    --tint: 0;
    opacity: 1;
    /* no border / shadow — reads as part of the content pane */
    cursor: default;
}
.rail.right {
    --tint: 1;
    opacity: 1;
    border-left-color: var(--rail-border);
    border-right-color: var(--rail-border);
    box-shadow: -3px 0 8px rgba(var(--shadow-rgb), 0.12);
}

.rail-inner {
    display: flex;
    flex-direction: column;
    gap: 12px;
    align-items: center;
    padding: 16px 0;
    /* The ±6px role nudge is the rail's only Y move. Keep it short so
                   it finishes before the staggered X slide begins — the pushed
                   current→left rail then reads as Y-then-X, not a diagonal. */
    transition: transform 0.3s var(--ease);
}
.rail.left .rail-inner {
    transform: translateY(6px);
}
.rail.current .rail-inner {
    transform: translateY(var(--rail-hint-push, 0px));
}
/* Pre-nudge: while a soon-to-leave rail still looks/sits 'current',
               shift its inner content to the left-role offset first (Y), so when
               it later flips role and slides (X) the motion reads as Y-then-X. */
.rail.current.nudge-to-left .rail-inner {
    transform: translateY(6px);
}
/* When a chapter hint pushes the content, the current rail's inner
               content rides along at the same 0.4s so they read as one unit. */
.rail.current.hint-pushing .rail-inner {
    transition: transform 0.4s var(--ease);
}
.rail.right .rail-inner {
    transform: translateY(-6px);
}

.vertical-text {
    writing-mode: vertical-lr;
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 1px;
    white-space: nowrap;
    text-transform: uppercase;
    color: var(--text-tertiary);
    transition: color var(--dur) var(--ease);
}
.rail.current .vertical-text {
    color: var(--accent-color);
}

@media (hover: hover) {
    .rail.left:hover .vertical-text,
    .rail.right:hover .vertical-text {
        color: var(--accent-color);
    }
}

.subchapter-indicators {
    display: flex;
    gap: 4px;
    flex-direction: column;
}
.sub-dot {
    width: 4px;
    height: 4px;
    border-radius: 50%;
    background: var(--text-tertiary);
    opacity: 0.4;
    cursor: pointer;
    /* Size/opacity react fast (hover, subchapter nav); the active
                   colour morphs at --dur so it stays in sync with the rail's
                   role change instead of popping. */
    transition:
        width 0.2s ease,
        height 0.2s ease,
        opacity 0.2s ease,
        background var(--dur) var(--ease);
}
@media (hover: hover) {
    .sub-dot:hover {
        opacity: 0.7;
    }
}
.sub-dot.active {
    background: var(--dot-active);
    width: 6px;
    height: 6px;
    opacity: 1;
}

/* Active-dot colour follows the rail role (blue on current, dimmed
               on the side rails) and transitions with the rail, not against it. */
.rail {
    --dot-active: var(--accent-color);
}
.rail.left,
.rail.right {
    --dot-active: var(--text-tertiary);
}

/* ----- Content panes (one per visible chapter during a transition) ----- */
.content-pane {
    position: absolute;
    top: 0;
    height: 100%;
    background: var(--bg-primary);
    transition:
        transform var(--dur) var(--ease),
        opacity var(--dur) var(--ease);
    will-change: transform;
}

/* Cast a shadow off the leading edge only while sliding, so the moving
               pane reads as floating above what it passes over (depth). */
.content-pane.sliding {
    box-shadow: -14px 0 32px rgba(var(--shadow-rgb), 0.22);
}

.slides-container {
    position: relative;
    z-index: 1;
    height: 100%;
    overflow-y: scroll;
    scroll-snap-type: y mandatory;
    scroll-behavior: smooth;
    -ms-overflow-style: none;
    scrollbar-width: none;
    transition: transform 0.4s var(--ease);
    touch-action: pan-y;
}
/* Push distance equals the hint banner's height (set in JS) so the
               content edge stays flush with the banner edge throughout — they
               read as one rigid unit. Sign is baked into --hint-push. */
.slides-container.hint-active,
.slides-container.hint-active-fwd {
    transform: translateY(var(--hint-push, 0px));
}
.slides-container::-webkit-scrollbar {
    display: none;
}

.slide {
    width: 100%;
    height: 100%;
    position: relative;
    overflow: hidden; /* clip letterbox edges */
    scroll-snap-align: start;
    /* Shift content so it centers on the viewport, not the pane. Set per pane. */
    transform: translateX(var(--slide-shift, 0px));
}
/* Presenter mode (Shift+P) drops the self-study quiz slide from the deck.
   It is always the trailing slide of a chapter, so removing it from the
   scroll flow simply shortens the chapter — navigation counts route through
   navSubs() in JS to match. */
body.presenting .slide--quiz {
    display: none;
}
.slide-canvas {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 1920px;
    height: 1080px;
    transform-origin: center center;
    /* --slide-scale set per pane in JS; fit 1920×1080 into the content area */
    transform: translate(-50%, -50%) scale(var(--slide-scale, 1));
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    text-align: center;
    color: var(--text-primary);
    padding: 96px; /* design-space padding (tunable) */
}
.slide-number {
    font-size: var(--s-eyebrow-size);
    font-weight: var(--s-eyebrow-weight);
    letter-spacing: var(--s-eyebrow-tracking);
    text-transform: uppercase;
    margin-bottom: 24px;
    opacity: 0.7;
}
.slide-title {
    font-size: var(--s-title-size);
    font-weight: var(--s-title-weight);
    margin-bottom: 16px;
    line-height: 1.2;
}
.slide-description {
    font-size: var(--s-caption-size);
    max-width: 500px;
    opacity: 0.7;
    font-weight: var(--s-body-weight);
}

/* ===== Slide-type templates (shared/slide-templates.js) =====
               These classes are the design-system realisation of DESIGN-BRIEF §5.
               A templated slide carries semantic classes only; all typography and
               spacing live here, so the deck restyles centrally. Templates render
               a still frame (no motion) — they're double-rendered as thumbnails. */

/* Shared eyebrow (slide number / kicker), reused across templates. */
.s-eyebrow {
    font-size: var(--s-eyebrow-size);
    font-weight: var(--s-eyebrow-weight);
    letter-spacing: var(--s-eyebrow-tracking);
    text-transform: uppercase;
    opacity: 0.7;
}
.s-accent {
    color: var(--accent-color);
}

/* Eyebrow excluded from vertical centring on the left-aligned CONTENT templates
   (bullets, split, code): the slide-number is floated above the title so the
   title+content group is what `justify-content:center` balances, instead of the
   eyebrow's height dragging it low (DESIGN-BRIEF §4.5). The centred openers
   (title, statement) deliberately keep the eyebrow in flow. */
.s-bullets > div:first-child,
.s-split-body > div:first-child {
    position: relative;
}
.s-bullets > div:first-child > .slide-number,
.s-split-body > div:first-child > .slide-number {
    position: absolute;
    bottom: 100%;
}

/* statement — one oversized idea, accent on a single word. */
.s-statement-num {
    margin-bottom: 64px;
}
.s-statement {
    font-size: var(--s-statement-size);
    font-weight: var(--s-statement-weight);
    line-height: 1.18;
    max-width: 1440px;
}
.s-statement-foot {
    margin-top: 48px;
    font-size: 19px;
    font-weight: 600;
    letter-spacing: 2.5px;
    text-transform: uppercase;
    /* 0.38 blandede ned til #A8A197 = 2,37:1 mod creme — under enhver WCAG-
       grænse, og linjen bærer statement-slidets eneste forklaring. 0.6 giver
       4,54:1 og er stadig tydeligt underordnet den 96px store sætning. */
    opacity: 0.6;
}

/* split — text column + media column (≈55/45). Breaks the safe-area
               padding deliberately and lays out left-aligned. */
.s-split {
    position: absolute;
    inset: var(--grid-margin);
    display: flex;
    gap: var(--grid-gutter); /* the split falls on the col-7 / col-8 gutter */
    align-items: center;
    text-align: left;
}
.s-split-body {
    flex: 0 0 var(--grid-span-7); /* columns 1–7 */
    display: flex;
    flex-direction: column;
    gap: 36px;
}
.s-split-title {
    font-size: var(--s-split-title-size);
    font-weight: var(--s-title-weight);
    line-height: 1.2;
}
.s-split-points {
    display: flex;
    flex-direction: column;
    gap: 18px;
}
.s-split-points > div {
    font-size: var(--s-body-size);
    color: var(--text-secondary);
    line-height: 1.5;
}
/* In split/bullets bodies: <strong> = accent highlight, <b> = primary
               emphasis. Authoring stays plain; the colour rule lives here. */
.s-split-points strong,
.s-bullets-list strong {
    color: var(--accent-color);
    font-weight: 600;
}
.s-split-points b,
.s-bullets-list b {
    color: var(--text-primary);
    font-weight: 600;
}
.s-aside {
    font-size: var(--s-caption-size);
    font-style: italic;
    opacity: 0.38;
    line-height: 1.65;
    max-width: 500px;
    border-left: 2px solid var(--border-color);
    padding-left: 20px;
}
.s-split-media {
    flex: 1 1 0; /* columns 8–12 (fills the remaining 706px) */
    align-self: stretch;
    border-radius: 20px;
    overflow: hidden;
    background: var(--bg-secondary);
    border: 1px dashed var(--border-color);
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    gap: 16px;
    padding: 48px;
}
.s-split-media img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}
/* Media column hosting an inline interactive artifact: a positioning context
   (so the artifact's `position:absolute; inset:0` fills THIS column, not the
   whole slide) with no placeholder frame — the artifact draws its own surface. */
.s-split-media--artifact {
    position: relative;
    background: transparent;
    border: 0;
    padding: 0;
    overflow: hidden;
}
.s-media-note {
    font-size: 13px;
    opacity: 0.3;
    text-align: center;
    font-family: var(--s-code-font);
}

/* Code — IBM Plex Mono 600; comments 400. Use .s-code on a code block and
   .s-code-comment (or a nested .s-comment) for comment text. */
.s-code {
    font-family: var(--s-code-font);
    font-weight: var(--s-code-weight);
}
.s-code-comment,
.s-code .s-comment {
    font-weight: var(--s-comment-weight);
}

/* ── Codebox — THE way to show code on a slide ──────────────────────
   One shared panel so code looks identical across chapters: rounded
   card, header bar with a label (left) and language tag (right),
   line-numbered body in IBM Plex Mono. Built by renderCodebox() in
   shared/slide-templates.js — both the `code` template and bespoke
   diagram canvases should go through it rather than hand-rolling
   panels. Inside a line: <span class="s-k"> = keyword (accent),
   <span class="s-comment"> = comment. Sizes are design-space px. */
.s-codebox {
    background: var(--bg-secondary);
    border: 1px solid var(--border-color);
    border-radius: 16px;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    text-align: left;
    box-shadow: 0 4px 28px rgba(var(--shadow-rgb), 0.06);
}
.s-codebox-bar {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 16px 28px;
    border-bottom: 1px solid var(--border-color);
    flex-shrink: 0;
}
.s-codebox-label {
    font-family: var(--s-code-font);
    font-weight: var(--s-code-weight);
    font-size: 17px;
    letter-spacing: 0.5px;
    color: var(--text-primary);
}
.s-codebox-lang {
    margin-left: auto;
    font-family: var(--s-code-font);
    font-weight: var(--s-comment-weight);
    font-size: 16px;
    letter-spacing: 1px;
    color: var(--text-tertiary);
}
.s-codebox-badge {
    background: var(--accent-color);
    color: var(--bg-primary);
    font-size: 13px;
    font-weight: 700;
    padding: 4px 14px;
    border-radius: 20px;
    letter-spacing: 0.5px;
    white-space: nowrap;
}
/* A badge sits at the far right; if no lang tag precedes it, push it there. */
.s-codebox-badge:nth-child(2) {
    margin-left: auto;
}
.s-codebox-body {
    padding: 36px 44px;
    font-family: var(--s-code-font);
    font-weight: var(--s-code-weight);
    font-size: var(--s-codebox-size, 40px);
    line-height: 1.75;
    counter-reset: codeline;
    display: flex;
    flex-direction: column;
    /* no flex: 1 or justify-content: center — size to content */
}
.s-codeline {
    counter-increment: codeline;
    display: flex;
    align-items: baseline;
    gap: 0.9em;
}
.s-codeline::before {
    content: counter(codeline);
    font-weight: var(--s-comment-weight);
    font-size: 0.62em;
    color: var(--text-tertiary);
    opacity: 0.7;
    min-width: 1.4em;
    text-align: right;
    flex-shrink: 0;
    /* keep the number on the line's baseline grid */
    transform: translateY(-0.06em);
}
.s-codebox--plain .s-codeline::before {
    display: none; /* opt out of line numbers (single-line snippets) */
}
.s-codebox .s-k {
    color: var(--accent-color);
}
.s-codebox .s-comment {
    font-weight: var(--s-comment-weight);
    color: var(--text-tertiary);
}

/* bullets — left-aligned title + a list of parallel points. */
.s-bullets {
    position: absolute;
    inset: 96px;
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 40px;
    text-align: left;
}
.s-bullets-title {
    font-size: var(--s-title-size);
    font-weight: var(--s-title-weight);
    line-height: 1.2;
}
.s-bullets-list {
    display: flex;
    flex-direction: column;
    gap: 22px;
    max-width: 1280px;
}
/* No bullet glyph: points read as clean stacked lines anchored by their bold
   key terms — same marker-free treatment as .s-split-points (DESIGN-BRIEF §5,
   "punktopstillinger uden streger/prikker"). */
.s-bullets-list > li {
    list-style: none;
    font-size: var(--s-body-size);
    font-weight: var(--s-body-weight);
    color: var(--text-secondary);
    line-height: 1.5;
}
/* ----- Chapter back hint ----- */
.chapter-back-hint {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    padding: 24px;
    text-align: center; /* text stays centered on the viewport */
    font-size: 16px;
    color: var(--text-secondary);
    z-index: 100;
    /* Hidden above the stage top (clipped by .stage overflow), then
                   slides down in sync with the content using the same transform. */
    transform: translateY(-100%);
    transition: transform 0.4s var(--ease);
}
/* The painted panel (bg, border, shadow) only spans the current rail
               + content pane, so the left/right rails are never touched. */
.chapter-back-hint::before {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: var(--hint-left, 0px);
    width: var(--hint-width, 100%);
    /* Subtle accent wash over the panel — mirrors the side-rail tint. */
    background:
        linear-gradient(
            rgba(var(--accent-rgb), 0.05),
            rgba(var(--accent-rgb), 0.05)
        ),
        var(--bg-secondary);
    border-bottom: 1px solid var(--border-color);
    box-shadow: inset 0 -4px 12px rgba(var(--shadow-rgb), 0.1);
    z-index: -1;
}
.chapter-back-hint.active {
    transform: translateY(0);
}
.chapter-back-hint.dismissing {
    transition: transform 0.15s var(--ease);
}
.chapter-back-hint-text {
    font-weight: 500;
    margin-bottom: 8px;
    color: var(--accent-color);
}
.chapter-back-hint-instruction {
    font-size: 14px;
    opacity: 0.7;
}

/* ----- Chapter forward hint (mirror of back hint, anchored bottom) ----- */
.chapter-fwd-hint {
    position: absolute;
    bottom: 0;
    left: 0;
    width: 100%;
    padding: 24px;
    text-align: center; /* text stays centered on the viewport */
    font-size: 16px;
    color: var(--text-secondary);
    z-index: 100;
    /* Hidden below the stage bottom (clipped by .stage overflow), then
                   slides up in sync with the content using the same transform. */
    transform: translateY(100%);
    transition: transform 0.4s var(--ease);
}
/* Painted panel spans current rail + content pane only (see back hint). */
.chapter-fwd-hint::before {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: var(--hint-left, 0px);
    width: var(--hint-width, 100%);
    /* Subtle accent wash over the panel — mirrors the side-rail tint. */
    background:
        linear-gradient(
            rgba(var(--accent-rgb), 0.05),
            rgba(var(--accent-rgb), 0.05)
        ),
        var(--bg-secondary);
    border-top: 1px solid var(--border-color);
    box-shadow: inset 0 4px 12px rgba(var(--shadow-rgb), 0.1);
    z-index: -1;
}
.chapter-fwd-hint.active {
    transform: translateY(0);
}
.chapter-fwd-hint.dismissing {
    transition: transform 0.15s var(--ease);
}
.chapter-fwd-hint-text {
    font-weight: 500;
    margin-bottom: 8px;
    color: var(--accent-color);
}
.chapter-fwd-hint-instruction {
    font-size: 14px;
    opacity: 0.7;
}

/* ----- Keyboard hint ----- */
.keyboard-hint {
    position: absolute;
    top: 78%;
    left: 50%;
    transform: translateX(-50%);
    font-size: 14px;
    text-transform: uppercase;
    letter-spacing: 0.8px;
    z-index: 60;
    opacity: 1;
    transition: opacity 1s ease-out;
    color: var(--text-tertiary);
    background: var(--bg-primary);
    padding: 10px 18px;
    border-radius: 6px;
    border: 1px solid var(--rail-border);
    white-space: nowrap;
}
.keyboard-hint.faded {
    opacity: 0;
    pointer-events: none;
}
.keyboard-hint span {
    padding: 6px 12px;
    border-radius: 4px;
    margin: 0 6px;
    font-family: var(--s-code-font);
    font-weight: var(--s-code-weight);
    background: rgba(128, 128, 128, 0.15);
}

/* ----- Watermark (floating at bottom of stage) ----- */
.watermark {
    position: absolute;
    bottom: 10px;
    left: 0;
    right: 0;
    height: auto;
    background: transparent;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 10px;
    color: var(--text-tertiary);
    font-weight: 500;
    letter-spacing: 0.5px;
    z-index: 50;
    pointer-events: none;
    padding: 4px 20px;
}

/* ----- Media button ("Lær mere") -----
   Original form factor and bottom-centre anchor preserved (the overlay zooms
   open from the button's centre, so this must stay put). Bumped up in size
   and tinted with the subject accent so it reads as an action, plus a
   one-shot ring pulse when it surfaces on a media-bearing slide. */
.media-button {
    position: absolute;
    bottom: 50px;
    left: 50%;
    transform: translateX(-50%);
    padding: 13px 28px;
    background: var(--bg-primary);
    color: var(--accent-color);
    border: 1px solid var(--accent-color);
    border-radius: 4px;
    font-size: 12px;
    font-weight: 600;
    cursor: pointer;
    transition:
        color 0.2s ease,
        background 0.2s ease,
        box-shadow 0.2s ease,
        transform 0.2s ease;
    z-index: 60;
    font-family: "Spectral", serif;
    text-transform: uppercase;
    letter-spacing: 2px;
    box-shadow:
        0 4px 12px rgba(var(--shadow-rgb), 0.15),
        0 1px 3px rgba(var(--shadow-rgb), 0.1);
}
@media (hover: hover) {
    .media-button:hover {
        color: var(--bg-primary);
        background: var(--accent-color);
        transform: translateX(-50%) translateY(-2px);
    }
}
/* ----- System-wide press feedback -----
   Every interactive control dips slightly while held — the tactile "received"
   beat taught by the 3.7 affordance artifact, applied deck-wide (chrome AND
   every artifact's own buttons) so it's a consistent behaviour, not a
   media-button one-off. Kept subtle on purpose — a real press is a nudge, not a
   hop. Ungated by (hover: hover) so touch taps get it too.

   The dip uses the individual `scale:` property, NOT transform: scale(). That
   matters: `scale:` COMPOSES with any existing `transform:` instead of
   replacing it, so it layers cleanly on top of .media-button's translateX(-50%)
   centring, a button's :hover translateY lift, or any transform an artifact
   button already carries. That's what lets one broad rule safely cover every
   <button> in every subject deck — including artifact-internal ones — without
   clobbering their layout. (Individual transforms have wider support than
   @scope, which the artifact host already depends on, so this costs no
   compatibility.) The non-<button> clickables — nav dots, TOC cards, overlay
   close X's — are spans/divs, so they're listed explicitly.

   NB: the artifact also teaches a *delayed* shadow "pop" on release; that half
   is intentionally NOT backported (see the 3.7 notes) — it needs live tuning
   and only reads on a held press. */
button:active,
.sub-dot:active,
.indhold-chapter:active,
.indhold-card:active,
.media-overlay-close:active,
.quiz-overlay-close:active {
    scale: 0.97;
    transition: scale 0.12s ease;
}
/* The two elevated controls also tighten their shadow on press — they're the
   only ones lifted off the page, so a press physically compresses the shadow
   (flat controls have none). Scale still comes from the rule above. */
.media-button:active {
    box-shadow:
        0 2px 8px rgba(var(--shadow-rgb), 0.16),
        0 1px 2px rgba(var(--shadow-rgb), 0.1);
    transition:
        scale 0.12s ease,
        color 0.12s ease,
        background 0.12s ease,
        box-shadow 0.12s ease;
}
/* One-shot ring pulse when the button surfaces for a media-bearing slide.
   Transform is left untouched so it keeps its translateX(-50%) centring. */
@keyframes mediaButtonPulse {
    0% {
        box-shadow:
            0 4px 12px rgba(var(--shadow-rgb), 0.15),
            0 0 0 0 rgba(var(--accent-rgb), 0.5);
    }
    70% {
        box-shadow:
            0 4px 12px rgba(var(--shadow-rgb), 0.15),
            0 0 0 13px rgba(var(--accent-rgb), 0);
    }
    100% {
        box-shadow:
            0 4px 12px rgba(var(--shadow-rgb), 0.15),
            0 0 0 0 rgba(var(--accent-rgb), 0);
    }
}
@media (prefers-reduced-motion: no-preference) {
    .media-button--pulse {
        animation: mediaButtonPulse 1.2s ease-out 1;
    }
}

/* ===== Overlays (subchapter grid + media) ===== */
@keyframes fadeInBackdrop {
    from {
        background-color: rgba(0, 0, 0, 0);
    }
    to {
        background-color: rgba(0, 0, 0, 0.55);
    }
}
@keyframes fadeOutBackdrop {
    from {
        background-color: rgba(0, 0, 0, 0.55);
    }
    to {
        background-color: rgba(0, 0, 0, 0);
    }
}

.overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0);
    z-index: 1000;
    display: none;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    padding: 60px;
    overflow-y: auto;
    color: var(--text-primary);
}
.overlay.active {
    display: flex;
    animation: fadeInBackdrop 0.3s ease-out forwards;
}
.overlay.closing {
    animation: fadeOutBackdrop 0.3s ease-out forwards;
}
/* ===== Live slide thumbnail (reused real slide markup, scaled) ===== */
.slide-thumb {
    position: relative;
    width: 100%;
    aspect-ratio: 16 / 9;
    overflow: hidden;
    border-radius: 6px;
    background: var(--bg-primary);
    /* Inset ring so it's clipped to the thumb's own rounded rect and
                   never cut off by the scroll-pane overflow. Hairline at rest. */
    box-shadow: inset 0 0 0 1px var(--border-color);
    transition: box-shadow 0.2s ease;
}
.slide-thumb-canvas {
    position: absolute;
    top: 0;
    left: 0;
    width: 1920px;
    height: 1080px;
    transform-origin: top left;
    /* --thumb-scale = frameWidth / 1920, set in JS after layout */
    transform: scale(var(--thumb-scale, 0.105));
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    text-align: center;
    padding: 96px;
    color: var(--text-primary);
}

/* ===== Indhold overlay: Miller two-pane ===== */
.indhold-header {
    font-size: 32px;
    font-weight: 200;
    margin-bottom: 32px;
}
.indhold-panes {
    display: flex;
    gap: 32px;
    width: 100%;
    max-width: 1100px;
    height: 64vh;
    min-height: 0;
}
.indhold-chapters {
    flex: 0 0 280px;
    display: flex;
    flex-direction: column;
    gap: 10px;
    overflow-y: auto;
    padding-right: 6px;
}
.indhold-sections {
    flex: 1;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    grid-auto-rows: min-content;
    gap: 18px;
    overflow-y: auto;
    align-content: start;
    padding-right: 6px;
}

/* Chapter row (left pane) — compact card with first-slide thumbnail */
.indhold-chapter {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 8px;
    /* Uniform row height regardless of 1- vs 2-line titles. */
    min-height: 78px;
    border-radius: 8px;
    cursor: pointer;
    transition: background 0.2s ease;
    background: var(--overlay-bg);
}
.indhold-chapter .slide-thumb {
    flex: 0 0 96px;
}
.indhold-chapter-meta {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}
.indhold-chapter-num {
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 1px;
    color: var(--text-tertiary);
}
.indhold-chapter-title {
    font-size: 15px;
    font-weight: 400;
    line-height: 1.2;
    /* Cap at two lines so rows never exceed the uniform height. */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}
.indhold-chapter-count {
    font-size: 11px;
    color: var(--text-tertiary);
}
@media (hover: hover) {
    .indhold-chapter:hover {
        background: var(--bg-secondary);
    }
}
/* Selected = this chapter's sections are showing (accent ring on thumb) */
.indhold-chapter.selected {
    background: var(--bg-secondary);
}
.indhold-chapter.selected .slide-thumb {
    box-shadow: inset 0 0 0 2px var(--accent-color);
}
/* Current = the live position marker */
.indhold-chapter.current .indhold-chapter-num::before {
    content: "● ";
    color: var(--accent-color);
}

/* Section card (right pane) — slide thumbnail + caption */
.indhold-card {
    display: flex;
    flex-direction: column;
    gap: 8px;
    cursor: pointer;
}
@media (hover: hover) {
    .indhold-card:hover .slide-thumb {
        box-shadow: inset 0 0 0 2px var(--accent-color);
    }
}
/* Active = accent ring around the frame (consistent with hover) */
.indhold-card.current .slide-thumb {
    box-shadow: inset 0 0 0 2px var(--accent-color);
}
.indhold-card-caption {
    display: flex;
    align-items: baseline;
    gap: 8px;
    padding: 0 2px;
}
.indhold-card-num {
    font-size: 11px;
    font-weight: 600;
    color: var(--text-tertiary);
    white-space: nowrap;
}
.indhold-card-title {
    font-size: 13px;
    font-weight: 400;
    line-height: 1.2;
}

/* Sections-pane header (mobile back affordance + chapter title) */
.indhold-sections-head {
    display: none;
}

/* ============================================================
   CUSTOM DOT CURSOR
   A center dot inside a dotted ring, replacing the native pointer.
   The class .has-dot-cursor is only added on fine-pointer devices
   (JS-gated), so the native cursor is never hidden on touch.
   Hover over anything clickable tightens the ring to a solid accent
   circle; a click presses it in — clean, explicit affordances.
   ============================================================ */
html.has-dot-cursor,
html.has-dot-cursor * {
    /* A transparent 1×1 image cursor, not `cursor: none`. `none` reverts to
       the OS default arrow when the element under a STATIONARY pointer
       re-renders (e.g. clicking a rail re-roles it) until the mouse moves
       again; an image cursor stays invisible across those DOM changes.
       `none` kept as a fallback for anything that ignores the data-URI. */
    cursor:
        url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==")
            0 0,
        none !important;
}
#dotCursor {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 100000;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.25s ease;
    will-change: transform;
}
#dotCursor.ready {
    opacity: 1;
}
#dotCursor.blackout-hidden {
    opacity: 0;
}
/* Presenting + no mouse movement for a few seconds: fade the pointer out.
   The next mousemove clears .cursor-idle (see wakeCursor in deck.js). The
   bare `cursor: none` is a fallback for any context without the dot cursor. */
html.cursor-idle.has-dot-cursor #dotCursor {
    opacity: 0;
}
html.cursor-idle,
html.cursor-idle * {
    cursor: none !important;
}
#dotCursor .dc-dot,
#dotCursor .dc-ring {
    position: absolute;
    top: 0;
    left: 0;
    border-radius: 50%;
    transform: translate(-50%, -50%);
    transition:
        width 0.18s ease,
        height 0.18s ease,
        border-color 0.18s ease,
        background 0.18s ease,
        box-shadow 0.18s ease;
}
#dotCursor .dc-dot {
    width: 6px;
    height: 6px;
    background: var(--text-primary);
}
#dotCursor .dc-ring {
    width: 28px;
    height: 28px;
    border: 1.5px dotted var(--text-tertiary);
}
/* Hover over something clickable: ring tightens + turns solid accent. The
   bg-primary halos keep the accent cursor legible on ANY background — most of
   all on a button whose OWN :hover fills with the same accent, where a bare
   accent cursor vanished. Ring gets a cream sheath on both edges of its
   border; dot gets a cream ring hugging it. On cream the halos are invisible
   (cream on cream), on accent they carry the shape. */
#dotCursor.is-hover .dc-ring {
    width: 20px;
    height: 20px;
    border-style: solid;
    border-color: var(--accent-color);
    box-shadow:
        0 0 0 1.5px var(--bg-primary),
        inset 0 0 0 1.5px var(--bg-primary);
}
#dotCursor.is-hover .dc-dot {
    width: 8px;
    height: 8px;
    background: var(--accent-color);
    box-shadow: 0 0 0 2px var(--bg-primary);
}
/* Click press feedback. */
#dotCursor.is-down .dc-ring {
    width: 15px;
    height: 15px;
}
#dotCursor.is-down .dc-dot {
    width: 5px;
    height: 5px;
}
@media (prefers-reduced-motion: reduce) {
    #dotCursor .dc-dot,
    #dotCursor .dc-ring {
        transition: none;
    }
}

/* Mobile: collapse to drill-down (chapters → sections) */
@media (max-width: 640px) {
    .indhold-panes {
        flex-direction: column;
        height: 70vh;
    }
    .indhold-chapters {
        flex: 1;
    }
    .indhold-sections {
        grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    }
    .indhold-overlay:not(.show-sections) .indhold-sections {
        display: none;
    }
    .indhold-overlay.show-sections .indhold-chapters {
        display: none;
    }
    .indhold-overlay.show-sections .indhold-sections-head {
        display: flex;
        align-items: center;
        gap: 10px;
        grid-column: 1 / -1;
        cursor: pointer;
        font-size: 14px;
        color: var(--text-secondary);
        margin-bottom: 4px;
    }
}
.overlay-hint {
    position: absolute;
    bottom: 40px;
    color: var(--text-tertiary);
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 1px;
}
.overlay-hint span {
    font-family: var(--s-code-font);
    font-weight: var(--s-code-weight);
    background: var(--bg-secondary);
    padding: 4px 8px;
    border-radius: 3px;
    margin: 0 4px;
}

.media-overlay {
    position: fixed;
    top: 70px;
    left: 0;
    width: 100%;
    height: calc(100dvh - 70px);
    background-color: rgba(0, 0, 0, 0);
    z-index: 999;
    display: none;
    flex-direction: column;
    justify-content: flex-end;
    align-items: center;
    padding: 40px;
    padding-bottom: 160px;
    overflow-y: auto;
}
.media-overlay.active {
    display: flex;
    animation: fadeInBackdrop 0.3s ease-out forwards;
}
.media-overlay.closing {
    animation: fadeOutBackdrop 0.3s ease-out forwards;
}
.media-overlay-content {
    background: var(--overlay-bg);
    border-radius: 12px;
    padding: 40px;
    max-width: 600px;
    width: 100%;
    max-height: calc(100dvh - 70px - 180px);
    overflow-y: auto;
    color: var(--text-primary);
    position: relative;
    box-shadow: 0 20px 60px rgba(var(--shadow-rgb), 0.3);
    transform: scale(0.05);
    opacity: 0;
    transition:
        transform 0.25s cubic-bezier(0.4, 0, 1, 1),
        opacity 0.15s ease;
}
.media-overlay.active .media-overlay-content {
    transform: scale(1);
    opacity: 1;
}
.media-overlay.closing .media-overlay-content {
    transform: scale(0.05);
    opacity: 0;
}
.media-overlay-close {
    position: absolute;
    top: 20px;
    right: 20px;
    font-size: 24px;
    cursor: pointer;
}
.media-text {
    font-size: 16px;
    line-height: 1.6;
    margin-bottom: 30px;
}
/* Supporting video link. Chrome under the voiceover text, never part of it —
   media.text is extracted verbatim as the recording manuscript. */
.media-text__link {
    margin-top: 16px;
}
.media-text__link a {
    color: var(--accent-color);
}
.media-audio audio {
    width: 100%;
    display: block;
    margin-bottom: 24px;
}
.media-video video {
    width: 100%;
    border-radius: 8px;
    max-height: 500px;
}

/* ===== Quiz (self-study layer) ===== */
/* Launcher button — rendered as a dedicated slide section by the chapter. */
.quiz-launcher {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    gap: 32px;
    max-width: 700px;
}
.quiz-launcher-heading {
    font-size: 88px;
    font-weight: 200;
    line-height: 1.1;
    text-align: center;
    color: var(--text-primary);
}
.quiz-launcher-description {
    font-size: 22px;
    line-height: 1.5;
    text-align: center;
    color: var(--text-secondary);
    max-width: 600px;
}
.quiz-launcher-button {
    margin-top: 16px;
    padding: 20px 48px;
    background: var(--accent-color);
    color: var(--bg-primary);
    border: none;
    border-radius: 8px;
    font-family: "Spectral", serif;
    font-size: 18px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 2px;
    cursor: pointer;
    transition:
        transform 0.2s ease,
        box-shadow 0.2s ease;
    box-shadow: 0 6px 24px rgba(var(--shadow-rgb), 0.15);
}
@media (hover: hover) {
    .quiz-launcher-button:hover {
        transform: translateY(-4px);
        box-shadow: 0 10px 32px rgba(var(--shadow-rgb), 0.25);
    }
}
/* Press: dips (scale via the system-wide rule, which composes with the hover
   translateY lift) and tightens its shadow — it's one of the two elevated
   controls. transition re-declares scale so the shared dip still animates here. */
.quiz-launcher-button:active {
    box-shadow: 0 3px 12px rgba(var(--shadow-rgb), 0.16);
    transition:
        scale 0.12s ease,
        box-shadow 0.12s ease;
}

.quiz-overlay {
    position: fixed;
    top: 70px;
    left: 0;
    width: 100%;
    height: calc(100dvh - 70px);
    background-color: rgba(0, 0, 0, 0);
    z-index: 999;
    display: none;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    padding: 40px;
    overflow-y: auto;
}
.quiz-overlay.active {
    display: flex;
    animation: fadeInBackdrop 0.3s ease-out forwards;
}
.quiz-overlay.closing {
    animation: fadeOutBackdrop 0.3s ease-out forwards;
}
.quiz-card {
    background: var(--overlay-bg);
    border-radius: 14px;
    padding: 48px;
    max-width: 640px;
    width: 100%;
    max-height: calc(100dvh - 70px - 80px);
    overflow-y: auto;
    color: var(--text-primary);
    position: relative;
    box-shadow: 0 20px 60px rgba(var(--shadow-rgb), 0.3);
    transform: scale(0.05);
    opacity: 0;
    transition:
        transform 0.25s cubic-bezier(0.4, 0, 1, 1),
        opacity 0.15s ease;
}
.quiz-overlay.active .quiz-card {
    transform: scale(1);
    opacity: 1;
}
.quiz-overlay.closing .quiz-card {
    transform: scale(0.05);
    opacity: 0;
}
.quiz-overlay-close {
    position: absolute;
    top: 18px;
    right: 22px;
    font-size: 24px;
    line-height: 1;
    cursor: pointer;
    color: var(--text-tertiary);
}
.quiz-progress {
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 2px;
    color: var(--text-tertiary);
    font-weight: 600;
    margin-bottom: 10px;
}
.quiz-progress-bar {
    height: 3px;
    background: var(--border-color);
    border-radius: 2px;
    margin-bottom: 28px;
    overflow: hidden;
}
.quiz-progress-fill {
    height: 100%;
    background: var(--accent-color);
    border-radius: 2px;
    transition: width 0.3s var(--ease);
}
.quiz-question {
    font-size: 26px;
    font-weight: 400;
    line-height: 1.4;
    margin-bottom: 28px;
}
.quiz-options {
    display: flex;
    flex-direction: column;
    gap: 12px;
}
.quiz-option {
    text-align: left;
    padding: 15px 18px;
    border: 1.5px solid var(--border-color);
    border-radius: 8px;
    background: var(--bg-secondary);
    color: var(--text-primary);
    font-family: "Spectral", serif;
    font-size: 17px;
    line-height: 1.4;
    cursor: pointer;
    transition:
        border-color 0.15s ease,
        background 0.15s ease;
    display: flex;
    align-items: center;
    gap: 14px;
}
@media (hover: hover) {
    .quiz-option:not(:disabled):hover {
        border-color: var(--accent-color);
    }
}
.quiz-option:disabled {
    cursor: default;
}
.quiz-key {
    flex-shrink: 0;
    width: 26px;
    height: 26px;
    border-radius: 6px;
    background: var(--bg-primary);
    border: 1px solid var(--border-color);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 13px;
    font-weight: 700;
    color: var(--text-tertiary);
}
/* Feedback colours are deliberately fixed (not themed): mid-tone green/
               red read correctly over both the cream and near-black surfaces. */
.quiz-option.correct {
    border-color: #3a9d5d;
    background: rgba(58, 157, 93, 0.14);
}
.quiz-option.incorrect {
    border-color: #cc4b3a;
    background: rgba(204, 75, 58, 0.14);
}
.quiz-option.correct .quiz-key {
    background: #3a9d5d;
    border-color: #3a9d5d;
    color: #fff;
}
.quiz-option.incorrect .quiz-key {
    background: #cc4b3a;
    border-color: #cc4b3a;
    color: #fff;
}
.quiz-explanation {
    margin-top: 24px;
    padding: 18px 20px;
    border-left: 3px solid var(--accent-color);
    background: var(--bg-secondary);
    border-radius: 0 8px 8px 0;
    font-size: 16px;
    line-height: 1.6;
    color: var(--text-secondary);
}
.quiz-explanation strong {
    color: var(--text-primary);
    font-weight: 600;
}
.quiz-footer {
    margin-top: 28px;
    display: flex;
    justify-content: flex-end;
}
.quiz-next {
    padding: 12px 26px;
    background: var(--accent-color);
    color: var(--bg-primary);
    border: none;
    border-radius: 6px;
    font-family: "Spectral", serif;
    font-size: 14px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 1px;
    cursor: pointer;
}
.quiz-result {
    text-align: center;
    padding: 12px 0;
}
.quiz-result-score {
    font-size: 72px;
    font-weight: 200;
    color: var(--accent-color);
    line-height: 1;
    margin: 8px 0;
}
.quiz-result-label {
    font-size: 18px;
    color: var(--text-secondary);
    margin-bottom: 32px;
}
.quiz-result-actions {
    display: flex;
    gap: 12px;
    justify-content: center;
}

/* ===== Mobile Layout (<640px) ===== */
@media (max-width: 640px) {
    .page-header {
        padding: 0 20px;
    }
    .page-header-divider,
    .page-header-subtitle,
    .page-header-slide-counter,
    .page-header-button.help-button {
        display: none;
    }
    /* The three header zones are flex:1 with no min-width, so a long chapter
       title can grow past the wordmark's zone and overlap it. Let the center
       zone shrink (min-width:0) and truncate its title instead — the
       wordmark stays put and fully visible (it's the only home link here). */
    .page-header-content {
        min-width: 0;
        /* Breathing room so a truncated title never sits flush against
           the wordmark on the left or the Indhold button on the right. */
        padding: 0 12px;
    }
    .page-header-chapter {
        font-size: 17px;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        max-width: 100%;
    }
    .keyboard-hint {
        display: none;
    }
    .rail {
        display: none;
    }
    .chapter-back-hint,
    .chapter-fwd-hint {
        display: none;
    } /* boundary hints are desktop-only; swipe replaces them */
    .quiz-card {
        padding: 32px 24px;
    }
    .quiz-question {
        font-size: 22px;
    }
}

/* ===== Chapter backdrop layer (behind scroller, for parallax/cross-slide effects) ===== */
.chapter-backdrop {
    position: absolute;
    inset: 0;
    z-index: 0; /* behind the slides scroller */
    overflow: hidden;
    pointer-events: none;
    will-change: transform;
}
/* Multi-depth backdrops: any backdrop element carrying data-parallax="f" is
   translated by the engine at scrollTop × f individually (the root backdrop
   then stays put). Position content inside a layer with `top` offsets — deeper
   factors travel further, so spread content down to ~(slides × f) viewport
   heights. The engine also sets --scroll-y / --scroll-progress and [data-sub]
   on the content pane, so backdrop CSS can derive scroll-linked motion and
   per-slide states without any chapter JS. */
.chapter-backdrop [data-parallax] {
    position: absolute;
    inset: 0;
    will-change: transform;
}
@media (max-width: 640px) {
    .chapter-backdrop {
        display: none;
    } /* disable distracting effects on mobile */
}

/* ===== Chapter progress indicator (mobile only) ===== */
.chapter-progress {
    display: none;
}
@media (max-width: 640px) {
    .chapter-progress {
        position: absolute;
        top: 70px; /* just under the header */
        left: 0;
        width: 100%;
        display: flex;
        justify-content: center;
        gap: 8px;
        padding: 10px 0;
        z-index: 50;
        pointer-events: none;
    }
    .chapter-progress .cp-dot {
        width: 6px;
        height: 6px;
        border-radius: 50%;
        background: var(--text-tertiary);
        opacity: 0.4;
        transition:
            opacity var(--dur) var(--ease),
            background var(--dur) var(--ease);
    }
    .chapter-progress .cp-dot.active {
        opacity: 1;
        background: var(--accent-color);
    }
}

/* ===== First-load entrance (engine-owned, plays once) =====
   deck.js puts .deck-enter on <body> at init and removes it after the run, so
   panes built later (chapter navigation) never re-trigger it. Animating the
   canvas transform is safe here because the keyframe restates the full resting
   transform (centering + --slide-scale). Collapsed by reduced-motion below. */
body.deck-enter .page-header {
    animation: deck-fade-down 0.7s var(--ease) both;
}
body.deck-enter .rail,
body.deck-enter .chapter-progress {
    animation: deck-fade 0.9s var(--ease) 0.3s both;
}
body.deck-enter .chapter-backdrop {
    animation: deck-fade 1.3s var(--ease) 0.45s both;
}
body.deck-enter .slide-canvas {
    animation: deck-rise 0.95s var(--ease) 0.15s both;
}
@keyframes deck-fade {
    from {
        opacity: 0;
    }
}
@keyframes deck-fade-down {
    from {
        opacity: 0;
        transform: translateY(-14px);
    }
}
@keyframes deck-rise {
    from {
        opacity: 0;
        transform: translate(-50%, calc(-50% + 36px))
            scale(var(--slide-scale, 1));
    }
    to {
        opacity: 1;
        transform: translate(-50%, -50%) scale(var(--slide-scale, 1));
    }
}

@media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
        transition-duration: 0.001ms !important;
        animation-duration: 0.001ms !important;
        scroll-behavior: auto !important;
    }
}

:focus-visible {
    outline: 2px solid var(--accent-color);
    outline-offset: 2px;
}

/* Inline artifacts ("components"): real DOM authored at 1920×1080. The host
               fills the design canvas (padding box) so it scales rigidly with the
               slide and shows up in live thumbnails — no iframe, no focus trap.
               Its own CSS is scoped via @scope (.artifact--<id>). */
.artifact {
    position: absolute;
    inset: 0;
}

/* The single, uniform "I'm interactive — use me" affordance for every inline
   artifact. When a slide comes to rest the engine marks it .slide--active
   (see markActiveSlide); the artifact's one primary control, tagged
   [data-poke], answers with a few accent ring-pulses — the
   same expanding-ring language as the "Lær mere" button. The ring is
   shape-agnostic, so a slider, a button, a card or a list all announce
   themselves identically. Controls you *drag* ([data-poke="drag"]) add a
   sideways nudge, matching the signifier to the gesture. Motion only,
   disabled under reduced-motion, re-fired on each arrival. */
@keyframes artifactPokeRing {
    0% {
        box-shadow: 0 0 0 0 rgba(var(--accent-rgb), 0.55);
    }
    70% {
        box-shadow: 0 0 0 13px rgba(var(--accent-rgb), 0);
    }
    100% {
        box-shadow: 0 0 0 0 rgba(var(--accent-rgb), 0);
    }
}
@keyframes artifactPokeNudge {
    0%,
    100% {
        transform: translateX(0);
    }
    30% {
        transform: translateX(-7px);
    }
    62% {
        transform: translateX(6px);
    }
}
@media (prefers-reduced-motion: no-preference) {
    .slide--active [data-poke] {
        animation: artifactPokeRing 0.9s ease-out 3;
    }
    /* Drag controls additionally hint the gesture itself. */
    .slide--active [data-poke="drag"] {
        animation:
            artifactPokeRing 0.9s ease-out 3,
            artifactPokeNudge 0.9s ease-in-out 1;
    }
}

/* ---- In-slide reveals (DESIGN-BRIEF §4.4 Tier 2) ----------------------
   Add these classes to elements inside a slide canvas to animate them in
   when the slide becomes active. The DEFAULT (no .slide--active — i.e. every
   contents-overlay thumbnail and every reduced-motion user) is the finished
   END state, so the slide always reads as a correct still frame; the intro
   plays only on the live active slide. Prefer the additive effects
   (highlight, underline, emphasis): they never hide content, so they can't
   pop or leave a thumbnail half-built. Any bespoke reveal must follow the
   same contract — key the @keyframes off `.slide--active` and make the
   no-class state the end state. */

/* Highlight a word/phrase. End state: accent band painted behind the text. */
.reveal-hl {
    background-image: linear-gradient(
        rgba(var(--accent-rgb), 0.22),
        rgba(var(--accent-rgb), 0.22)
    );
    background-repeat: no-repeat;
    background-position: 0 90%;
    background-size: 100% 36%;
    border-radius: 2px;
    padding: 0 0.06em;
}
/* Underline draw. End state: a 2px accent rule under the text. */
.reveal-underline {
    background-image: linear-gradient(
        var(--accent-color),
        var(--accent-color)
    );
    background-repeat: no-repeat;
    background-position: 0 100%;
    background-size: 100% 2px;
}

@media (prefers-reduced-motion: no-preference) {
    /* Sweep the highlight band in from the left. */
    .slide--active .reveal-hl {
        animation: revealHighlight 0.6s cubic-bezier(0.4, 0, 0.2, 1) 0.15s both;
    }
    /* Draw the underline out from the left. */
    .slide--active .reveal-underline {
        animation: revealUnderline 0.5s ease-out 0.1s both;
    }
    /* A gentle scale-settle for an emphasised element (stays visible). */
    .slide--active .reveal-emph {
        animation: revealEmphasis 0.5s cubic-bezier(0.2, 0.7, 0.2, 1.3) both;
    }
    /* Fade + rise. The one effect that hides then shows, so reserve it for
       sequential list reveals; stagger with style="--reveal-i:0|1|2…". */
    .slide--active .reveal-rise {
        opacity: 0;
        animation: revealRise 0.55s ease-out both;
        animation-delay: calc(var(--reveal-i, 0) * 90ms);
    }
}
@keyframes revealHighlight {
    from {
        background-size: 0% 36%;
    }
    to {
        background-size: 100% 36%;
    }
}
@keyframes revealUnderline {
    from {
        background-size: 0% 2px;
    }
    to {
        background-size: 100% 2px;
    }
}
@keyframes revealEmphasis {
    0% {
        transform: scale(0.9);
    }
    60% {
        transform: scale(1.04);
    }
    100% {
        transform: scale(1);
    }
}
@keyframes revealRise {
    from {
        opacity: 0;
        transform: translateY(12px);
    }
    to {
        opacity: 1;
        transform: translateY(0);
    }
}
