
*, *::before, *::after { box-sizing: border-box; }
/* --accent stays the bright sky blue and keeps every job where it is the mark
   rather than the ground: links, the nav underline, the featured card's rail, the
   progress bar, the current-lesson dot. --accent-fill is only for a surface that
   carries white text.

   They had been the same colour, and white on #38bdf8 measures 2.14:1 — the
   lowest contrast anywhere on the site, on the button most meant to be pressed.
   The two roles pull in opposite directions on a dark page: as text on the
   background the accent wants to be bright, as a ground under white text it wants
   to be dark, and no single blue does both (the closest, #0284c7, fails at 4.10
   AND drops links to 4.36). Splitting the token is the fix. White on #0369a1 is
   5.93:1, and it is the same hue two steps down, so the button still reads as the
   site's blue with the volume lowered rather than as a second colour.

   To revert: point --accent-fill at var(--accent). */

/* --fg-chrome is for labels that sit on the raised chrome — the language tag on a
   code header, the copy/reset buttons — where the surface is --bg-3. On dark that
   surface is #334155, light enough that no genuinely muted colour reaches 4.5:1
   against it; the best --fg-3 could manage was 4.04. A separate token keeps the
   quiet text quiet everywhere else and lifts only the few labels that need it. */

/* --fg-3 and --syn-com carry the quiet text: breadcrumbs, footers, the little
   annotations beside a heading, and every code comment. They measured 3.75:1 on
   dark, 4.34 on light and 3.87 on eye-saver against the surfaces they sit on —
   all under the 4.5:1 that ordinary-sized text needs to be readable by someone
   who is not looking at a calibrated screen in a dark room. Lifted to the
   smallest value that clears it in each theme, so they stay quiet without
   disappearing. */

/* ── Metric-matched fallback ────────────────────────────────────────────────
   Inter loads with display:swap, so the page paints in the system sans first and
   re-lays out when Inter arrives. Measured on the academy: one shift of 0.1196 at
   413ms, everything below the hero dropping five pixels at once. Google counts
   anything above 0.1 as a failing Cumulative Layout Shift, so this was the one
   Core Web Vital the site was losing.

   The fix is to make the fallback occupy the same space as the real thing. These
   overrides scale the local sans to Inter's metrics, so when the swap happens the
   glyphs change and nothing moves. */
@font-face {
    font-family: 'Inter-fallback';
    src: local('Arial'), local('Helvetica'), local('Liberation Sans');
    size-adjust: 107%;
    ascent-override: 90%;
    descent-override: 22%;
    line-gap-override: 0%;
}


html, body { margin: 0; padding: 0; }

/* ── Theme switching ───────────────────────────────────────────────────────
   A plain custom property is NOT animatable: the browser treats it as an
   untyped token and swaps it in one frame. Every colour below therefore used to
   snap instantly, while the 18 rules on this page that carry their own
   `transition: background/color/border-color 0.12-0.15s` eased behind it.
   Measured: at t=0 only 14.2% of the page still differed from its final
   rendering — 86% had already jumped — and that remaining 14% kept moving until
   ~126ms. That split is the visible glitch: a hard snap with a fading tail.

   Registering each colour with @property gives it a type, which makes it
   interpolable, so the whole page can move together on one clock. The companion
   rule below then suppresses every element's own transition for the duration,
   so nothing runs on a second clock. Same approach as styles.css on the IDE,
   which measures as one coherent 300ms move (98.5% of the page in flight
   together, settled at 300ms). */
@property --fg         { syntax: '<color>'; inherits: true; initial-value: #f8fafc; }
@property --fg-2       { syntax: '<color>'; inherits: true; initial-value: #94a3b8; }
@property --fg-3       { syntax: '<color>'; inherits: true; initial-value: #64748b; }
@property --fg-4       { syntax: '<color>'; inherits: true; initial-value: #475569; }
@property --bg         { syntax: '<color>'; inherits: true; initial-value: #0f172a; }
@property --bg-2       { syntax: '<color>'; inherits: true; initial-value: #1e293b; }
@property --bg-3       { syntax: '<color>'; inherits: true; initial-value: #334155; }
@property --bg-code    { syntax: '<color>'; inherits: true; initial-value: #1e293b; }
@property --bg-out     { syntax: '<color>'; inherits: true; initial-value: #0f172a; }
@property --line       { syntax: '<color>'; inherits: true; initial-value: rgba(248, 250, 252, 0.12); }
@property --line-2     { syntax: '<color>'; inherits: true; initial-value: rgba(248, 250, 252, 0.20); }
@property --line-3     { syntax: '<color>'; inherits: true; initial-value: rgba(248, 250, 252, 0.30); }
@property --accent     { syntax: '<color>'; inherits: true; initial-value: #38bdf8; }
/* Same colour as --accent, different JOB. See the transition block below: fills
   and text must ease, 1px rings must snap, and one token cannot do both. */
@property --accent-line{ syntax: '<color>'; inherits: true; initial-value: #38bdf8; }
/* Execute-green. "Run" is one action and gets ONE identity across the product:
   the IDE's Run Code button and the Run button in every lesson code block use
   the same green, so blue stays brand/navigation and green always means "this
   executes your code". Stops are pitched dark enough that the white label clears
   WCAG AA at the 0.72rem the code-block button uses — the old accent-blue fill
   measured 2.14:1 against white in dark theme, which fails outright. */
@property --run        { syntax: '<color>'; inherits: true; initial-value: #059669; }
@property --run-deep   { syntax: '<color>'; inherits: true; initial-value: #047857; }
@property --accent-soft{ syntax: '<color>'; inherits: true; initial-value: rgba(56, 189, 248, 0.10); }
@property --good       { syntax: '<color>'; inherits: true; initial-value: #10b981; }
@property --good-soft  { syntax: '<color>'; inherits: true; initial-value: rgba(16, 185, 129, 0.15); }
@property --warn       { syntax: '<color>'; inherits: true; initial-value: #f59e0b; }
@property --warn-soft  { syntax: '<color>'; inherits: true; initial-value: rgba(245, 158, 11, 0.15); }
@property --err        { syntax: '<color>'; inherits: true; initial-value: #ef4444; }
@property --syn-kw     { syntax: '<color>'; inherits: true; initial-value: #f472b6; }
@property --syn-str    { syntax: '<color>'; inherits: true; initial-value: #7dd3fc; }
@property --syn-com    { syntax: '<color>'; inherits: true; initial-value: #64748b; }
@property --syn-fn     { syntax: '<color>'; inherits: true; initial-value: #c4b5fd; }
@property --syn-num    { syntax: '<color>'; inherits: true; initial-value: #fbbf24; }

:root[data-theme-transitioning] {
    transition:
        --fg 0.3s ease, --fg-2 0.3s ease, --fg-3 0.3s ease, --fg-4 0.3s ease,
        /* The rule that matters here: WHAT A TOKEN PAINTS decides its clock, and
           a surface must never be on a different clock from the text sitting on
           it. Snapping is only ever right for a thin line, because a line has no
           content of its own to fall out of step with.

           I got this wrong once already. --bg-3 and --accent were snapped along
           with the hairlines because they colour chips and rings — but --bg-3 is
           also the code block's HEADER BAR and --accent is also the Run button's
           FILL. So inside a single code example the header jumped at 80ms while
           the body eased beneath it for another 220ms, the header text eased
           while its own background had already jumped, and Run changed at a
           different instant from the buttons beside it. That is the same stagger
           this block exists to prevent, reintroduced by hand.

           SURFACES and FILLS ease. */
        --bg 0.3s ease, --bg-2 0.3s ease, --bg-3 0.3s ease,
        --bg-code 0.3s ease, --bg-out 0.3s ease,
        --accent 0.3s ease, --accent-soft 0.3s ease,
        --run 0.3s ease, --run-deep 0.3s ease,
        --good 0.3s ease, --good-soft 0.3s ease,
        --warn 0.3s ease, --warn-soft 0.3s ease, --err 0.3s ease,

        /* TEXT eases, with its surface. It does cross its background mid-sweep,
           but that is the whole-page wash: it affects every glyph equally, and
           snapping type is far more jarring than letting it pass through. */
        --syn-kw 0.3s ease, --syn-str 0.3s ease, --syn-com 0.3s ease,
        --syn-fn 0.3s ease, --syn-num 0.3s ease,

        /* LINES SNAP. A hairline flips polarity between themes (white-alpha in
           dark, ink-alpha in light), and a flipping line interpolated alongside
           its interpolating background meets that background exactly once, where
           the line is mathematically invisible. Snapping at 80ms -- where `ease`
           actually carries the background through mid -- never renders the
           crossing. Only LINES qualify: a line has no content of its own, so
           nothing can fall out of step with it. Verified by role: --line is used
           in 38 border declarations and 0 fills, --line-3 in 1 and 0. */
        --line 1ms linear 80ms, --line-2 1ms linear 80ms, --line-3 1ms linear 80ms,

        /* --accent-line exists solely so the accent RING can snap while the
           accent FILL eases. --accent is used in 11 fills, 9 text colours and 10
           borders; those requirements are incompatible, so the token is split by
           role rather than compromised. The ring needs it: border + 0 0 0 1px in
           accent over a --bg-2 fill, accent travelling luminance 0.44 -> 0.155
           while the fill travels 0.022 -> 1.000, meeting almost exactly at the
           midpoint (computed contrast 1.03 at t=0.5). Measured at that edge:
           8.19 at rest, 1.25 mid-switch, 4.67 at rest again. */
        --accent-line 1ms linear 80ms;
}

/* Descendants must not run their own clocks during the switch, or they stagger
   against the token interpolation — which is precisely what was happening here. */
:root[data-theme-transitioning] *,
:root[data-theme-transitioning] *::before,
:root[data-theme-transitioning] *::after {
    transition: none !important;
}

/* Reduced motion: snap, but snap ATOMICALLY. The guard attribute still applies,
   so the suppression rule above still holds and nothing eases out of step. */
@media (prefers-reduced-motion: reduce) {
    :root[data-theme-transitioning] {
        transition: none !important;
    }
}

/* Visible keyboard focus. This stylesheet had NO :focus-visible rule at all, so
   every control on academy and all 179 lesson pages fell through to the browser
   default: outline: auto in rgb(16, 16, 16) — a near-black ring that disappears
   on the dark theme and on any filled control, measured on the lesson Run
   button. :focus-visible matches keyboard focus only, so mouse users see no
   change. --accent-line rather than --accent because a focus ring is a LINE and
   should be on the snapped clock like every other rule on the page. */
:focus-visible {
    outline: 2px solid var(--accent-line);
    outline-offset: 2px;
}

:root {
    /* Dark theme (default) — colors match the live pythonmastery.io design system.
       Live tokens: --bg-body #0f172a, --bg-panel #1e293b, --bg-tertiary #334155,
       --text-primary #f8fafc, --text-secondary #94a3b8, --text-muted #64748b,
       --accent-primary #38bdf8 (sky blue), --accent-green #10b981, --accent-red #ef4444. */
    --fg: #f8fafc;
    --fg-2: #94a3b8;
    --fg-3: #8497ad;
    --fg-chrome: #a3b2c4;
    --fg-4: #475569;
    --bg: #0f172a;
    --bg-2: #1e293b;
    --bg-3: #334155;
    --bg-code: #1e293b;
    --bg-out: #0f172a;
    --line: rgba(248, 250, 252, 0.12);
    --line-2: rgba(248, 250, 252, 0.20);
    --line-3: rgba(248, 250, 252, 0.30);
    --accent: #38bdf8;
    --accent-fill: #0369a1;
    --accent-fill-hover: #0284c7;
    --run: #059669;
    --run-deep: #047857;
    --accent-line: #38bdf8;
    --accent-soft: rgba(56, 189, 248, 0.10);
    --good: #10b981;
    --good-soft: rgba(16, 185, 129, 0.15);
    --warn: #f59e0b;
    --warn-soft: rgba(245, 158, 11, 0.15);
    --err: #ef4444;

    /* Syntax — dark, tuned to the slate palette */
    --syn-kw:  #f472b6;
    --syn-str: #7dd3fc;
    --syn-com: #8b9bb0;
    --syn-fn:  #c4b5fd;
    --syn-num: #fbbf24;

    --radius: 6px;
    --max: 1180px;

    /* Aliases for the shared feedback modal (assets/feedback.css), which is
       written against the IDE's token names. Mirrors what styles.css already
       does in the other direction for the shared theme FAB — one alias block per
       side keeps both stylesheets able to host the same shared components. */
    --accent-primary: var(--accent);
    --border-main: var(--line);
    --text-primary: var(--fg);
    --text-secondary: var(--fg-2);
}
html[data-theme="light"] {
    /* Light theme — matches live site light theme. */
    --fg: #334155;
    --fg-2: #475569;
    --fg-3: #5e6f84;
    --fg-chrome: #4b5866;
    --fg-4: #94a3b8;
    --bg: #f1f5f9;
    --bg-2: #ffffff;
    --bg-3: #cbd5e1;
    --bg-code: #ffffff;
    --bg-out: #ffffff;
    --line: rgba(51, 65, 85, 0.12);
    --line-2: rgba(51, 65, 85, 0.20);
    --line-3: rgba(51, 65, 85, 0.30);
    --accent: #2563eb;
    --accent-fill: #2563eb;
    --accent-fill-hover: #1d4ed8;
    --run: #059669;
    --run-deep: #047857;
    --accent-line: #2563eb;
    --accent-soft: rgba(37, 99, 235, 0.08);
    --good: #10b981;
    --good-soft: rgba(16, 185, 129, 0.10);
    --warn: #d97706;
    --warn-soft: rgba(217, 119, 6, 0.10);
    --err: #dc2626;

    --syn-kw:  #be185d;
    --syn-str: #0369a1;
    --syn-com: #5a6b80;
    --syn-fn:  #7c3aed;
    --syn-num: #b45309;
}
/* Eye-saver — warm low-contrast cream theme (Solarized-inspired) */
html[data-theme="eye-saver"] {
    --fg: #4a3f2a;
    --fg-2: #6b5d3f;
    --fg-3: #70603e;
    --fg-chrome: #6f6142;
    --fg-4: #6f6142;
    --bg: #fdf6e3;
    --bg-2: #f5edd6;
    --bg-3: #ede4c8;
    --bg-code: #f5edd6;
    --bg-out: #faf3df;
    /* Alpha, not opaque — deliberately, and it matters during a theme switch.
       An ALPHA line is composited over whatever is behind it, so it stays darker
       than its background by a proportional amount and can never cross it. An
       OPAQUE line is a fixed luminance, and the surface sweeping past it during a
       switch WILL pass through that value: the header rule measured contrast 1.01
       (invisible) for part of a 79ms window on dark -> eye-saver, and only on the
       eye-saver directions, because dark and light already used alpha here.
       These three render within 8/255 per channel of the opaque colours they
       replace across every eye-saver surface, so the resting look is unchanged. */
    --line: rgba(196, 182, 138, 0.5);
    --line-2: rgba(146, 126, 74, 0.5);
    --line-3: rgba(94, 72, 20, 0.5);
    --accent: #96521f;
    --accent-fill: #96521f;
    --accent-fill-hover: #b46b3c;
    --run: #5a7a2f;
    --run-deep: #46601f;
    --accent-line: #b46b3c;
    --accent-soft: rgba(180, 107, 60, 0.10);
    --good: #5a7a2f;
    --good-soft: rgba(90, 122, 47, 0.10);
    --warn: #b58900;
    --warn-soft: rgba(181, 137, 0, 0.10);
    --err: #b8472f;

    --syn-kw:  #b8472f;
    --syn-str: #5a7a2f;
    --syn-com: #736345;
    --syn-fn:  #6c5b9c;
    --syn-num: #c47118;
}

body {
    font-family: 'Inter', 'Inter-fallback', -apple-system, BlinkMacSystemFont, sans-serif;
    font-feature-settings: 'cv11', 'ss01', 'cv05';
    background: var(--bg); color: var(--fg);
    -webkit-font-smoothing: antialiased;
    font-size: 15.5px; line-height: 1.6;
}
a { color: inherit; }
.mono, .tab-num { font-variant-numeric: tabular-nums; }
.mono { font-family: 'JetBrains Mono', ui-monospace, monospace; }

.preview-tag {
    background: var(--warn); color: #1c2128;
    text-align: center; padding: 5px;
    font-size: 0.72rem;
    font-family: 'JetBrains Mono', monospace;
    font-weight: 500; letter-spacing: 0.04em;
}
.shell { max-width: var(--max); margin: 0 auto; padding: 0 24px; }

.top {
    display: flex; align-items: center; justify-content: space-between;
    padding: 18px 0; border-bottom: 1px solid var(--line);
}
.brand {
    display: inline-flex; align-items: center; gap: 10px;
    font-weight: 600; font-size: 0.98rem;
    text-decoration: none; color: var(--fg);
}
/* The Indent mark (PM_MARK_SVG) + a Bricolage Grotesque wordmark. */
.mark { width: 22px; height: 22px; flex-shrink: 0; }
.mark svg { display: block; width: 100%; height: 100%; }
.brand-name {
    font-family: 'Bricolage Grotesque', 'Inter', sans-serif; font-weight: 700;
    font-size: 0.98rem; letter-spacing: -0.03em;
}
.brand-name small {
    display: block; font-size: 0.66rem; color: var(--fg-3);
    font-weight: 400; margin-top: -1px; letter-spacing: 0.03em;
}
/* Header nav lives in /assets/site-chrome.css — one definition shared with
   index.html, which used to carry its own copy under .grid-header .nav. */

.theme-btn {
    background: transparent; border: 1px solid var(--line);
    color: var(--fg-2); padding: 5px 8px; border-radius: 4px;
    cursor: pointer; font-size: 0.78rem; font-family: inherit;
}
.theme-btn:hover { color: var(--fg); border-color: var(--line-2); }

.crumb {
    font-size: 0.8rem; color: var(--fg-3);
    font-family: 'JetBrains Mono', monospace;
    margin: 22px 0 0;
    /* The trail is emitted with no whitespace between its <a> and <span>, so as
       inline text it had no soft-wrap opportunity at all and the last segment
       hung off the page. As flex items each segment can drop to the next line. */
    display: flex; flex-wrap: wrap; align-items: baseline;
}
.crumb a { color: var(--fg-3); text-decoration: none; }
.crumb a:hover { color: var(--fg-2); }
.crumb .sep { padding: 0 6px; opacity: 0.6; }

.btn {
    display: inline-flex; align-items: center; gap: 6px;
    padding: 10px 18px; border-radius: var(--radius);
    font-size: 0.92rem; font-weight: 600;
    text-decoration: none; cursor: pointer;
    border: 1px solid transparent; font-family: inherit;
    transition: background 0.12s, border-color 0.12s, color 0.12s;
}
.btn-pri { background: var(--accent-fill); color: #fff; }
.btn-pri:hover { background: var(--accent-fill-hover); }
.btn-ghost {
    background: transparent; color: var(--fg); border-color: var(--line-2);
}
.btn-ghost:hover { background: var(--bg-2); }

/* Academy hero */
.hero {
    display: grid; grid-template-columns: 1.35fr 1fr;
    gap: 56px; align-items: center;
    padding: 56px 0 64px;
    border-bottom: 1px solid var(--line);
}
@media (max-width: 760px) { .hero { grid-template-columns: 1fr; gap: 36px; padding: 40px 0 48px; } }
.hero h1 {
    font-size: clamp(1.85rem, 4vw, 2.55rem);
    font-weight: 700; line-height: 1.12;
    letter-spacing: -0.025em;
    margin: 0 0 14px;
}
.hero h1 em {
    font-family: 'Newsreader', Georgia, serif;
    font-style: italic; font-weight: 400; color: var(--fg-2);
}
.hero .lede {
    font-size: 1.04rem; line-height: 1.6;
    color: var(--fg-2); margin: 0 0 28px;
    max-width: 520px;
}
.hero .lede strong { color: var(--fg); font-weight: 600; }
.cta-row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.facts {
    display: flex; flex-wrap: wrap; gap: 32px;
    margin-top: 36px; padding-top: 24px;
    border-top: 1px solid var(--line);
    font-size: 0.84rem; color: var(--fg-2);
}
.facts .n {
    display: block; font-size: 1.22rem; color: var(--fg);
    font-weight: 600; letter-spacing: -0.01em;
    font-variant-numeric: tabular-nums; margin-bottom: 2px;
}

.code-card {
    background: var(--bg-2); border: 1px solid var(--line);
    border-radius: 8px; overflow: hidden;
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.81rem; line-height: 1.65;
}
.code-head {
    display: flex; align-items: center; justify-content: space-between;
    padding: 9px 14px; border-bottom: 1px solid var(--line);
    font-size: 0.72rem; color: var(--fg-3);
}
.code-head .dots { display: flex; gap: 5px; }
.code-head .dots i {
    width: 9px; height: 9px; border-radius: 50%;
    background: var(--line-2); display: inline-block;
}
.code-body {
    padding: 16px; margin: 0;
    color: var(--fg); white-space: pre;
    overflow-x: auto; font-family: 'JetBrains Mono', monospace;
}
.kw  { color: var(--syn-kw); }
.str { color: var(--syn-str); }
.com { color: var(--syn-com); font-style: italic; }
.fn  { color: var(--syn-fn); }
.num { color: var(--syn-num); }

.section { padding: 56px 0; border-bottom: 1px solid var(--line); }
.section:last-of-type { border-bottom: 0; }
.section-head {
    display: flex; align-items: baseline; justify-content: space-between;
    margin-bottom: 28px; gap: 16px;
}
.section-head h2 { font-size: 1.05rem; font-weight: 600; margin: 0; }
.section-head .ann {
    font-size: 0.78rem; color: var(--fg-3);
    font-family: 'JetBrains Mono', monospace;
}

.grid {
    display: grid;
    /* min() so the 320px track floor cannot exceed the container — a bare
       minmax(320px, 1fr) forced a 320px page minimum on a 280px foldable. */
    grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
    gap: 20px;
}
.card {
    background: var(--bg-2);
    border: 1px solid var(--line);
    border-radius: 12px;
    padding: 26px 26px 22px;
    display: flex; flex-direction: column;
    transition: transform 0.18s cubic-bezier(.2,.7,.3,1),
                box-shadow 0.18s,
                border-color 0.18s,
                background 0.15s;
    position: relative;
    overflow: hidden;
}
.card::before {
    /* Top accent strip — hidden at rest, animates left→right on hover.
       Mirrors the Tutorial Lab assessment-card pattern for site-wide consistency. */
    content: "";
    position: absolute;
    top: 0; left: 0; right: 0; height: 4px;
    background: var(--track-color, var(--accent, #38bdf8));
    transform: scaleX(0);
    transform-origin: left center;
    transition: transform 0.32s cubic-bezier(0.2, 0.7, 0.3, 1);
}
.card:hover {
    transform: translateY(-2px);
    border-color: var(--line-2);
    box-shadow: 0 10px 30px -12px rgba(0,0,0,0.35),
                0 2px 6px -2px rgba(0,0,0,0.18);
}
.card:hover::before { transform: scaleX(1); }

/* Per-track colour bands (subtle differentiation) */
.card[data-path="fundamentals"]                  { --track-color: #5b9dff; }
.card[data-path="intermediate"]                  { --track-color: #d6a8ff; }
.card[data-path="advanced"]                      { --track-color: #ff7b7b; }
.card[data-path="howto"]                         { --track-color: #e0b341; }
.card[data-path="datascience"],
.card[data-path="datascience-fundamentals"]      { --track-color: #4dba6a; }
.card[data-path="ml-fundamentals"]               { --track-color: #4dba6a; }
.card[data-path="deep-learning-fundamentals"]    { --track-color: #f3719a; }
.card[data-path="ai"],
.card[data-path="generative-ai"]                 { --track-color: #f3719a; }
/* Errors gets the amber of a warning light — the one card you arrive at because
   something is already wrong, rather than because you chose to learn a subject. */
.card[data-path="errors"]                        { --track-color: #e8833a; }

/* Featured: taller, stronger accent rail, gentle gradient backdrop */
.card.fea {
    background:
        linear-gradient(135deg, var(--accent-soft) 0%, transparent 60%),
        var(--bg-2);
    border-color: var(--accent-line);
    box-shadow: 0 0 0 1px var(--accent-line), 0 8px 24px -10px rgba(91,157,255,0.25);
}
.card.fea::before { height: 4px; opacity: 1; background: var(--accent); }
.card.fea:hover {
    transform: translateY(-3px);
    box-shadow: 0 0 0 1px var(--accent-line),
                0 18px 40px -14px rgba(91,157,255,0.35),
                0 4px 10px -2px rgba(0,0,0,0.12);
}

.tag {
    display: inline-flex; align-items: center; gap: 7px;
    font-size: 0.66rem; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.11em;
    color: var(--accent); margin-bottom: 12px;
}
.tag .dot {
    width: 6px; height: 6px; border-radius: 50%;
    background: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
}

.card h3 {
    font-size: 1.22rem; font-weight: 700;
    margin: 0 0 10px;
    letter-spacing: -0.015em;
    color: var(--fg);
}
.card.fea h3 { font-size: 1.32rem; }

.blurb {
    font-size: 0.93rem; line-height: 1.6;
    color: var(--fg-2);
    margin: 0 0 20px;
}

/* Spec: documentation-style metadata grid, but with breathing room */
.spec {
    padding: 16px 0;
    margin: 0 0 18px;
    border-top: 1px solid var(--line);
    border-bottom: 1px solid var(--line);
    display: grid;
    grid-template-columns: 80px 1fr;
    gap: 8px 16px;
    font-size: 0.82rem;
    font-family: 'JetBrains Mono', monospace;
}
.spec dt {
    color: var(--fg-3);
    margin: 0;
    font-weight: 500;
}
.spec dd {
    color: var(--fg);
    margin: 0;
    font-weight: 500;
    font-variant-numeric: tabular-nums;
}

.build {
    font-size: 0.87rem; color: var(--fg-2);
    line-height: 1.6;
    padding: 12px 14px;
    background: var(--bg);
    border-radius: 6px;
    border: 1px solid var(--line);
    margin-bottom: 18px;
}
.build em {
    font-style: normal; color: var(--fg);
    font-weight: 600;
    display: inline-block;
    margin-right: 6px;
}

.card-foot {
    display: flex; align-items: center; justify-content: space-between;
    margin-top: auto; padding-top: 4px;
    gap: 12px;
}
.prog {
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.74rem; color: var(--fg-3);
    flex: 1;
    min-width: 0;
}
.card[data-status="in-progress"] .prog { color: var(--accent); }
.card[data-status="complete"] .prog { color: var(--good); }
/* Thin progress bar pinned to the bottom edge of each track card.
   --prog-pct is set by academy-build-runtime.js to "N%". Uses a real child
   element instead of ::after — the card already uses ::after for the ✓ badge. */
.card-prog-bar {
    position: absolute;
    left: 0;
    bottom: 0;
    height: 3px;
    width: var(--prog-pct, 0%);
    background: var(--accent, #38bdf8);
    transition: width 0.4s cubic-bezier(0.2, 0.7, 0.3, 1);
    pointer-events: none;
}
.card[data-status="complete"] .card-prog-bar { background: var(--good, #10b981); }
.card[data-status="complete"]::after {
    content: "✓";
    position: absolute;
    top: 16px; right: 16px;
    width: 24px; height: 24px;
    border-radius: 50%;
    background: var(--good);
    color: white;
    font-size: 0.82rem;
    display: flex; align-items: center; justify-content: center;
    font-weight: 700;
    box-shadow: 0 0 0 3px var(--good-soft);
}
.go {
    font-size: 0.88rem; font-weight: 600;
    color: var(--accent); text-decoration: none;
    padding: 6px 12px;
    border: 1px solid transparent;
    border-radius: 6px;
    transition: background 0.15s, border-color 0.15s;
}
.go:hover {
    background: var(--accent-soft);
    border-color: var(--accent-line);
    text-decoration: none;
}
.card.fea .go {
    background: var(--accent-fill);
    color: #fff;
    border-color: var(--accent-fill);
}
.card.fea .go:hover { background: var(--accent-fill-hover); border-color: var(--accent-fill-hover); }

.honest h2 {
    font-size: 1.45rem; font-weight: 700;
    letter-spacing: -0.02em; margin: 0 0 6px;
}
.honest .by {
    font-size: 0.8rem; color: var(--fg-3); font-style: italic;
    font-family: 'Newsreader', Georgia, serif;
    margin: 0 0 28px;
}
.honest .row { display: grid; grid-template-columns: 1fr 1fr; gap: 48px; }
@media (max-width: 640px) { .honest .row { grid-template-columns: 1fr; gap: 32px; } }
.honest h4 {
    font-size: 0.76rem; font-weight: 600;
    text-transform: uppercase; letter-spacing: 0.09em;
    color: var(--fg-3); margin: 0 0 12px;
}
.honest ul {
    list-style: none; padding: 0; margin: 0;
    font-size: 0.95rem; line-height: 1.75; color: var(--fg-2);
}
.honest li { padding-left: 18px; position: relative; }
.honest li::before { content: "—"; position: absolute; left: 0; color: var(--fg-3); }

.free {
    background: var(--bg-2); border: 1px solid var(--line);
    border-radius: 8px; padding: 28px 32px;
    display: grid; grid-template-columns: auto 1fr;
    gap: 24px; align-items: center;
}
.free-lab {
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.7rem; color: var(--fg-3);
    text-transform: uppercase; letter-spacing: 0.1em;
    font-weight: 600;
    border-right: 1px solid var(--line);
    padding-right: 24px;
}
.free-lab strong {
    display: block; font-family: 'Inter', sans-serif;
    font-size: 1.55rem; color: var(--fg);
    font-weight: 700; letter-spacing: -0.02em;
    margin-top: 4px; text-transform: none;
}
.free p { font-size: 0.92rem; color: var(--fg-2); line-height: 1.6; margin: 0; }
@media (max-width: 580px) {
    .free { grid-template-columns: 1fr; gap: 16px; }
    .free-lab { border-right: 0; border-bottom: 1px solid var(--line); padding-right: 0; padding-bottom: 16px; }
}

/* ── Error index ────────────────────────────────────────────────────────────
   Two ways into the same thirty pages: by family for someone holding the exact
   message, by stage for someone who only knows when it broke. The message is set
   in mono because that is how the reader met it — pasted out of a terminal. */
.err-families { display: grid; gap: 34px; }
@media (min-width: 900px) { .err-families { grid-template-columns: 1fr 1fr; gap: 34px 48px; } }

.err-family-name {
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.82rem; letter-spacing: 0.06em; text-transform: uppercase;
    color: var(--fg-3); font-weight: 600;
    display: flex; align-items: center; gap: 10px;
    margin: 0 0 14px; padding-bottom: 8px;
    border-bottom: 1px solid var(--line);
    /* "TimeoutError / requests.exceptions.ReadTimeout" is 302px of unbreakable
       mono in a flex row, which set the whole errors page's minimum width and
       zoomed it out on a 280px foldable. Let the row wrap and the name break. */
    flex-wrap: wrap; overflow-wrap: anywhere; min-width: 0;
}
.err-count {
    font-size: 0.72rem; color: var(--fg-3);
    background: var(--bg-3); border-radius: 100px; padding: 1px 8px;
    font-variant-numeric: tabular-nums;
}
.err-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 16px; }
.err-item { margin: 0; }

/* ─── Tips & tricks ───────────────────────────────────────────────────────── */
.tip-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 14px; }
.tip-card, .story-card {
    display: flex; flex-direction: column; gap: 6px; padding: 16px 18px;
    border: 1px solid var(--line); border-radius: 10px; background: var(--bg-2);
    text-decoration: none; color: var(--fg-2); transition: border-color .15s ease, transform .15s ease;
}
.tip-card:hover, .story-card:hover { border-color: var(--accent); transform: translateY(-1px); }
.tip-card:focus-visible, .story-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.tip-card-title, .story-card-title { color: var(--fg); font-weight: 600; font-size: 1rem; line-height: 1.35; }
.tip-card-desc { font-size: .88rem; line-height: 1.55; }
.story-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 14px; }
.story-card { border-left: 3px solid var(--pm-red, var(--accent)); }
.story-card-co { font-size: .72rem; letter-spacing: .1em; text-transform: uppercase; color: var(--fg-3); font-weight: 600; }
.story-card-tip { font-size: .88rem; line-height: 1.5; }
.tip-tag-mistake {
    align-self: flex-start; font-size: .68rem; letter-spacing: .08em; text-transform: uppercase; font-weight: 700;
    color: var(--pm-red, var(--accent)); border: 1px solid currentColor; border-radius: 999px; padding: 1px 8px;
}
.tip-filter { display: flex; gap: 8px; flex-wrap: wrap; margin: 2rem 0 -.5rem; }
.tip-filter[hidden] { display: none; }
.tip-filter button {
    font: inherit; font-size: .88rem; padding: 6px 14px; border-radius: 999px; cursor: pointer;
    border: 1px solid var(--line); background: var(--bg-2); color: var(--fg-2);
}
.tip-filter button:hover { border-color: var(--accent); color: var(--fg); }
.tip-filter button[aria-pressed="true"] { background: var(--accent-fill); border-color: var(--accent-fill); color: #fff; }
.tip-filter button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.tip-filter-n { opacity: .75; margin-left: 2px; }
/* Filtered view: only mistake cards and the sections that hold one; case studies step aside. */
#tips-hub[data-filter="mistakes"] .tip-card:not(.is-mistake),
#tips-hub[data-filter="mistakes"] .tip-cat:not(.has-mistakes),
#tips-hub[data-filter="mistakes"] .story-group { display: none; }
/* Own block, not .hero: .hero is the homepage's two-column grid, which put the
   eyebrow alone on the left and squeezed the title into the right column. */
.tip-hero { padding: 3rem 0 1.25rem; max-width: 48rem; }
.tip-eyebrow { font-size: .75rem; letter-spacing: .12em; text-transform: uppercase; font-weight: 700; color: var(--accent); }
.tip-eyebrow a { color: inherit; text-decoration: none; }
.tip-eyebrow a:hover { text-decoration: underline; }
.tip-title { font-size: clamp(1.75rem, 3.6vw, 2.4rem); font-weight: 700; line-height: 1.15; letter-spacing: -0.02em; margin: .6rem 0 .8rem; color: var(--fg); }
.tip-lede { font-size: 1.04rem; line-height: 1.6; color: var(--fg-2); margin: 0; }
.tip-body { max-width: 48rem; }
.tip-body h2 { margin-top: 2rem; }
.tip-lessons { max-width: 48rem; margin: 1.5rem 0 0; color: var(--fg-2); }
.tip-source-list { margin: 0; padding-left: 1.2rem; color: var(--fg-2); line-height: 1.7; }
@media (max-width: 640px) { .story-grid { grid-template-columns: 1fr; } }

/* "Tips for this topic" on a lesson page. */
.tips-box {
    margin: 2rem 0 1.5rem; padding: 16px 18px; border: 1px solid var(--line);
    border-left: 3px solid var(--accent); border-radius: 0 10px 10px 0; background: var(--bg-2);
}
.tips-box-h { font-size: .75rem; letter-spacing: .1em; text-transform: uppercase; font-weight: 700; color: var(--fg-3); margin-bottom: 8px; }
.tips-box-list { list-style: none; margin: 0 0 8px; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.tips-box-list li { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; }
.tips-box-list a { color: var(--fg); font-weight: 600; }
.tips-box-cat { font-size: .78rem; color: var(--fg-3); }
.tips-box-all { font-size: .85rem; color: var(--accent); }
.err-link { text-decoration: none; display: block; }
.err-code {
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.86rem; line-height: 1.45;
    color: var(--accent); overflow-wrap: anywhere;
}
.err-link:hover .err-code { text-decoration: underline; text-underline-offset: 3px; }
.err-gloss { margin: 3px 0 0; font-size: 0.9rem; color: var(--fg-2); line-height: 1.5; }

.err-whens { display: grid; gap: 26px; }
@media (min-width: 900px) { .err-whens { grid-template-columns: 1fr 1fr; gap: 26px 48px; } }
.err-when h3 {
    font-size: 1rem; margin: 0 0 6px;
    display: flex; align-items: center; gap: 10px;
}
.err-when-blurb { margin: 0 0 8px; font-size: 0.9rem; color: var(--fg-2); line-height: 1.55; }
.err-when-links { margin: 0; display: grid; gap: 5px; }
.err-when-link {
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.79rem; line-height: 1.5;
    color: var(--fg-2); text-decoration: none;
    overflow-wrap: anywhere;
}
.err-when-link:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 3px; }

/* Thematic break between lesson sections. The browser default is an inset 3D
   groove designed for grey documents in 1996; on a dark ground it reads as a
   scratch. A hairline on the same token as every other rule on the page, with
   real air around it, is what the separator was for. */
.lesson-body hr,
.prose hr,
article hr {
    border: 0;
    height: 1px;
    background: var(--line);
    margin: 34px 0;
}

/* Lesson page */
.page {
    display: grid; grid-template-columns: 240px 1fr;
    gap: 56px; padding: 36px 0 64px;
}
@media (max-width: 920px) {
    .page { grid-template-columns: 1fr; gap: 28px; }
    .side { order: 2; }
}

.side { position: sticky; top: 24px; align-self: start; }
.side-h {
    font-size: 0.72rem; text-transform: uppercase;
    letter-spacing: 0.1em; color: var(--fg-3);
    margin: 0 0 14px; font-weight: 600;
}
.side-title { font-size: 0.95rem; font-weight: 600; color: var(--fg); margin: 0 0 4px; }
.side-meta {
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.74rem; color: var(--fg-3);
    margin-bottom: 18px;
}
.lesson-list { list-style: none; padding: 0; margin: 0; border-top: 1px solid var(--line); }
.lesson-list li { border-bottom: 1px solid var(--line); }
.lesson-list a {
    display: flex; align-items: center; gap: 10px;
    padding: 9px 4px; text-decoration: none;
    color: var(--fg-2); font-size: 0.86rem;
    font-variant-numeric: tabular-nums;
}
.lesson-list a:hover { color: var(--fg); }
.lesson-list .num {
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.72rem; color: var(--fg-4); min-width: 22px;
}
.lesson-list .state {
    width: 14px; height: 14px; flex-shrink: 0;
    border-radius: 50%; border: 1.5px solid var(--line-2);
    display: inline-block; position: relative;
}
.lesson-list .now .state {
    background: var(--accent); border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
}
.lesson-list .now a { color: var(--fg); font-weight: 600; }
.lesson-list .future { opacity: 0.65; }
/* ...unless it has actually been completed — a tick must not be greyed out. */
.lesson-list li.done-js { opacity: 1; }
.side-foot {
    margin-top: 22px;
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.74rem; color: var(--fg-3);
    display: flex; align-items: center; gap: 8px;
}
.pbar {
    flex: 1; height: 4px; background: var(--bg-3);
    border-radius: 2px; overflow: hidden;
}
.pbar i { display: block; height: 100%; background: var(--accent); }

/* "What builds on this" — cross-track downstream lessons from prereq graph */
.side-downstream {
    margin-top: 28px;
    padding-top: 18px;
    border-top: 1px solid var(--line);
}
.side-downstream .side-h { margin-bottom: 10px; }
.downstream-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
}
.downstream-list a {
    display: grid;
    grid-template-columns: auto 1fr;
    align-items: start;
    gap: 8px;
    padding: 8px 10px;
    border-radius: 6px;
    color: var(--fg-2);
    text-decoration: none;
    border: 1px solid transparent;
    transition: border-color 0.15s, background 0.15s, color 0.15s;
}
.downstream-list a:hover {
    color: var(--fg);
    background: var(--bg);
    border-color: var(--line);
}
.ds-arrow {
    color: var(--accent);
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.85rem;
    line-height: 1.4;
    flex-shrink: 0;
}
.ds-body {
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
}
.ds-title {
    font-size: 0.84rem;
    line-height: 1.25;
    color: inherit;
}
.ds-track {
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.68rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--fg-3);
}

.main { min-width: 0; }
.head-row {
    display: flex; align-items: center; gap: 12px;
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.76rem; color: var(--fg-3);
    margin-bottom: 14px; flex-wrap: wrap;
}
.head-row .pill {
    background: var(--bg-2); border: 1px solid var(--line);
    color: var(--fg-2); padding: 2px 9px; border-radius: 100px;
    font-size: 0.72rem;
}
.head-row .pill.lvl {
    color: var(--good);
    border-color: rgba(77, 186, 106, 0.3);
    background: var(--good-soft);
}
.head-row .sep { opacity: 0.4; }

h1.lesson-title {
    font-size: clamp(1.8rem, 3.5vw, 2.4rem);
    font-weight: 700; line-height: 1.15;
    letter-spacing: -0.025em; margin: 0 0 14px;
}
h1.lesson-title em {
    font-family: 'Newsreader', Georgia, serif;
    font-style: italic; font-weight: 400; color: var(--fg-2);
}

section.lesson-sec { margin: 44px 0; }
section.lesson-sec > .sec-h {
    display: flex; align-items: baseline; justify-content: space-between;
    margin-bottom: 18px; padding-bottom: 10px;
    border-bottom: 1px solid var(--line);
}
section.lesson-sec > .sec-h h2 {
    font-size: 1.18rem; font-weight: 600;
    margin: 0; letter-spacing: -0.01em;
}
section.lesson-sec > .sec-h .read {
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.74rem; color: var(--fg-3);
}

.prose h3 {
    font-size: 1.05rem; font-weight: 600;
    margin: 28px 0 10px; letter-spacing: -0.005em;
}
.prose h2 {
    font-size: 1.15rem; font-weight: 600;
    margin: 32px 0 12px; letter-spacing: -0.01em;
}
.prose p { margin: 12px 0; color: var(--fg-2); }
.prose p strong { color: var(--fg); font-weight: 600; }
.prose code {
    background: var(--bg-2); border: 1px solid var(--line);
    padding: 1px 6px; border-radius: 3px;
    font-family: 'JetBrains Mono', monospace; font-size: 0.86em;
    color: var(--fg);
}
.prose ul { color: var(--fg-2); padding-left: 22px; margin: 12px 0; }
.prose ul li { margin: 4px 0; }
.prose blockquote {
    margin: 16px 0; padding: 2px 16px;
    border-left: 3px solid var(--line); background: var(--bg-2); border-radius: 0 4px 4px 0;
}
.prose blockquote p { margin: 10px 0; }

.code {
    background: var(--bg-code);
    border: 1px solid var(--line-2);
    border-radius: 8px;
    overflow: hidden;
    margin: 18px 0;
    box-shadow: 0 1px 0 rgba(0,0,0,0.04);
}
.code-h {
    display: flex; align-items: center; justify-content: space-between;
    padding: 8px 14px;
    background: var(--bg-3);
    border-bottom: 1px solid var(--line-2);
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.72rem; color: var(--fg-2);
    min-height: 36px;
}
.code-lang {
    text-transform: lowercase;
    letter-spacing: 0.02em;
    color: var(--fg-chrome);
    font-weight: 500;
}
.code[data-lang="java"] .code-lang::before,
.code[data-lang="javascript"] .code-lang::before,
.code[data-lang="bash"] .code-lang::before,
.code[data-lang="shell"] .code-lang::before { content: "» "; opacity: 0.6; }

.code-h .actions { display: flex; gap: 6px; }
.btn-code {
    background: var(--bg-2);
    border: 1px solid var(--line-2);
    color: var(--fg-chrome);
    padding: 4px 11px;
    border-radius: 4px;
    font-size: 0.72rem;
    font-family: 'JetBrains Mono', monospace;
    font-weight: 500;
    cursor: pointer;
    transition: background 0.12s, color 0.12s, border-color 0.12s;
    line-height: 1.4;
}
.btn-code:hover {
    color: var(--fg);
    border-color: var(--line-3);
    background: var(--bg);
}
.btn-code.run {
    /* Green because it EXECUTES — the same identity as the IDE's Run Code button,
       so blue stays brand/navigation and green always means "this runs your code".
       border-color matches the fill deliberately: this border is part of the fill,
       not a ring, so it must stay on the fill's clock during a theme switch (see
       the --accent / --accent-line split above for why that matters).
       The old accent-blue fill also failed contrast — white on dark-theme
       #38bdf8 is 2.14:1. These stops put the white label above AA. */
    background: linear-gradient(135deg, var(--run) 0%, var(--run-deep) 100%);
    color: #fff;
    border-color: var(--run-deep);
    font-weight: 600;
}
.btn-code.run:hover {
    filter: brightness(1.08);
    color: white;
}
.code-b {
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.85rem; line-height: 1.6;
    padding: 16px;
    color: var(--fg);
    background: var(--bg-code);
    white-space: pre; overflow-x: auto;
    margin: 0;
    outline: 2px solid transparent;
    outline-offset: -2px;
    caret-color: var(--accent);
    transition: outline-color 0.12s, background 0.12s;
}
.code-b[contenteditable="true"] { cursor: text; }
.code-b[contenteditable="true"]:hover {
    background: linear-gradient(to right, var(--accent-soft) 0, transparent 3px), var(--bg-code);
}
.code-b[contenteditable="true"]:focus {
    outline-color: var(--accent-line);
    background: var(--bg-code);
}
.code[data-edited="true"] .code-lang::after {
    content: " · edited";
    color: var(--accent);
    font-weight: 600;
    margin-left: 4px;
}
.code-explainer { font-size: 0.92rem; color: var(--fg-2); margin: 6px 0 0; }
.btn-code.reset { color: var(--fg-chrome); }
.btn-code.reset:hover { color: var(--fg); }

/* ─── Auto-generated setup disclosure ────────────────────────────────── */
.code-setup {
    border-top: 1px solid var(--line-2);
    background: var(--bg-3);
    font-size: 0.78rem;
}
.code-setup summary {
    cursor: pointer;
    padding: 8px 16px;
    color: var(--fg-3);
    font-family: 'JetBrains Mono', monospace;
    letter-spacing: 0.02em;
    list-style: none;
    user-select: none;
    transition: color 0.12s, background 0.12s;
}
.code-setup summary::-webkit-details-marker { display: none; }
.code-setup summary:hover {
    color: var(--fg-2);
    background: var(--bg-2);
}
.code-setup[open] summary { color: var(--fg-2); background: var(--bg-2); border-bottom: 1px solid var(--line); }
.code-setup .setup-icon {
    display: inline-block;
    width: 14px; height: 14px;
    line-height: 14px; text-align: center;
    border: 1px solid var(--line-2);
    border-radius: 3px;
    font-weight: 600;
    margin-right: 6px;
    color: var(--fg-2);
    transition: transform 0.18s;
}
.code-setup[open] .setup-icon { transform: rotate(45deg); }
.code-setup summary code {
    background: var(--bg);
    border: 1px solid var(--line);
    padding: 1px 5px;
    border-radius: 3px;
    color: var(--fg);
    font-size: 0.92em;
    margin: 0 2px;
}
.code-setup-body {
    margin: 0;
    padding: 14px 16px;
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.78rem;
    line-height: 1.55;
    color: var(--fg-2);
    background: var(--bg-3);
    white-space: pre;
    overflow-x: auto;
}

/* ─── Progressive hints (used in Projects' "Your Turn" sections) ─────── */
.prose details {
    background: var(--bg-2);
    border: 1px solid var(--line);
    border-left: 3px solid var(--accent-line);
    border-radius: var(--radius);
    padding: 10px 14px;
    margin: 10px 0;
    transition: background 0.12s;
}
.prose details[open] {
    background: var(--bg-3);
}
.prose details > summary {
    cursor: pointer;
    color: var(--fg);
    font-weight: 600;
    font-size: 0.92rem;
    list-style: none;
    user-select: none;
    padding: 2px 0;
    display: flex;
    align-items: center;
    gap: 8px;
}
.prose details > summary::-webkit-details-marker { display: none; }
.prose details > summary::before {
    content: "▸";
    color: var(--accent);
    font-size: 0.8rem;
    transition: transform 0.18s;
    width: 12px;
    display: inline-block;
}
.prose details[open] > summary::before { transform: rotate(90deg); }
.prose details > *:not(summary) {
    margin-top: 8px;
}
/* "Show full solution" gets a slightly stronger visual */
.prose details.solution {
    border-left-color: var(--good);
}
.prose details.solution > summary { color: var(--good); }
.prose details.solution > summary::before { color: var(--good); }

.dive {
    background: var(--bg-2); border: 1px solid var(--line);
    border-left: 2px solid var(--fg-3); border-radius: var(--radius);
    padding: 18px 22px; margin: 14px 0;
}
.dive-lab {
    display: inline-block; font-family: 'JetBrains Mono', monospace;
    font-size: 0.7rem; text-transform: uppercase;
    letter-spacing: 0.08em; color: var(--fg-3);
    font-weight: 600; margin-bottom: 8px;
}
.dive h4 { font-size: 1rem; font-weight: 600; margin: 0 0 8px; }
.dive p { color: var(--fg-2); margin: 8px 0 0; font-size: 0.95rem; }
.dive code {
    background: var(--bg-3); border: 1px solid var(--line);
    padding: 1px 6px; border-radius: 3px;
    font-family: 'JetBrains Mono', monospace; font-size: 0.86em;
}

.tryit-task {
    font-size: 0.95rem; color: var(--fg-2);
    border-left: 3px solid var(--accent-line);
    padding-left: 14px; margin: 14px 0;
}
.tryit-task strong { color: var(--fg); }
.tryit-task code {
    background: var(--bg-2); border: 1px solid var(--line);
    padding: 1px 6px; border-radius: 3px;
    font-family: 'JetBrains Mono', monospace; font-size: 0.86em;
}

.mistakes ul { list-style: none; padding: 0; margin: 0; }
.mistakes li {
    display: flex; gap: 14px; padding: 14px 0;
    border-bottom: 1px solid var(--line);
    font-size: 0.95rem; color: var(--fg-2);
}
.mistakes li:last-child { border-bottom: 0; }
.mistakes .badge {
    flex-shrink: 0; width: 22px; height: 22px;
    background: var(--warn-soft); color: var(--warn);
    border-radius: 50%;
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.74rem; font-weight: 600;
    display: inline-flex; align-items: center; justify-content: center;
    margin-top: 1px;
}
.mistakes code {
    background: var(--bg-2); border: 1px solid var(--line);
    padding: 1px 6px; border-radius: 3px;
    font-family: 'JetBrains Mono', monospace; font-size: 0.86em;
    color: var(--fg);
}
.mistakes strong { color: var(--fg); }

.quiz {
    background: var(--bg-2); border: 1px solid var(--line);
    border-radius: 8px; padding: 24px 26px; margin: 18px 0;
}
.quiz-q {
    font-size: 1rem; font-weight: 600; color: var(--fg);
    margin: 0 0 14px;
}
.quiz-q .q-num {
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.76rem; color: var(--fg-3);
    margin-right: 8px; font-weight: 500;
}
.quiz-q code {
    background: var(--bg-3); border: 1px solid var(--line);
    padding: 1px 6px; border-radius: 3px;
    font-family: 'JetBrains Mono', monospace; font-size: 0.86em;
}
.quiz-foot {
    display: flex; justify-content: space-between; align-items: center;
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.76rem; color: var(--fg-3);
    margin-top: 12px;
}
.quiz-foot .pad { display: flex; gap: 5px; }
.quiz-foot .pad i {
    width: 8px; height: 8px; border-radius: 50%;
    background: var(--bg-3); display: inline-block;
    border: 1px solid var(--line-2);
}
.quiz-foot .pad i.active { background: var(--accent); border-color: var(--accent); }

.summary {
    background: var(--bg-2); border: 1px solid var(--line);
    border-radius: 8px; padding: 24px 26px;
}
.summary ul { list-style: none; padding: 0; margin: 0; }
.summary li {
    padding: 8px 0 8px 22px; position: relative;
    color: var(--fg-2); font-size: 0.94rem;
    border-bottom: 1px dashed var(--line);
}
.summary li:last-child { border-bottom: 0; }
.summary li::before {
    content: "✓"; position: absolute; left: 0; top: 8px;
    color: var(--good); font-weight: 700;
}
.summary strong { color: var(--fg); }

/* ── End-of-lesson CTAs — surface "act on what you just read" intent ── */
/* ── Mark complete ───────────────────────────────────────────────────────────
   Placed between the lesson body and the "what next" cards, because that is the
   question it answers: you have just finished reading. It is a TOGGLE (see
   academy-build-runtime.js) so an accidental click is recoverable, and it shows
   track progress beside itself so the action has a visible consequence beyond
   the button's own label. --good, not --accent: completion is a success state,
   and blue already means navigation across this site. */
.lesson-done {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 12px 18px;
    margin: 48px 0 0;
    padding: 14px 16px;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--bg-2);
}

.mark-complete {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    padding: 8px 14px 8px 10px;
    border: 1px solid var(--line-2);
    border-radius: var(--radius);
    background: var(--bg);
    color: var(--fg);
    font: inherit;
    font-size: 0.92rem;
    font-weight: 500;
    cursor: pointer;
    transition: border-color 0.15s, background 0.15s;
}

.mark-complete:hover {
    border-color: var(--good);
}

.mark-complete-box {
    flex: none;
    width: 18px;
    height: 18px;
    border: 1.5px solid var(--line-3);
    border-radius: 5px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: background 0.15s, border-color 0.15s;
}

/* Drawn tick rather than a glyph — scales cleanly and needs no font support */
.mark-complete-box::after {
    content: "";
    width: 9px;
    height: 5px;
    margin-top: -2px;
    border-left: 2px solid #fff;
    border-bottom: 2px solid #fff;
    transform: rotate(-45deg) scale(0.5);
    opacity: 0;
    transition: opacity 0.15s, transform 0.15s;
}

.mark-complete.is-done {
    border-color: var(--good);
}

/* Deepened rather than plain --good: the white tick measured 2.54:1 against
   #10b981, under the 3:1 minimum for a state indicator. Mixing toward black
   keeps each theme's own green (eye-saver stays olive) while lifting the tick
   clear of it — no extra token to keep in sync. */
.mark-complete.is-done .mark-complete-box {
    background: color-mix(in srgb, var(--good) 72%, #000);
    border-color: color-mix(in srgb, var(--good) 72%, #000);
}

.mark-complete.is-done .mark-complete-box::after {
    opacity: 1;
    transform: rotate(-45deg) scale(1);
}

.lesson-done-meta {
    margin: 0;
    font-size: 0.85rem;
    color: var(--fg-3);
    font-variant-numeric: tabular-nums;
}

@media (prefers-reduced-motion: reduce) {
    .mark-complete,
    .mark-complete-box,
    .mark-complete-box::after {
        transition: none;
    }
}

.lesson-cta-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 14px;
    margin: 32px 0 16px;
}
.lesson-cta {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    gap: 14px;
    padding: 16px 18px;
    background: var(--bg);
    border: 1px solid var(--line);
    border-radius: 12px;
    text-decoration: none;
    color: var(--fg);
    transition: border-color 0.18s, background 0.18s, transform 0.18s;
    position: relative;
    overflow: hidden;
}
.lesson-cta::before {
    /* same top-strip animation pattern as track cards — site-wide consistency */
    content: "";
    position: absolute;
    top: 0; left: 0; right: 0; height: 3px;
    background: var(--accent, #38bdf8);
    transform: scaleX(0);
    transform-origin: left center;
    transition: transform 0.3s cubic-bezier(0.2, 0.7, 0.3, 1);
}
.lesson-cta:hover {
    border-color: var(--accent, #38bdf8);
    background: var(--bg-2);
    transform: translateY(-1px);
}
.lesson-cta:hover::before { transform: scaleX(1); }
.lesson-cta-icon {
    display: grid; place-items: center;
    width: 36px; height: 36px;
    border-radius: 50%;
    background: var(--bg-2);
    color: var(--accent, #38bdf8);
    flex-shrink: 0;
}
.lesson-cta-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.lesson-cta-body strong {
    font-size: 0.95rem;
    font-weight: 600;
    color: var(--fg);
    letter-spacing: -0.01em;
}
.lesson-cta-body small {
    font-size: 0.78rem;
    color: var(--fg-3);
    font-family: var(--font-sans, inherit);
}
.lesson-cta-arrow {
    color: var(--fg-3);
    font-size: 1.1rem;
    transition: transform 0.18s, color 0.18s;
}
.lesson-cta:hover .lesson-cta-arrow {
    color: var(--accent, #38bdf8);
    transform: translateX(3px);
}
@media (max-width: 640px) { .lesson-cta-row { grid-template-columns: 1fr; } }

/* "Practice this": the same cards as .lesson-cta, minus the icon, one per
   exercise on practicepython.in. */
.practice-lede { margin: 0 0 14px; color: var(--fg-2); font-size: 0.95rem; }
.practice-list {
    list-style: none; margin: 0; padding: 0;
    display: grid; grid-template-columns: 1fr 1fr; gap: 12px;
}
.practice-list li { min-width: 0; }
.lesson-cta.practice-card { grid-template-columns: 1fr auto; height: 100%; box-sizing: border-box; }
@media (max-width: 640px) { .practice-list { grid-template-columns: 1fr; } }

.next-nav {
    display: grid; grid-template-columns: 1fr 1fr;
    gap: 16px; margin: 24px 0 0;
}
@media (max-width: 640px) { .next-nav { grid-template-columns: 1fr; } }
.next-nav a {
    display: block; padding: 18px 20px;
    background: var(--bg); border: 1px solid var(--line);
    border-radius: 8px; text-decoration: none; color: var(--fg);
    transition: background 0.15s, border-color 0.15s;
}
.next-nav a:hover {
    background: var(--bg-2); border-color: var(--line-2);
}
.next-nav .dir {
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.72rem; color: var(--fg-3); margin-bottom: 4px;
}
.next-nav .ttl { font-size: 0.95rem; font-weight: 600; }
.next-nav .met {
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.74rem; color: var(--fg-3); margin-top: 4px;
}
.next-nav .primary {
    background: var(--accent-soft);
    border-color: rgba(91, 157, 255, 0.3);
}
.next-nav .primary .ttl { color: var(--accent); }

/* Page footer lives in /assets/site-chrome.css, for the same reason as the
   nav: it has to be byte-identical on the IDE landing and on a lesson. */

/* ─── New: key takeaways list & runtime extras ───────────────────────── */
.keypoints-list {
    list-style: none; padding: 0; margin: 0;
}
.keypoints-list li {
    padding: 8px 0 8px 22px; position: relative;
    color: var(--fg-2); font-size: 0.94rem;
    border-bottom: 1px dashed var(--line);
}
.keypoints-list li:last-child { border-bottom: 0; }
.keypoints-list li::before {
    content: "·"; position: absolute; left: 6px; top: 2px;
    color: var(--accent); font-weight: 700; font-size: 1.4rem;
}

/* Pyodide output panel — distinct from code area */
.code-out {
    border-top: 2px solid var(--line-2);
    padding: 14px 16px 16px;
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.8rem;
    line-height: 1.55;
    color: var(--fg-2);
    background: var(--bg-out);
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    max-height: 320px;
    overflow-y: auto;
}
.code-out:empty { display: none; }
.code-out.is-error  { color: var(--err); }
.code-out.is-warn   { color: var(--warn); }
.code-out.is-ok     { color: var(--good); }
.code-out-label {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 0.66rem;
    color: var(--fg-3);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    font-weight: 600;
    margin-bottom: 6px;
}
.code-out-label::before {
    content: "";
    width: 6px; height: 6px;
    border-radius: 50%;
    background: var(--accent);
    display: inline-block;
    box-shadow: 0 0 0 2px var(--accent-soft);
}
.code-out.is-error .code-out-label::before { background: var(--err); box-shadow: 0 0 0 2px rgba(196,48,43,0.18); }
.code-out.is-warn  .code-out-label::before { background: var(--warn); box-shadow: 0 0 0 2px var(--warn-soft); }
.code-out.is-ok    .code-out-label::before { background: var(--good); box-shadow: 0 0 0 2px var(--good-soft); }
.code-out-text { margin: 0; font-family: inherit; }

.btn-code.run.busy { opacity: 0.7; pointer-events: none; }
.btn-code.run.busy::before {
    content: ""; display: inline-block;
    width: 10px; height: 10px;
    border-radius: 50%;
    border: 1.5px solid currentColor;
    border-top-color: transparent;
    margin-right: 6px;
    vertical-align: -1px;
    animation: pm-spin 0.8s linear infinite;
}
@keyframes pm-spin { to { transform: rotate(360deg); } }

/* Quiz solution reveal + hints */
.quiz-foot { gap: 10px; }
.quiz-foot button { cursor: pointer; }
.quiz-solution { margin-top: 18px; }
.quiz-sol-label {
    font-size: 0.74rem; color: var(--good);
    text-transform: uppercase; letter-spacing: 0.08em;
    font-weight: 600; margin: 0 0 8px;
    font-family: 'JetBrains Mono', monospace;
}
.quiz-hint {
    margin: 12px 0;
    background: var(--bg-3);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 10px 14px;
    font-size: 0.86rem;
}
.quiz-hint summary {
    cursor: pointer;
    color: var(--fg-2);
    font-weight: 600;
    font-size: 0.82rem;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    font-family: 'JetBrains Mono', monospace;
}
.quiz-hint p {
    margin: 8px 0 0;
    color: var(--fg-2);
}

/* Sidebar: when a lesson is marked done via JS */
.lesson-list li.done-js .state {
    background: var(--good); border-color: var(--good);
}
.lesson-list li.done-js .state::after {
    content: ""; position: absolute;
    left: 3px; top: 1px; width: 4px; height: 7px;
    border: solid white; border-width: 0 1.5px 1.5px 0;
    transform: rotate(45deg);
}

/* Toast for Pyodide loading */
.pm-toast {
    position: fixed;
    bottom: 24px; left: 50%; transform: translateX(-50%);
    background: var(--bg-2);
    border: 1px solid var(--line-2);
    border-radius: 100px;
    padding: 10px 20px;
    font-size: 0.82rem;
    color: var(--fg-2);
    font-family: 'JetBrains Mono', monospace;
    z-index: 50;
    opacity: 0;
    transition: opacity 0.18s;
    pointer-events: none;
}
.pm-toast.show { opacity: 1; }
.pm-toast .pm-cursor { margin-left: 6px; }

/* ─── Rich run output: figures and tables ─────────────────────────────────
   Both live inside .code-out-text, which is a <pre>, so they have to opt out of
   its pre-wrap. The figure keeps a white ground because that is what matplotlib
   draws on -- a transparent PNG would put dark axis labels on the dark theme. */
.code-out-text .code-out-fig,
.code-out-text .code-out-table { white-space: normal; }

.code-out-fig { margin: 10px 0; }
.code-out-fig img {
    display: block; max-width: 100%; height: auto;
    border-radius: var(--radius);
    background: #fff;
    box-shadow: inset 0 0 0 1px var(--line);
}

.code-out-table { margin: 10px 0; overflow-x: auto; }
/* The responsive pass gives every table in .prose `display: block; overflow-x:
   auto` so a wide table scrolls instead of widening the page. An output table is
   already inside its own scrolling wrapper, and that rule was catching it too --
   making the <table> element the scroll container and dropping real table layout
   with it. Hand the job back to the wrapper. */
/* Two class names on purpose. `.prose table` is declared LATER in this file, so
   at equal specificity it wins on order -- and did, which is how the table ended
   up being its own scroll container. Specificity settles it wherever the rules
   happen to sit. */
.code-out .code-out-table table {
    display: table; overflow-x: visible; max-width: none;
    border-collapse: collapse;
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.78rem;
}
.code-out-table th,
.code-out-table td {
    padding: 4px 12px 4px 0; text-align: right; white-space: nowrap;
    border-bottom: 1px solid var(--line);
}
.code-out-table thead th {
    color: var(--fg-3); font-weight: 600;
    border-bottom: 1px solid var(--line-2);
}
/* The index column pandas emits as a row header — left-aligned and dimmed, so
   the eye reads the numbers and not the row labels. */
.code-out-table tbody th { color: var(--fg-3); text-align: left; padding-right: 18px; }
.code-out-table-note { margin: 6px 0 0; font-size: 0.74rem; color: var(--fg-3); }

/* Academy: the tools strip under the tracks grid. */
.tools-lede {
    margin: 6px 0 22px; max-width: 74ch;
    font-size: 0.96rem; line-height: 1.62; color: var(--fg-2);
}
.tools-grid {
    display: grid; gap: 16px;
    grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr));
}
.tool-card {
    display: flex; flex-direction: column; gap: 8px;
    padding: 18px; min-width: 0;
    border: 1px solid var(--line); border-radius: var(--radius);
    background: var(--bg-2); text-decoration: none; color: inherit;
    transition: border-color 0.14s, background 0.14s, transform 0.14s;
}
.tool-card:hover {
    border-color: var(--accent-line); background: var(--bg-3);
    transform: translateY(-1px);
}
.tool-card h3 { margin: 0; font-size: 1.02rem; letter-spacing: -0.01em; }
.tool-card p { margin: 0; font-size: 0.88rem; line-height: 1.58; color: var(--fg-2); }
.tool-go { margin-top: auto; padding-top: 4px; font-size: 0.84rem; color: var(--accent); font-weight: 500; }
.tools-fine {
    margin: 18px 0 0; max-width: 74ch;
    font-size: 0.88rem; line-height: 1.58; color: var(--fg-3);
}
.tools-fine strong { color: var(--fg-2); }

/* Roadmap cards. Shaped like .tool-card on purpose — same grid, same rhythm —
   but they are <div>s, not links, because there is nothing to open yet. The
   hover lift and the accent border are what make a .tool-card read as clickable,
   so neither is inherited here: a card that rises under the cursor and then does
   nothing is a worse promise than a flat one. */
.soon-lede {
    margin: 6px 0 22px; max-width: 74ch;
    font-size: 0.96rem; line-height: 1.62; color: var(--fg-2);
}
.soon-lede em { color: var(--fg); }

/* The essay opens on .hero, which is a two-column grid. This is the second
   column: the compressed argument, for the reader deciding whether to spend
   twelve hundred words on the rest. */
.hero-gist {
    align-self: start;
    padding: 20px 22px;
    border: 1px solid var(--line); border-left: 2px solid var(--accent);
    border-radius: var(--radius);
    background: var(--bg-2);
}
.hero-gist h2 {
    margin: 0 0 10px; font-size: 0.72rem; font-weight: 600;
    letter-spacing: 0.08em; text-transform: uppercase; color: var(--fg-3);
}
.hero-gist p { margin: 0 0 10px; font-size: 0.93rem; line-height: 1.6; color: var(--fg-2); }
.hero-gist p:last-child { margin-bottom: 0; }
.hero-gist strong { color: var(--fg); font-weight: 600; }
.soon-card {
    display: flex; flex-direction: column; gap: 8px;
    padding: 18px; min-width: 0;
    border: 1px dashed var(--line); border-radius: var(--radius);
    background: var(--bg-2);
}
.soon-card h3 { margin: 0; font-size: 1.02rem; letter-spacing: -0.01em; }
/* Shipped roadmap items: a solid border instead of dashed, a green tag, a link. */
.soon-card.is-shipped { border-style: solid; }
.soon-tag {
    align-self: flex-start; font-size: 0.7rem; font-weight: 700; letter-spacing: 0.08em;
    text-transform: uppercase; padding: 2px 8px; border-radius: 999px;
    color: var(--good); background: var(--good-soft);
}
.soon-tag.is-next { color: var(--fg-3); background: var(--bg-3); }
.soon-link { margin-top: auto; font-size: 0.88rem; font-weight: 600; color: var(--accent); }
.soon-card p { margin: 0; font-size: 0.88rem; line-height: 1.58; color: var(--fg-2); }
.soon-card code {
    font-family: 'JetBrains Mono', monospace; font-size: 0.86em;
    color: var(--fg); background: var(--bg-3);
    padding: 1px 5px; border-radius: 4px;
}

/* ─── Notebook ────────────────────────────────────────────────────────────
   Lives in SHARED_CSS rather than its own stylesheet on purpose: the cells reuse
   .code-out-fig and .code-out-table verbatim, and a seventh shared asset would
   mean another entry in the restamp list, the version guard and the publish
   manifest for about 2 KB of rules. */
.nb-head {
    display: flex; justify-content: space-between; align-items: flex-end;
    gap: 20px; flex-wrap: wrap;
    padding: 32px 0 20px; border-bottom: 1px solid var(--line);
}
.nb-head h1 { font-size: clamp(1.7rem, 3.4vw, 2.2rem); margin: 0 0 6px; letter-spacing: -0.025em; }
.nb-lede { margin: 0; color: var(--fg-2); font-size: 0.95rem; max-width: 54ch; line-height: 1.55; }
.nb-toolbar { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.nb-toolbar .nb-btn { padding: 8px 14px; font-size: 0.84rem; }
.nb-meta {
    display: flex; gap: 14px; align-items: center; flex-wrap: wrap;
    padding: 10px 0 0; font-family: 'JetBrains Mono', monospace;
    font-size: 0.76rem; color: var(--fg-3);
}
/* Neutral text while busy: the red cursor is the signal (same as the IDE pill). */
.nb-status.is-busy { color: var(--fg-2); }
.nb-status .pm-cursor { margin-left: 5px; }
.nb-status.is-error { color: var(--err); }

/* The one thing in this strip that is not status text: it goes to another page.
   Sitting inline it read as the last fragment of the sentence, so it keeps the
   arrow the academy's tool cards use -- that arrow is what says "this leaves
   the notebook" -- and names where it goes before you click it. */
.nb-page-link {
    margin-left: auto; display: inline-flex; align-items: center; gap: 6px;
    padding: 5px 12px; border: 1px solid var(--accent-line); border-radius: 999px;
    background: var(--accent-soft); color: var(--accent); text-decoration: none;
    transition: background 0.12s, border-color 0.12s;
}
.nb-page-link:hover { background: var(--bg-3); border-color: var(--accent); }
@media (max-width: 640px) {
    /* Right-aligned, it sat three pixels off the edge and read as an overflow. */
    .nb-page-link { margin-left: 0; width: 100%; justify-content: space-between; }
}

.nb-btn {
    background: transparent; border: 1px solid var(--line-2); color: var(--fg-2);
    border-radius: var(--radius); padding: 6px 10px; cursor: pointer;
    font-family: inherit; font-size: 0.8rem; line-height: 1.2;
    transition: color 0.12s, border-color 0.12s, background 0.12s;
}
.nb-btn:hover { color: var(--fg); border-color: var(--fg-3); }
.nb-btn-run { color: var(--good); border-color: var(--good); font-weight: 600; }
.nb-btn-run:hover { background: var(--good-soft); color: var(--good); }
/* The file input is replaced by its own label so it can look like the rest. */
.nb-file { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }

.nb-cell {
    margin: 18px 0; border: 1px solid var(--line);
    border-radius: var(--radius); background: var(--bg-2);
    overflow: hidden;
}
.nb-cell-bar {
    display: flex; align-items: center; gap: 6px;
    padding: 6px 8px 6px 12px; border-bottom: 1px solid var(--line);
    background: var(--bg-3);
}
.nb-count {
    font-family: 'JetBrains Mono', monospace; font-size: 0.74rem;
    color: var(--fg-4); font-variant-numeric: tabular-nums;
}
.nb-spacer { flex: 1; }
.nb-editor { background: var(--bg-code); }
.nb-editor .cm-editor { font-family: 'JetBrains Mono', monospace; font-size: 0.85rem; }
.nb-editor .cm-editor.cm-focused { outline: none; }
.nb-editor .cm-scroller { font-family: inherit; }
.nb-editor .cm-gutters { background: transparent; border-right: 1px solid var(--line); color: var(--fg-4); }
.nb-editor .cm-activeLine, .nb-editor .cm-activeLineGutter { background: transparent; }

.nb-out {
    margin: 0; padding: 12px 14px;
    font-family: 'JetBrains Mono', monospace; font-size: 0.8rem; line-height: 1.55;
    color: var(--fg-2); background: var(--bg-out);
    white-space: pre-wrap; overflow-wrap: anywhere;
    border-top: 1px solid var(--line);
    max-height: 460px; overflow-y: auto;
}
.nb-out.is-empty { display: none; }
.nb-out.is-busy { color: var(--fg-3); font-style: italic; }
.nb-out.is-error { color: var(--err); }
.nb-out .code-out-fig, .nb-out .code-out-table { white-space: normal; }
.nb-out .code-out-table table { display: table; overflow-x: visible; max-width: none; }

@media (max-width: 660px) {
    .nb-head { padding: 22px 0 16px; }
    .nb-toolbar { width: 100%; }
    .nb-toolbar .nb-btn { flex: 1 1 auto; text-align: center; }
    .nb-cell-bar { flex-wrap: wrap; gap: 8px; }
    /* The per-cell controls are the most-tapped things on the page and came out
       at 29px. These are not text links you brush past -- they run and delete
       your work, so they get a real hit box. */
    .nb-cell-bar .nb-btn { min-height: 38px; padding: 8px 12px; }
    .nb-meta a { display: inline-block; padding: 7px 0; }
}

/* Interactive charts. Plotly sizes itself to its container, so the container is
   what has to be right: full width, a sane default height, and never wider than
   the reading column on a phone. */
.code-out-plotly {
    /* 3D needs height. At 320px in a 1100px column a rotatable scene renders as
       a squashed letterbox with the axis labels piled on top of each other. */
    width: 100%; min-height: 440px; margin: 10px 0;
    white-space: normal;
}
.code-out-plotly .js-plotly-plot,
.code-out-plotly .plot-container { width: 100% !important; }
/* Plotly's toolbar is white-on-white against a dark panel until it is told. */
.code-out-plotly .modebar {
    background: transparent !important;
}
.code-out-plotly .modebar-btn path { fill: var(--fg-3) !important; }
.code-out-plotly .modebar-btn:hover path { fill: var(--fg) !important; }
@media (max-width: 480px) {
    .code-out-plotly { min-height: 300px; }
}

/* Dataset picker inside the notebook. The /datasets/ page is the reference and
   the SEO landing; this is where you actually pick, because leaving the notebook
   to read a page and coming back to type a load line from memory is three steps
   too many. */
.nb-btn-data.is-open { color: var(--accent); border-color: var(--accent); }
.nb-data-panel {
    margin: 14px 0 4px; padding: 16px;
    border: 1px solid var(--line); border-radius: var(--radius);
    background: var(--bg-2);
}
.nb-data-intro { margin: 0 0 12px; font-size: 0.86rem; color: var(--fg-2); line-height: 1.5; }
.nb-data-grid {
    display: grid; gap: 10px;
    grid-template-columns: repeat(auto-fit, minmax(min(230px, 100%), 1fr));
}
.nb-data-card {
    display: flex; flex-direction: column; gap: 4px; min-width: 0;
    padding: 12px 13px; text-align: left; cursor: pointer;
    border: 1px solid var(--line); border-radius: var(--radius);
    background: var(--bg-3); color: inherit; font-family: inherit;
    transition: border-color 0.12s, background 0.12s;
}
.nb-data-card:hover { border-color: var(--accent-line); background: var(--bg-2); }
.nb-data-card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.nb-data-name { font-weight: 600; font-size: 0.92rem; color: var(--fg); }
.nb-data-flag {
    display: inline-block; margin-left: 7px; padding: 0 6px;
    border-radius: 100px; font-size: 0.62rem; font-weight: 600;
    text-transform: uppercase; letter-spacing: 0.06em;
    color: var(--warn); background: var(--warn-soft); border: 1px solid var(--warn);
    vertical-align: 1px;
}
.nb-data-shape {
    font-family: 'JetBrains Mono', monospace; font-size: 0.7rem; color: var(--fg-3);
}
.nb-data-blurb { font-size: 0.8rem; line-height: 1.5; color: var(--fg-2); padding-bottom: 6px; }
.nb-data-code {
    /* auto pushes the chip to the bottom so it lines up across a row of cards
       whose descriptions are different lengths. */
    margin-top: auto; padding: 4px 7px; align-self: flex-start;
    font-family: 'JetBrains Mono', monospace; font-size: 0.72rem;
    color: var(--accent); background: var(--bg-code);
    border: 1px solid var(--line); border-radius: 4px;
    overflow-wrap: anywhere;
}
.nb-data-more { margin: 12px 0 0; font-size: 0.82rem; }
.nb-data-more a { color: var(--accent); text-decoration: none; }
.nb-data-more a:hover { text-decoration: underline; text-underline-offset: 3px; }
@media (max-width: 480px) {
    .nb-data-panel { padding: 13px; }
    .nb-data-card { padding: 11px; }
}

/* ─── Practice datasets ───────────────────────────────────────────────────
   auto-fit with min() for the same reason the track cards use it: a bare
   minmax(300px, 1fr) sets a 300px page minimum on a 280px foldable. */
.ds-head { padding: 32px 0 8px; }
.ds-head h1 { font-size: clamp(1.7rem, 3.4vw, 2.2rem); margin: 0 0 12px; letter-spacing: -0.025em; }
.ds-lede { color: var(--fg-2); font-size: 0.98rem; line-height: 1.62; max-width: 68ch; margin: 0 0 12px; }

.ds-load {
    margin: 12px 0 0; padding: 12px 14px;
    background: var(--bg-code); border: 1px solid var(--line);
    border-radius: var(--radius);
    font-family: 'JetBrains Mono', monospace; font-size: 0.82rem; line-height: 1.6;
    color: var(--fg); overflow-x: auto;
}
.ds-load code { background: none; padding: 0; color: inherit; font-size: inherit; }
.ds-load-big { margin-top: 18px; }

.ds-grid {
    display: grid; gap: 18px;
    grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr));
    padding: 26px 0 10px;
}
.ds-card {
    border: 1px solid var(--line); border-radius: var(--radius);
    background: var(--bg-2); padding: 18px 18px 16px;
    display: flex; flex-direction: column; min-width: 0;
}
.ds-card-h { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.ds-card-h h3 { margin: 0 0 2px; font-size: 1.04rem; letter-spacing: -0.01em; }
.ds-shape {
    font-family: 'JetBrains Mono', monospace; font-size: 0.72rem;
    color: var(--fg-3); white-space: nowrap;
}
.ds-flag {
    display: inline-block; margin-left: 8px; padding: 1px 8px;
    border-radius: 100px; font-size: 0.66rem; font-weight: 600;
    text-transform: uppercase; letter-spacing: 0.06em;
    color: var(--warn); background: var(--warn-soft); border: 1px solid var(--warn);
    vertical-align: 2px;
}
.ds-blurb { margin: 8px 0 0; font-size: 0.9rem; line-height: 1.58; color: var(--fg-2); }
.ds-teaches { margin: 8px 0 0; font-size: 0.86rem; line-height: 1.55; color: var(--fg-3); }
.ds-teaches strong { color: var(--fg-2); }
.ds-cols { display: flex; flex-wrap: wrap; gap: 5px; margin: 12px 0 0; }
.ds-cols code {
    font-size: 0.7rem; padding: 2px 7px; border-radius: 4px;
    background: var(--bg-3); color: var(--fg-3); border: 1px solid var(--line);
}
.ds-card .ds-load { margin-top: auto; }
.ds-links { margin: 12px 0 0; font-size: 0.82rem; }
.ds-links a { color: var(--accent); text-decoration: none; }
.ds-links a:hover { text-decoration: underline; text-underline-offset: 3px; }
.ds-links .sep { padding: 0 6px; opacity: 0.5; }

.ds-note { margin: 26px 0 8px; padding-top: 22px; border-top: 1px solid var(--line); }
.ds-note h2 { font-size: 1.1rem; margin: 0 0 8px; }
.ds-note p { color: var(--fg-2); font-size: 0.92rem; line-height: 1.6; margin: 0 0 8px; max-width: 68ch; }
.ds-fine { color: var(--fg-3) !important; font-size: 0.84rem !important; margin-top: 14px !important; }

@media (max-width: 480px) {
    .ds-head { padding: 22px 0 4px; }
    .ds-card { padding: 15px 14px; }
}

/* Theme FAB styles live in /assets/theme-fab.css — single source of truth
   shared by academy, lessons, 404, the live IDE, and Tutorial Lab. */


/* ═══ Responsive: phones, foldables, tablets ══════════════════════════════
 *
 * The header was the load-bearing bug, and it did not present as one.
 * `.top` is a flex row of `.brand` (172px) and `.nav` (249px) with no wrap and
 * no shrink, so every page carried a hard 421px minimum width. Chrome on
 * Android does not give you a horizontal scrollbar when a page overflows like
 * that — it widens the layout viewport to fit the content and zooms the whole
 * document out. Measured, not assumed: `innerWidth` read 421 at *every* device
 * width from 280 to 412. So the site never scrolled sideways; it rendered
 * shrunk — 7% small on an iPhone 13, 34% small on a folded Galaxy Fold — and
 * every font size, tap target and line length shrank with it.
 *
 * Rules below are ordered widest-first so each band only states what changes.
 * Nothing here uses a `min-width` that could re-create the same trap.
 */

/* Long identifiers are the other way a page acquires a minimum width:
   `ConnectionRefusedError` or a dotted module path is one unbreakable word.
   Break inside a word only when the word genuinely cannot fit. */
h1, h2, h3, h4, .prose, .lesson-body, .hero .lede, .side-title, .err-when-link {
    overflow-wrap: break-word;
}
.prose code, .lesson-body code, .side code { overflow-wrap: break-word; }
img, svg, video, iframe { max-width: 100%; }
/* Any table that ever lands in a lesson scrolls in its own box rather than
   widening the page. */
.prose table, .lesson-body table { display: block; overflow-x: auto; max-width: 100%; }

/* Lesson tables (md() wraps each in .prose-table). The wrapper scrolls, so the
   table keeps real table layout -- the rule above would make the <table> itself
   the scroll box. Two classes outrank it wherever it sits. */
.prose-table {
    margin: 18px 0 24px;
    overflow-x: auto;
    border: 1px solid var(--line);
    border-radius: var(--radius);
}
.prose .prose-table table, .lesson-body .prose-table table {
    display: table; width: 100%; max-width: none; overflow: visible;
    border-collapse: collapse;
    font-size: 0.92rem; line-height: 1.5;
}
.prose-table th, .prose-table td {
    padding: 10px 14px;
    text-align: left; vertical-align: top;
    border-bottom: 1px solid var(--line);
}
.prose-table thead th {
    background: var(--bg-2);
    color: var(--fg); font-weight: 600;
    border-bottom: 1px solid var(--line-2);
    white-space: nowrap;
}
.prose-table tbody tr:last-child td { border-bottom: 0; }
/* The first column is usually the thing being described -- a tag, a method, a
   pattern -- so it stays on one line. */
.prose-table td:first-child code { white-space: nowrap; }
.prose-table .nw { white-space: nowrap; }
.prose-table .al-center { text-align: center; }
.prose-table .al-right { text-align: right; }
/* On a phone, columns keep a readable width and the box scrolls sideways
   rather than crushing three columns into 300px. */
@media (max-width: 640px) {
    .prose .prose-table table, .lesson-body .prose-table table { min-width: 520px; font-size: 0.88rem; }
    .prose-table th, .prose-table td { padding: 8px 10px; }
}
/* /embed-python/: the live example box, the screenshots, the copy-paste snippets. */
.guide-fig { margin: 20px 0 24px; }
.guide-fig img { display: block; width: 100%; height: auto; border: 1px solid var(--line); border-radius: 10px; }
.guide-fig.narrow img { max-width: 320px; }
.guide-fig figcaption { margin-top: 8px; font-size: 0.88rem; color: var(--fg-2); }
.guide-files { font-size: 0.95rem; }
.guide-embed { display: block; border: 1px solid var(--line); border-radius: 10px; background: var(--bg-2); }
.guide-snip {
    background: var(--bg-2); border: 1px solid var(--line); border-radius: 8px;
    padding: 12px 14px; overflow-x: auto; font-size: 0.85rem; line-height: 1.5;
}
.guide-snip code { background: none; padding: 0; white-space: pre; }
/* A #section link lands the heading with some air above it, not flush on the edge. */
h2[id], h3[id] { scroll-margin-top: 20px; }

/* Tablets — only the gutter needs to give. */
@media (max-width: 1024px) {
    .shell { padding: 0 20px; }
}

/* THE HEADER BREAK. Below this, brand and nav cannot share a row at any
   readable size, so the nav drops to its own full-width row underneath.
   Set at 660 rather than at the 421 where it actually collides, so the row
   breaks while it is still comfortable instead of at the moment it fails. */
@media (max-width: 660px) {
    .shell { padding: 0 18px; }
    .top { flex-wrap: wrap; gap: 10px 18px; padding: 14px 0 10px; }
    /* .nav sizing is in site-chrome.css so the IDE landing gets it too. */
    .brand { padding: 6px 0; }
    .crumb { margin-top: 16px; row-gap: 2px; }
    /* Breadcrumb links are navigation, not prose — give them a real hit box.
       Kept at 29px rather than 44: a full-height breadcrumb on a phone reads
       as a second nav bar, which is not what a trail is for. */
    .crumb a { display: inline-block; padding: 6px 0; }
}

/* Phones. */
@media (max-width: 480px) {
    .shell { padding: 0 16px; }
    .page { padding: 24px 0 48px; }
    h1.lesson-title { font-size: clamp(1.5rem, 7.5vw, 1.9rem); }
    .head-row { row-gap: 6px; }
    section.lesson-sec { margin: 32px 0; }
    section.lesson-sec > .sec-h { flex-wrap: wrap; gap: 4px 12px; }
    /* Code never reflows — it scrolls. Trim the chrome instead so more of the
       line is visible inside the same gutter. */
    .code-b { font-size: 0.79rem; padding: 12px; }
    .btn-code { padding: 8px 12px; font-size: 0.76rem; }   /* 26px → 36px tall */
    .prose details > summary { padding: 8px 0; }
}

/* Folded foldables. The Galaxy Fold's outer screen is 280 CSS px — the
   narrowest display shipping on a phone. Nothing below this is a real device. */
@media (max-width: 360px) {
    .shell { padding: 0 12px; }
    .brand { font-size: 0.9rem; }
    .brand .mark { width: 20px; height: 20px; }
}
