/* woff dropped (SKR-398): woff2 alone covers every browser this site
   supports, so the fallback format was dead weight on every request. */
@font-face {
    font-family: 'Source Serif 4';
    src: url("../fonts/SourceSerif4Variable-Roman.otf-wY2rN0j.woff2") format('woff2');
    font-weight: 200 900;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: 'Source Serif 4';
    src: url("../fonts/SourceSerif4Variable-Italic.otf-TjB8U-W.woff2") format('woff2');
    font-weight: 200 900;
    font-style: italic;
    font-display: swap;
}

@font-face {
    font-family: 'Source Sans 3';
    src: url("../fonts/SourceSans3VF-Upright.otf-QG1Nnrn.woff2") format('woff2');
    font-weight: 200 900;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: 'Source Sans 3';
    src: url("../fonts/SourceSans3VF-Italic.otf-CjkGm57.woff2") format('woff2');
    font-weight: 200 900;
    font-style: italic;
    font-display: swap;
}

@font-face {
    font-family: 'Berkeley Mono';
    src: url("../fonts/BerkeleyMono-Regular-bRSmJcE.woff2") format('woff2');
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: 'Berkeley Mono';
    src: url("../fonts/BerkeleyMono-Bold-8xf8vgx.woff2") format('woff2');
    font-weight: 700;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: 'Berkeley Mono';
    src: url("../fonts/BerkeleyMono-Oblique-QMd6PVe.woff2") format('woff2');
    font-weight: 400;
    font-style: oblique;
    font-display: swap;
}

@font-face {
    font-family: 'Berkeley Mono';
    src: url("../fonts/BerkeleyMono-Bold-Oblique-6MffFIU.woff2") format('woff2');
    font-weight: 700;
    font-style: oblique;
    font-display: swap;
}

* {
    box-sizing: border-box;
    max-inline-size: var(--measure);
}

/* Links are part of their surrounding editorial voice by default. Individual
   content treatments opt into an accent color where that distinction carries
   meaning, rather than inheriting the browser's blue/purple fallback. */
a { color: inherit; }

:root {
    font-family: var(--font-serif);
    /* 112.5% of the UA default 16px is 18px — pixel-identical to the
       flat 18px this used to read, but expressed as a multiple of the
       user's own font-size preference rather than overriding it
       (SKR-400). Zero visual change on a default browser; a reader who
       has bumped their base size up or down now actually gets that. */
    font-size: 112.5%;
    font-weight: 400;
    font-optical-sizing: auto;
    font-variant-numeric: oldstyle-nums;
    font-variant-ligatures: common-ligatures;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    line-height: 1.5;

    --font-serif: 'Source Serif 4', Georgia, serif;
    --font-sans: 'Source Sans 3', system-ui, sans-serif;
    --font-mono: 'Berkeley Mono', ui-monospace, 'SF Mono', 'Cascadia Mono', Menlo, monospace;
    --ratio: 1.5;
    --s-5: calc(var(--s-4) / var(--ratio));
    --s-4: calc(var(--s-3) / var(--ratio));
    --s-3: calc(var(--s-2) / var(--ratio));
    --s-2: calc(var(--s-1) / var(--ratio));
    --s-1: calc(var(--s0) / var(--ratio));
    --s0: 1rem;
    --s1: calc(var(--s0) * var(--ratio));
    --s2: calc(var(--s1) * var(--ratio));
    --s3: calc(var(--s2) * var(--ratio));
    --s4: calc(var(--s3) * var(--ratio));
    --s5: calc(var(--s4) * var(--ratio));

    /* Type scale — ratio 1.2, anchored on the 18px body. Deliberately
       NOT --s*: those are the spacing scale (76% of their uses are
       gaps, padding and margins, not text) and stay at ratio 1.5,
       untouched by this.

       There is exactly ONE small size (SKR-404). Three independent
       design agents working from real listing content each picked a
       ratio in 1.125–1.2 and a floor of 14–16px for the same content
       this register carries — dates, durations, counts, dense table
       rows — and none went near the 12.5px (let alone 8px) this file
       used to. Eyebrows and metadata are distinguished from body by
       face, weight, case and tracking — NOT by being smaller as well.
       Stacking size on top of caps/tracking/weight/--ink-mid was four
       de-emphasis signals on one run of text, and it's what produced
       the old 8px register in the first place. No type REGISTER on
       this site goes below --type-small; `code`/`pre` are sized at
       0.82em relative to their context on purpose (documented in
       design-system.md) and are not a register of their own. */
    --type-body: 1rem;
    --type-small: 0.8333rem;
    --measure: 68ch;

    color-scheme: light dark;

    .light {
        color-scheme: light;
    }

    .dark {
        color-scheme: dark;
    }

    /* Mineral Signals is the shared, cool-paper environment. Keep the
       established aliases below: all current templates and components use
       them, while later publication layers can use the semantic tokens. */
    --canvas: light-dark(#f2f4f1, #111714);
    --reading: light-dark(#fafbf8, #171e1a);
    --ink: light-dark(#1c2421, #e9eee9);
    --muted: light-dark(#56605b, #abb7af);
    --rule: light-dark(#c6cec8, #39443d);
    --border: light-dark(#737e77, #75837a);
    --focus: light-dark(#005ea8, #8dbbff);

    /* Publication identity stays separate from status and focus colors. */
    --greenhouse-accent-light: #923d28;
    --greenhouse-accent-dark: #ff9b78;
    --greenhouse-art-light: #d66243;
    --greenhouse-art-dark: #e86e4c;
    --greenhouse-soft-light: #f0dbd3;
    --greenhouse-soft-dark: #3a221c;
    --garden-accent-light: #5c6500;
    --garden-accent-dark: #d7e85b;
    --garden-art-light: #c7d62f;
    --garden-art-dark: #c2d333;
    --garden-soft-light: #ecefc9;
    --garden-soft-dark: #2c3015;
    --nuance-accent-light: #4d50a1;
    --nuance-accent-dark: #bbc0ff;
    --nuance-art-light: #7477da;
    --nuance-art-dark: #8589e8;
    --nuance-soft-light: #e2e3f5;
    --nuance-soft-dark: #242641;

    --accent-light: var(--greenhouse-accent-light);
    --accent-dark: var(--greenhouse-accent-dark);
    --art-ink-light: var(--greenhouse-art-light);
    --art-ink-dark: var(--greenhouse-art-dark);
    --accent-wash-light: var(--greenhouse-soft-light);
    --accent-wash-dark: var(--greenhouse-soft-dark);
    --accent: light-dark(var(--accent-light), var(--accent-dark));
    --art-ink: light-dark(var(--art-ink-light), var(--art-ink-dark));
    --accent-wash: light-dark(var(--accent-wash-light), var(--accent-wash-dark));
    --panel: var(--accent-wash);

    --color: var(--ink);
    --background-color: var(--canvas);
    --color-inverse: var(--canvas);
    --background-color-inverse: var(--ink);

    /* NOT defining --border-thin here, deliberately. `.box` below
       references it and it is defined nowhere, so `.box`'s border is
       invalid at computed-value time and drops out — the episode
       hero, the show notes and the listen links have all been
       rendering borderless. Defining it would switch a 2px border on
       across 33 templates at once, which is a site-wide look change,
       not a bug fix. Tracked on SKR-143 as a decision. The episode
       page sets its own concrete widths and does not depend on it. */

    /* The cool-paper tints distinguish inline code, current rows, and
       callouts without adding another interface palette. */
    --tint: light-dark(#e3e6e3, #1d2320);
    --tint-strong: light-dark(#c6c9c6, #323834);

    /* The approved muted register carries secondary text and quantity
       marks. They remain distinct aliases because their roles diverge. */
    --ink-mid: var(--muted);

    /* Same value, different job. --ink-mid is a TEXT register; this
       draws quantities — a progress fill, a duration meter. They are
       one colour today and should not be one token, because the first
       time a quantity mark needs to change, doing it through
       --ink-mid would drag twenty pieces of secondary text with it. */
    --ink-fill: var(--muted);

    color: var(--color);
    background-color: var(--background-color);

}

html,
body,
div,
header,
hgroup,
nav,
main,
footer {
    max-inline-size: none;
}

body {
    margin: 0;
}

/* There is no global :focus-visible rule anywhere else in this file.
   Several components define their own hover + focus-visible pairs, so
   keyboard focus is visible in places by accident; the episode page's
   chapter rows depend on this rule existing rather than defining their
   own outline. The stable 3px Mineral Signals focus token keeps the
   smallest controls clear for keyboard users. */
:where(a, button, summary, input, select, textarea, [tabindex]):focus-visible {
    outline: 3px solid var(--focus);
    outline-offset: 4px;
}

.skip-link {
    background: var(--reading);
    block-size: 1px;
    clip-path: inset(50%);
    color: var(--color);
    inline-size: 1px;
    inset-block-start: 0.75rem;
    inset-inline-start: 0.75rem;
    overflow: hidden;
    padding: 0.75rem 1rem;
    position: fixed;
    white-space: nowrap;
    z-index: 100;
}

.skip-link:focus-visible {
    block-size: auto;
    clip-path: none;
    inline-size: auto;
    overflow: visible;
}

/* The site-wide visually-hidden class. The episode page's facts row
   needs one: its <dt>s carry real structure for a screen reader but
   no visible text of their own. The login/register forms in
   templates/security/login.html.twig depend on it staying
   autofill-visible too — their carried-email inputs must render as
   a real, readable field for password managers, so this can never
   become display:none or visibility:hidden, both of which autofill
   ignores same as it ignores type="hidden". */
.visually-hidden {
    block-size: 1px;
    clip-path: inset(50%);
    inline-size: 1px;
    overflow: hidden;
    position: absolute;
    white-space: nowrap;
}

header {
    display: flex;
    flex-direction: column;
    align-items: center;
}

code {
    font-family: var(--font-mono);
    font-size: 0.82em;
}

:not(pre)>code {
    padding: 0.12em 0.35em;
    border-radius: 3px;
}

pre {
    font-family: var(--font-mono);
    font-size: 0.82em;
    line-height: 1.55;
    padding: 1.2em 1.5em;
    margin-block: 1.4em;
    overflow-x: auto;
}

pre code {
    font-size: inherit;
    padding: 0;
}

blockquote {
    border-inline-start: 2px solid;
    padding-inline-start: 1.2em;
    margin-block: 1.4em;
    margin-inline: 0;
    font-style: italic;

    p:last-child {
        margin-block-end: 0;
    }
}

input[type=email] {
    font-family: inherit;
    font-size: inherit;
    padding: var(--s-2);
    border: var(--s-5) solid;
}

input[type=submit] {
    appearance: none;

    font-family: inherit;
    font-size: inherit;
    font-weight: 600;
    padding: var(--s-1);
    width: 100%;
    border: none;
    background-color: rgba(0, 0, 0, 0.5);
    color: rgb(240, 240, 240);
}

input[type=submit]:hover {
    background-color: rgba(0, 0, 0, 0.75);
}

hr {
    border-bottom: 0.0125em solid;
    /* width: 70ch; */
    margin-inline-start: 0px;
}

.center {
    box-sizing: content-box;
    margin-inline: auto;
    max-inline-size: calc(var(--measure) * 1.5);
}

.press-page {
    --measure: 82ch;
    max-inline-size: 112ch;
}

.cover {
    display: flex;
    flex-direction: column;
    min-block-size: 85vh;
    padding: 1rem;
}

.cover>* {
    margin-block: 1rem;
}

.cover> :first-child:not(div) {
    margin-block-start: 0;
}

.cover> :last-child:not(div) {
    margin-block-end: 0;
}

.cover>div {
    margin-block: auto;
}

.cover.publication>div {
    margin-block-start: 0;
    margin-block-end: auto;
}

.box {
    padding: var(--padding, var(--s1));
    border: var(--border-thin) solid;
}

.box * {
    color: inherit;
}

.box.invert {
    color: var(--color-inverse);
    background-color: var(--background-color-inverse);
}

.switcher {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s1);
}

.switcher>* {
    flex-grow: 1;
    flex-basis: calc((40rem - 100%) * 999);
}

.switcher> :nth-last-child(n + 5),
.switcher> :nth-last-child(n + 5)~* {
    flex-basis: 100%;
}

.stack {
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
}

.stack>* {
    margin-block: 0;
}

.stack>*+* {
    margin-block-start: var(--space, 1.5rem);
}

.stack.page-header {
    flex-direction: row;
}

.stack.publication:only-child {
    block-size: 100%;
}

.stack.publication> :nth-last-child(2) {
    margin-block-end: auto;
}

.page-header {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    margin-block-end: 0.25em;
    border-bottom: 0.15em solid;
    margin-inline: 1em;
}

.page-header h2 {
    margin-inline-end: auto;
    margin-block-end: 0;
    align-self: end;
    padding-inline-end: 0.5rem;
    font-size: clamp(1rem, 1vw + 1rem, 3rem);
}

.page-header p {
    font-size: 1.25em;
    font-weight: bold;
    margin-block: 0;
    align-self: end;
}

.page-header a,
.company-tagline a {
    text-decoration: none;
    color: inherit;
}

.page-header a:visited,
.company-tagline a:visited {
    color: inherit;
}

.site-nav,
.section-nav {
    max-inline-size: none;
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    margin-block: 0em;
    padding-inline-start: 0em;
    margin-inline: 1em;
    list-style: none;
    white-space: nowrap;

    >a {
        text-decoration: none;
        color: inherit;
    }

    >form {
        margin: 0;
    }

    >form button {
        background: transparent;
        border: 0;
        color: inherit;
        cursor: pointer;
        font: inherit;
        padding: 0;
    }

    *+* {
        padding-inline-start: 1ch;
    }
}

.site-nav {
    .subscribe-link {
        margin-inline-start: auto;
    }

    .pub-nav {
        position: relative;

        button {
            anchor-name: --publication-navigation-trigger;
            color: inherit;
            border: none;
            background-color: inherit;
            font-family: inherit;
            font-size: inherit;
            padding: 0;
        }

        [popover] {
            position: absolute;
            top: 100%;
            left: 0;
            margin: 0;
            inset: unset;
            position-anchor: --publication-navigation-trigger;
            top: anchor(bottom);
            left: anchor(left);
            border-inline: none;
            padding: 0;
            max-width: calc(100vw - 2em);

            .nav-links {
                display: flex;
                flex-wrap: wrap;
                justify-content: space-around;

                a {
                    display: inline-block;
                    padding-block: 0.90em;
                    text-decoration: none;
                    color: inherit;

                    &.current-publication,
                    &.current-podcast {
                        color: var(--color-inverse);
                        background-color: var(--background-color-inverse);
                    }

                    &:first-of-type {
                        padding-inline-end: 0.5ch;
                    }

                    &:first-of-type.current-publication,
                    &:first-of-type.current-podcast {
                        padding-inline: 0.5ch;
                    }
                }

                *+* {
                    padding-inline: 0.5ch;
                }

                >* {
                    flex-grow: 1;
                    flex-basis: calc((55ch - 100vw) * 999);
                }
            }
        }
    }
}

.section-nav {
    border-block-start: 0.08em solid;

    >a {
        display: inline-block;
        padding-block: var(--s-2);
    }

    a[aria-current='page'] {
        font-weight: 700;
    }
}

.site-flashes {
    display: grid;
    gap: var(--s-2);
    justify-items: center;
    margin: var(--s0) 1em 0;
}

.site-flash {
    border: 0.12em solid;
    font-family: var(--font-sans);
    font-size: var(--type-small);
    inline-size: min(100%, var(--measure));
    margin: 0;
    padding: var(--s-2);
    text-align: center;
}

.site-flash--success {
    background: var(--background-color-inverse);
    color: var(--color-inverse);
}

.page-footer {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: start;
    justify-content: space-around;
    margin-block-start: 0.25em;
    border-block-start: 0.15em solid;
    margin-block-end: 0.5em;
    margin-inline: 1em;

    section {
        display: flex;
        flex-direction: column;
        align-items: center;

        p {
            font-size: 0.85em;
            font-weight: bold;
            margin-block-end: 0;
            margin-block-start: 0.25em;

            a {
                text-decoration: none;
                color: inherit;
            }
        }

        &:first-of-type {
            /* align-items: flex-start; */
            align-content: start;
        }

        &:last-of-type {
            /* align-items: end; */
        }
    }
}

/* Shared Press shell. Existing page selectors retain their ownership; this
   layer supplies the common band, directory, local row, and footer contract. */
.shell.center {
    inline-size: min(1380px, calc(100% - 32px));
    max-inline-size: none;
}

.press-head {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
    align-items: end;
    column-gap: 16px;
    border-bottom: 1px solid var(--rule);
    min-block-size: 70px;
    padding-block: 16px 10.4px;
    font-family: var(--font-sans);
    font-size: 13.44px;
}

.press-head .house {
    align-items: center;
    color: var(--ink);
    display: inline-flex;
    font: 700 18px/1 var(--font-serif);
    letter-spacing: -0.02em;
    min-block-size: 24px;
    text-decoration: none;
}

.press-head .page-header {
    align-items: center;
    border: 0;
    display: block;
    margin: 0;
    text-align: center;
}

.press-head .page-header h2 {
    align-self: auto;
    font-family: var(--font-serif);
    font-size: 20.8px;
    font-weight: 700;
    letter-spacing: -0.035em;
    line-height: 1.05;
    margin: 0;
    padding: 0;
}

.press-head .page-header h2 a {
    align-items: center;
    display: inline-flex;
    min-block-size: 24px;
}

.press-head .page-header p {
    color: var(--muted);
    font-family: var(--font-sans);
    font-size: 10.56px;
    font-weight: 400;
    letter-spacing: 0.11em;
    margin: 2.72px 0 0;
    text-transform: uppercase;
    white-space: nowrap;
}

.press-head .site-nav {
    align-items: center;
    display: flex;
    gap: 1rem;
    justify-self: end;
    margin: 0;
    padding: 0;
}

.press-head .site-nav > * + *,
.press-head .site-nav > form,
.press-head .site-nav > form button {
    padding: 0;
}

.press-head .site-nav > a,
.explore summary,
.explore__heading button {
    align-items: center;
    color: inherit;
    display: inline-flex;
    font: 650 13.44px/1.2 var(--font-sans);
    min-block-size: 24px;
    text-decoration: none;
    white-space: nowrap;
}

.explore {
    position: relative;
}

.explore summary {
    cursor: pointer;
    list-style: none;
}

.explore summary::-webkit-details-marker {
    display: none;
}

.explore__sheet {
    background: var(--reading);
    border: 1px solid var(--rule);
    box-shadow: 0 17.6px 38.4px rgb(0 0 0 / 20%);
    inset: auto 0 auto auto;
    max-block-size: min(608px, calc(100vh - 80px));
    overflow: auto;
    padding: 16px;
    position: absolute;
    top: calc(100% + 12px);
    width: min(752px, calc(100vw - 32px));
    z-index: 5;
}

.explore__heading {
    align-items: center;
    border-bottom: 1px solid var(--rule);
    display: flex;
    justify-content: space-between;
    padding-bottom: 10.4px;
}

.explore__heading p,
.explore h2 {
    color: var(--muted);
    font: 700 10.72px/1.2 var(--font-sans);
    letter-spacing: 0.1em;
    margin: 0;
    text-transform: uppercase;
}

.explore__heading button {
    background: transparent;
    border: 0;
    cursor: pointer;
    display: none;
}

.explore.is-enhanced .explore__heading button {
    display: inline-flex;
}

.explore__groups {
    display: grid;
    gap: 24px;
    grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
    inline-size: 100%;
    max-inline-size: none;
    padding-top: 12.8px;
}

.explore__groups section {
    inline-size: auto;
    margin: 0;
    max-inline-size: none;
    min-inline-size: 0;
    padding: 0;
}

.explore__groups h2 {
    margin-bottom: 6.4px;
}

.explore__groups a {
    border-top: 1px solid color-mix(in srgb, var(--rule) 66%, transparent);
    color: inherit;
    display: grid;
    gap: 9.6px;
    grid-template-columns: minmax(0, 1fr) auto;
    max-inline-size: none;
    padding-block: 6.4px;
    text-decoration: none;
}

.explore__groups a[aria-current='location'],
.press-footer a[aria-current='location'] {
    color: var(--accent);
    font-weight: 700;
}

.explore__groups strong {
    font: inherit;
    font-weight: 700;
    line-height: 1.15;
    max-inline-size: none;
}

.explore__groups span {
    align-self: end;
    color: var(--muted);
    font: 10.88px/1.15 var(--font-sans);
    max-inline-size: none;
    text-align: end;
}

.section-nav.local {
    border-bottom: 1px solid var(--rule);
    border-top: 0;
    display: flex;
    gap: 1rem;
    margin: 0;
    overflow-x: auto;
    padding: 8px 0;
    white-space: nowrap;
}

.section-nav.local > a {
    align-items: center;
    color: inherit;
    display: inline-flex;
    font: 650 11.84px/1.2 var(--font-sans);
    min-block-size: 24px;
    padding: 0;
    text-decoration: none;
}

.section-nav.local a[aria-current='page'] {
    color: var(--accent);
}

.press-footer.page-footer {
    align-items: start;
    border-top: 1px solid var(--rule);
    display: grid;
    gap: 19.2px;
    grid-template-columns: 1.25fr repeat(3, minmax(0, 1fr));
    inline-size: min(1216px, 100%);
    justify-content: initial;
    margin: clamp(56px, 8vw, 128px) auto 0;
    max-inline-size: none;
    padding: 17.6px 0 40px;
}

.press-footer .footer-links,
.press-footer nav {
    align-items: start;
    display: flex;
    flex-direction: column;
    gap: 4.8px;
}

.press-footer .footer-links p {
    color: var(--muted);
    font: 12.8px/1.3 var(--font-sans);
    margin: 5.6px 0;
    max-inline-size: 20ch;
}

.press-footer .footer-links p + p {
    margin: 0;
}

.press-footer .footer-links .footer-press {
    color: var(--ink);
    font: 700 17.6px/1.2 var(--font-serif);
    letter-spacing: -0.025em;
    text-decoration: none;
}

.press-footer h2 {
    color: var(--muted);
    font: 700 10.72px/normal var(--font-sans);
    letter-spacing: 0.1em;
    margin: 0 0 6.4px;
    text-transform: uppercase;
}

.press-footer a {
    align-items: center;
    color: var(--muted);
    display: inline-flex;
    font: 12.8px/1.3 var(--font-sans);
    min-block-size: 24px;
}

.press-footer a:hover {
    color: var(--ink);
}

.press-footer .footer-copyright {
    border-top: 1px solid var(--rule);
    color: var(--muted);
    font: 11.52px/1.3 var(--font-sans);
    grid-column: 1 / -1;
    inline-size: 100%;
    margin: 4.8px 0 0;
    max-inline-size: none;
    padding-top: 12.8px;
}

@media (max-width: 720px) {
    .shell.center {
        inline-size: min(100% - 20px, 1380px);
    }

    .press-head {
        align-items: center;
        grid-template-columns: 1fr auto;
        font-size: 11.2px;
        padding-block: 11.2px 8.8px;
        /* The source masthead keeps its desktop 16px grid gap through the
           standard narrow breakpoint; only the dedicated <=370px rule
           condenses it. */
        row-gap: 16px;
    }

    .press-head .page-header {
        grid-column: 1 / -1;
        grid-row: 2;
        padding-top: 0.25rem;
    }

    .press-head .page-header h2 {
        font-size: 17.92px;
    }

    .press-head .publication--press-root {
        grid-column: 1 / -1;
        grid-row: 2;
        padding-top: 4px;
    }

    .press-head .site-nav {
        gap: 0.65rem;
    }

    .explore__sheet {
        inset: 1rem;
        max-block-size: calc(100vh - 2rem);
        padding: 0.9rem;
        position: fixed;
        width: auto;
    }

    .explore__groups {
        gap: 1rem;
        grid-template-columns: 1fr;
    }

    .explore__groups a {
        gap: 0.12rem;
        grid-template-columns: 1fr;
    }

    .explore__groups span {
        text-align: start;
    }

    .press-footer.page-footer {
        gap: 24px 12.8px;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        inline-size: auto;
        margin: 56px 16px 0;
    }

    .press-footer .footer-links,
    .press-footer nav:last-of-type {
        grid-column: 1 / -1;
    }
}

@media (max-width: 370px) {
    .press-head {
        grid-template-columns: 1fr;
        row-gap: 8px;
    }

    .press-head .house {
        order: 1;
    }

    .press-head .house-spacer {
        display: none;
    }

    .press-head .site-nav {
        flex-wrap: wrap;
        justify-self: start;
        order: 2;
    }

    .press-head .publication,
    .press-head .page-header {
        grid-column: 1 / -1;
        grid-row: auto;
        order: 3;
    }

    .press-footer.page-footer {
        grid-template-columns: 1fr;
    }

    .press-footer nav:last-of-type {
        grid-column: auto;
    }
}

.publications hgroup>*,
.publications hgroup>a>* {
    margin-block: 0;
}

.body-content {
    margin-inline: auto;

    article {
        margin-inline: auto;
    }

    p {
        margin-inline-end: auto;
        margin-block-start: 0;
        margin-block-end: 1em;
    }
}

.body-content p:last-of-type {
    margin-block-end: 0;
}

.body-content h2 {
    margin-block-start: 0.25em;
    margin-block-end: 0.25em;
}

/* `body, div, header, hgroup, nav, main, footer { max-inline-size:
   none }` above uncaps main, so terms/privacy/stripe-refunds.html.twig
   render flush left with no free space in .center to center into.
   One rule here beats three template edits adding the existing
   .margin-inline\:auto utility (used that way at
   publishing/index.html.twig:12), and gives the long-dead
   .legal-page class a definition instead of a third spelling of the
   same fix. margin-inline: auto duplicates .body-content's own
   (~line 596); kept so this rule stands on its own. */
.legal-page {
    max-inline-size: var(--measure);
    margin-inline: auto;
    overflow-wrap: anywhere;
}

.border-width\:thin {
    border-width: 0.25em;
}

.border-style\:solid {
    border-style: solid;
}

.margin-inline\:auto {
    margin-inline: auto;
    max-inline-size: calc(var(--measure));
}

.garden-landing,
.nuance-landing,
.greenhouse-landing,
.reference-landing,
.dispatch-landing,
.research-landing,
.article-entry {
    display: flex;
    flex-direction: column;
    min-block-size: 85vh;
    --measure: 68ch;

    hgroup h1 {
        margin-block-start: 0;
    }
}

.press-landing {
    padding-inline: 1em;
    --measure: 75ch;
}

.publication-intro {
    --measure: 82ch;
    padding: var(--padding, var(--s1));
}

.body-content {

    hgroup h1,
    h1 {
        margin-block-start: 0;
    }
}

.garden-landing,
.nuance-landing,
.greenhouse-landing,
.reference-landing,
.dispatch-landing,
.research-landing {
    --measure: 96ch;
}

.listing {
    margin-block-end: 0.5rem;
}

.listing h3 {
    margin-block: auto;
}

.listing p {
    margin-block-end: auto;
    margin-block-start: 0.5rem;
}

/* SKR-388: a gated row is listed like any other now — the paywall gates on
   click, not on presence in the archive — so this is the only thing that
   tells a reader, before they click, that the row is Early Access or
   Restricted. Text, not colour alone (echoes .podcast-tag's own reasoning),
   so it reads the same for a screen reader or a colour-blind visitor as it
   does for anyone else. */
.listing__tag {
    border: 0.09em solid var(--ink-mid);
    /* Signal register (SKR-402), narrowed: this, .podcast-chapter__badge
       and .podcast-tag are a gating mark, not a de-emphasised label, so
       the text reads at normal contrast rather than --ink-mid — but no
       extra emphasis stacked on top (no bold, no caps/tracking, no
       chip). On a listing where most rows are gated this renders ~20
       times on one page; loud on every row recreates the wall it
       exists to prevent, so legible-at-a-glance is the whole fix, not
       louder-than-the-row. Sentence case, same as body — this is a
       gating mark, not the eyebrow register, and stacking both onto
       one short run collapses the two into a single register told
       apart only by weight. font-weight is explicit, not a no-op:
       this span sits inside the listing's <h2>/<h3> (see
       templates/publication/index.html.twig and
       templates/press/catalog.html.twig), so without it the tag
       would inherit the UA heading bold — the one emphasis signal
       this rule is supposed to not carry. */
    color: var(--color);
    font-family: var(--font-sans);
    font-size: var(--type-small);
    font-weight: 400;
    margin-inline-start: 0.6ch;
    padding: 0 0.5ch;
    white-space: nowrap;
}

section.publication-descriptions {
    --measure: 85ch;
    --padding: 0.25em;
    margin-inline: auto;
    padding-inline: 1em;

    article {

        &>hgroup {
            display: flex;
            flex-direction: row;
            flex-wrap: wrap;
            align-items: center;
            margin-block-end: 0.25em;
            border-bottom: 0.15em solid;

            h1 {
                margin-inline-end: auto;
                margin-block-end: 0;
                align-self: end;
                padding-inline-end: 0.5rem;
                font-size: clamp(1rem, 0.5vw + 1rem, 3rem);
            }

            p {
                font-size: 1em;
                font-weight: bold;
                margin-block-end: 0;
                align-self: end;
            }

            a {
                text-decoration: none;
                color: inherit;

                &:visited {
                    color: inherit;
                }
            }
        }
    }
}

.podcast-page {
    padding-inline: 1em;
    padding-block-start: var(--s-2);
    padding-block-end: var(--s1);
    --measure: 76ch;
}

.center:has(.podcast-page) {
    max-inline-size: 112ch;
}

.podcast-page--landing,
.podcast-page--episodes {
    --measure: 82ch;
}

.podcast-page--episode {
    --measure: 96ch;
}

.podcast-page :where(section, article, div, nav) {
    max-inline-size: none;
}

.podcast-landing,
.podcast-section {
    margin-inline: auto;
}

.podcast-section {
    --measure: 96ch;
    max-inline-size: var(--measure);
}

.podcast-landing,
.podcast-page--episodes .podcast-section {
    max-inline-size: none;
}

.podcast-meta,
.podcast-actions {
    font-family: var(--font-sans);
    font-size: var(--type-small);
}

/* Re-verified for SKR-401 (the previous comment here claimed nothing
   used this class; templates/podcast/person.html.twig's contributor
   links render inside .podcast-actions and need the reset, so this is
   live, not dead). */
.podcast-actions a {
    color: inherit;
}

.podcast-landing {
    display: flex;
    flex-direction: column;
    min-block-size: 85vh;
}

:is(.podcast-intro, .press-intro) {
    inline-size: min(100%, var(--measure));
    margin-inline: auto;
    max-inline-size: var(--measure);
    padding-block-start: 0;
}

:is(.podcast-intro, .press-intro) hgroup {
    border-block-end: 0.12em solid;
    margin-block-end: var(--s0);
    padding-block-end: var(--s-1);
}

:is(.podcast-intro, .press-intro) hgroup h1,
:is(.podcast-intro, .press-intro) hgroup p {
    margin-block: 0;
}

:is(.podcast-intro, .press-intro) hgroup h1 {
    font-size: clamp(var(--s0), 6vw, var(--s2));
    line-height: 1.1;
    white-space: nowrap;
}

:is(.podcast-intro, .press-intro) hgroup p {
    font-size: clamp(var(--type-small), 2.25vw, var(--s0));
    font-weight: 600;
    line-height: 1.25;
    margin-block-start: var(--s-3);
}

:is(.podcast-intro, .press-intro) .principle {
    border-block: 0.08em solid;
    border-inline-start: 0.25em solid;
    font-size: var(--s0);
    font-weight: 700;
    line-height: 1.35;
    margin-block: var(--s0);
    max-inline-size: none;
    padding: var(--s-2) var(--s-1);
}

@media (max-width: 640px) {
    :is(.podcast-intro, .press-intro) hgroup h1 {
        font-size: clamp(var(--s2), 16vw, var(--s4));
        line-height: 1.02;
        white-space: normal;
    }

    :is(.podcast-intro, .press-intro) hgroup p {
        font-size: clamp(var(--s0), 5vw, var(--s1));
    }
}

.podcast-section {
    margin-block-start: var(--s0);
}

.podcast-page--landing .podcast-section {
    inline-size: min(100%, 96ch);
    max-inline-size: 96ch;
}

.podcast-listen-cards {
    display: grid;
    gap: var(--s-1);
    grid-template-columns: 1fr;
    inline-size: 100%;
    list-style: none;
    margin: var(--s-1) 0 0;
    max-inline-size: none;
    padding: 0;
}

.podcast-listen-cards>li {
    inline-size: 100%;
    max-inline-size: none;
    min-inline-size: 0;
}

.podcast-listen-card {
    align-items: center;
    appearance: none;
    background: transparent;
    border: 0.15em solid;
    color: inherit;
    cursor: pointer;
    display: flex;
    font: inherit;
    gap: var(--s-1);
    min-block-size: 100%;
    inline-size: 100%;
    min-inline-size: 0;
    max-inline-size: none;
    padding: var(--s-1);
    text-align: start;
    text-decoration: none;
}

.podcast-listen-card:visited {
    color: inherit;
}

.podcast-listen-card:hover,
.podcast-listen-card:focus-visible {
    background: var(--background-color-inverse);
    color: var(--color-inverse);
}

.podcast-listen-card__logo {
    align-items: center;
    background: light-dark(#fff, #fdfdfc);
    border: 0.12em solid;
    color: #1a1a1a;
    display: inline-flex;
    flex: 0 0 var(--s2);
    justify-content: center;
    block-size: var(--s2);
    inline-size: var(--s2);
}

.podcast-listen-card__logo img {
    block-size: var(--s1);
    display: block;
    inline-size: var(--s1);
    max-inline-size: 100%;
}

.podcast-listen-card__copy {
    display: grid;
    gap: var(--s-5);
    flex: 1;
    min-inline-size: 0;
}

.podcast-listen-card__eyebrow,
.podcast-listen-card__label,
.podcast-listen-card__action {
    font-family: var(--font-sans);
}

.podcast-listen-card__eyebrow {
    font-size: var(--type-small);
}

.podcast-listen-card__label {
    font-size: var(--s0);
    font-weight: 700;
    line-height: 1.1;
    overflow-wrap: anywhere;
}

.podcast-listen-card__action {
    font-size: var(--type-small);
    font-weight: 700;
}

.podcast-listen-card[data-copy-state="copied"] .podcast-listen-card__action {
    text-decoration: underline;
}

.podcast-listen-links__more {
    font-family: var(--font-sans);
    font-size: var(--type-small);
    margin-block: var(--s-1) 0;
}

.podcast-listen-links__more a {
    color: inherit;
    font-weight: 700;
}

@media (min-width: 520px) {
    .podcast-listen-cards {
        grid-template-columns: repeat(2, minmax(0, 1fr));
    }
}

@media (min-width: 820px) {
    .podcast-listen-cards {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

/* Listen band cards (episode page): text only, no cdn.jsdelivr.net
   logo fetch, and nine platforms fit three-across in half the shell.
   auto-fill answers to the width it is given exactly as the chapter
   row does, rather than keying off the viewport. */
.podcast-listen-links--compact {
    display: grid;
    grid-template-rows: auto 1fr;
    margin-block-start: 0;
    max-inline-size: none;
}

/* A <p>, not an <h2>: "Subscribe & Listen" is chrome beside the
   player, not a heading-level division of the episode the way
   Chapters and Show notes are — see the eyebrow rule below. */
.podcast-listen-links__eyebrow {
    color: var(--ink-mid);
    font-family: var(--font-sans);
    font-size: var(--type-small);
    font-weight: 700;
    letter-spacing: 0.06em;
    margin: 0 0 var(--s-2);
    text-transform: uppercase;
}

/* align-content: start is load-bearing here for the same reason it is
   on the player: a grid container's default align-content is stretch,
   so these rows would silently inflate to match the band's taller
   side whenever the two halves are not equal height. */
.podcast-listen-links--compact .podcast-listen-cards {
    align-content: start;
    grid-template-columns: repeat(auto-fill, minmax(8rem, 1fr));
}

.podcast-listen-links--compact .podcast-listen-card {
    font-family: var(--font-sans);
    font-size: var(--type-small);
    gap: 0;
    padding: var(--s-3) var(--s-2);
}

.podcast-listen-links--compact .podcast-listen-card__label {
    font-size: inherit;
    font-weight: 700;
}

/* The compact RSS card dropped the .podcast-listen-card__copy grid
   wrapper along with the logo, so __action sits as a second flex
   child straight in the card row rather than stacked under the label
   — push it to the far edge instead of letting it hug the label. */
.podcast-listen-links--compact .podcast-listen-card__action {
    margin-inline-start: auto;
}

.podcast-meta {
    margin-block-start: var(--s-3);
}

/* People pages: role-grouped grid of photo cards. Portraits sit monochrome
   in the grid and take color on hover; people without a photo get their
   initials set in mono on an inverted square. */
.podcast-page--people .podcast-section {
    --measure: 96ch;
}

.podcast-people-group + .podcast-people-group {
    margin-block-start: var(--s0);
}

.podcast-people__label {
    border-block-end: 0.12em solid;
    font-weight: 700;
    margin-block: var(--s-2) var(--s-1);
    padding-block-end: var(--s-3);
}

.podcast-people {
    display: grid;
    gap: var(--s-1);
    grid-template-columns: repeat(auto-fill, minmax(min(100%, 8.75rem), 1fr));
    list-style: none;
    margin: 0;
    max-inline-size: none;
    padding: 0;
}

.podcast-people > li {
    max-inline-size: none;
    min-inline-size: 0;
}

.podcast-person {
    block-size: 100%;
    display: flex;
    flex-direction: column;
    max-inline-size: none;
    position: relative;
    transition: background-color 0.12s ease;
}

.podcast-person__avatar-frame,
span.podcast-person__avatar {
    aspect-ratio: 1;
    border-block-end: 0.12em solid;
    display: block;
    inline-size: 100%;
    max-inline-size: none;
    overflow: hidden;
}

img.podcast-person__avatar {
    block-size: 100%;
    display: block;
    filter: grayscale(1);
    inline-size: 100%;
    max-inline-size: none;
    object-fit: cover;
    transition: filter 0.16s ease;
}

.podcast-person:hover img.podcast-person__avatar,
.podcast-person:focus-within img.podcast-person__avatar {
    filter: grayscale(0);
}

span.podcast-person__avatar {
    align-items: center;
    background: var(--background-color-inverse);
    color: var(--color-inverse);
    display: flex;
    font-family: var(--font-mono);
    font-size: var(--s2);
    font-weight: 700;
    justify-content: center;
    letter-spacing: 0.04em;
}

.podcast-person__body {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    min-inline-size: 0;
    padding: var(--s-2);
}

.podcast-person__body > * {
    margin-block: 0;
}

.podcast-person__body > * + * {
    margin-block-start: var(--s-4);
}

.podcast-person__name {
    font-size: var(--s0);
    line-height: 1.15;
    overflow-wrap: anywhere;
}

.podcast-person__bio {
    max-inline-size: none;
}

.podcast-person__name a {
    color: inherit;
    text-decoration: none;
}

/* Whole-card link to the bio page, stretched over the card. */
.podcast-person__name a::after {
    content: "";
    inset: 0;
    position: absolute;
}

.podcast-person:hover,
.podcast-person:focus-within {
    /* Same fill as every other row hover in this file (--tint) — this one
       and .podcast-episode-listing's were hand-written near-duplicates of
       it (light-dark(#f1f0ed, #1f1f1e) vs --tint's #f0eeeb/#242220), which
       is how a design system's token drifts from itself. */
    background-color: var(--tint);
}

.podcast-person:hover .podcast-person__name a,
.podcast-person:focus-within .podcast-person__name a {
    text-decoration: underline;
    text-decoration-thickness: 0.08em;
    text-underline-offset: 0.14em;
}

/* Contributor bio page. */
.podcast-page--person .podcast-section {
    --measure: 68ch;
}

.podcast-person__back {
    color: inherit;
    display: inline-block;
    margin-block-end: var(--s0);
}

.podcast-person-hero {
    align-items: flex-start;
    display: flex;
    /* The global `header` rule stacks its children in a column. */
    flex-direction: row;
    flex-wrap: wrap;
    gap: var(--s0);
    max-inline-size: none;
}

.podcast-person-hero .podcast-person__avatar-frame,
.podcast-person-hero span.podcast-person__avatar {
    border: 0.12em solid;
    flex: none;
    inline-size: min(100%, 10.5rem);
}

/* The portrait carries the page: full color, unlike the index grid. */
.podcast-person-hero img.podcast-person__avatar {
    filter: none;
}

.podcast-person-hero span.podcast-person__avatar {
    font-size: var(--s3);
}

.podcast-person-hero__body {
    flex: 1 1 18rem;
    min-inline-size: 0;
}

.podcast-person-hero__body > * {
    margin-block: 0;
}

.podcast-person-hero__body > * + * {
    margin-block-start: var(--s-2);
}

.podcast-person-hero h1 {
    font-size: var(--s2);
    line-height: 1.1;
}

.podcast-person-appearances {
    margin-block-start: var(--s1);
    max-inline-size: none;
}

.podcast-person-appearances h2 {
    border-block-end: 0.12em solid;
    margin-block: 0 var(--s-1);
    padding-block-end: var(--s-3);
}

/* Episode listing card: cover thumbnail + title, excerpt, meta. */
.podcast-episode-listing {
    align-items: start;
    border: 0.12em solid;
    display: grid;
    gap: var(--s-1) var(--s0);
    grid-template-columns: 5.5rem minmax(0, 1fr);
    margin-block-end: var(--s-1);
    max-inline-size: none;
    position: relative;
    transition: background-color 0.12s ease;
}

.podcast-episode-listing--noart {
    grid-template-columns: minmax(0, 1fr);
}

.podcast-episode-listing:hover,
.podcast-episode-listing:focus-within {
    /* Migrated onto --tint (SKR-152 review) — this was a hand-written
       near-duplicate of the token (light-dark(#f1f0ed, #1f1f1e) vs
       --tint's #f0eeeb/#242220), not a deliberate second color. */
    background-color: var(--tint);
}

.podcast-episode-listing__art {
    aspect-ratio: 1;
    block-size: auto;
    border: 0.12em solid;
    display: block;
    inline-size: 100%;
    max-inline-size: 100%;
    object-fit: cover;
}

.podcast-episode-listing__body {
    display: grid;
    gap: var(--s-3);
    max-inline-size: none;
}

.podcast-episode-listing__meta {
    margin-block: 0;
}

.podcast-episode-listing__title {
    font-size: clamp(var(--s0), 1.2vw, var(--s1));
    line-height: 1.12;
    margin-block: 0;
}

.podcast-episode-listing__title a {
    color: inherit;
    text-decoration: none;
}

.podcast-episode-listing__title a::after {
    content: "";
    inset: 0;
    position: absolute;
}

.podcast-episode-listing:hover .podcast-episode-listing__title a,
.podcast-episode-listing:focus-within .podcast-episode-listing__title a {
    text-decoration: underline;
    text-decoration-thickness: 0.08em;
    text-underline-offset: 0.14em;
}

.podcast-episode-listing__excerpt {
    margin-block: 0;
    max-inline-size: 66ch;
}

/* The native <audio>. It is no longer the player — it is the player's
   engine and its no-JavaScript fallback — so it answers to its own name;
   .podcast-player is the styled component below. */
.podcast-player__native {
    display: block;
    inline-size: 100%;
    margin-block: var(--s-1);
    max-inline-size: 100%;
}

.podcast-episode-page,
.podcast-episode-hero,
.podcast-episode-hero__body,
.podcast-episode-hero__art,
.podcast-listen-band,
.podcast-episode-layout,
.podcast-episode-sidebar,
.podcast-show-notes,
.podcast-episode-section,
.podcast-show-notes__content,
.podcast-episode-actions,
.podcast-episode-endmatter,
.podcast-episode-support {
    max-inline-size: none;
}

/* The measure trap: `* { max-inline-size: var(--measure) }` (top of
   this file) is measured in an element's OWN font, so a mono run at
   --s-2 caps far narrower than this page's grid cells expect. The
   site-wide opt-out above only covers section/article/div/nav; this
   page introduces dl, ul/ol/li, a, button and others that need the
   same opt-out, so it is widened here rather than repeated per
   component. The show notes prose re-imposes a measure explicitly
   afterward — see .podcast-show-notes__content below — which is the
   one thing on this page that wants a line length. */
/* img is deliberately not in this list: there is no `img { max-inline-size:
   100% }` anywhere else in the stylesheet, so opting img out of the global
   `* { max-inline-size: var(--measure) }` cap here would let show-notes
   images (author/Transistor-supplied HTML) overflow the document. The
   components on this page that need a wide image already cap themselves —
   .podcast-episode-cover and .podcast-listen-card__logo img both set
   max-inline-size: 100%, and .podcast-person__avatar sets none — so img
   buys nothing here and costs a real overflow bug. */
.podcast-episode-page :where(
    section, article, aside, div, nav, header, footer, figure,
    ol, ul, li, dl, dt, dd, p, span, label, a, button, audio
) {
    max-inline-size: none;
}

/* The player and the dock opt out BY THEIR OWN NAME, not by where they
   sit. This is not tidiness. SKR-109 puts the dock in the layout as
   `position: fixed`, so it is NOT a descendant of .podcast-episode-page
   and cannot be made one; scoped to the page wrapper the opt-out above
   misses it entirely and 68ch of Berkeley Mono eats the bar — measured
   in the mockup at 624px for the track against 504px for the range
   input inside it, which put the playhead and the fill 40px apart at
   the same percentage. A component that has to be correct wherever it
   is mounted must not depend on an ancestor for it. */
.podcast-player,
.podcast-player *,
.podcast-player-dock,
.podcast-player-dock * {
    max-inline-size: none;
}

.podcast-episode-page {
    display: grid;
    gap: var(--s1);
    margin-inline: auto;
    max-inline-size: 104ch;
}

/* Hero: cover art alongside the facts row, the h1 and the contributor
   credits. Concrete border, not .box: .box's own border variable
   (--border-thin) is undefined and the class renders borderless as a
   result (SKR-143, tracked separately) — this component does not
   depend on it. */
.podcast-episode-hero {
    align-items: start;
    border: 0.12em solid;
    display: grid;
    gap: var(--s0);
    padding: var(--s-1);
}

.podcast-episode-hero__art {
    max-inline-size: 11rem;
}

.podcast-episode-cover {
    aspect-ratio: 1;
    block-size: auto;
    border: 0.15em solid;
    display: block;
    inline-size: 100%;
    max-inline-size: 100%;
    object-fit: cover;
}

.podcast-episode-hero__body {
    align-content: start;
    display: grid;
    gap: var(--s-1);
}

/* The byline's replacement: a <dl> of exactly the values a reader was
   reading (date, runtime, episode number). The <dt>s stay in the
   markup as .visually-hidden — the structure is right for a screen
   reader, and the render shows what a sighted reader was reading. */
.podcast-episode-facts {
    column-gap: var(--s0);
    display: flex;
    flex-wrap: wrap;
    margin: 0;
    row-gap: var(--s-3);
}

.podcast-episode-facts > div {
    align-items: baseline;
    display: flex;
    gap: 0.8ch;
}

.podcast-episode-facts dd {
    font-variant-numeric: lining-nums tabular-nums;
    margin: 0;
}

.podcast-episode-hero h1 {
    font-size: clamp(var(--s1), 3.6vw, var(--s2));
    line-height: 1.05;
    margin-block: 0;
    max-inline-size: 20ch;
}

/* --- The photo byline ----------------------------------------------
   Contributors render exactly once, here (SKR-116), reusing the same
   .podcast-person component the people index uses at a different
   scale: a 2.4rem greyscale portrait beside a name, colour on hover,
   scoped under .podcast-episode-people so the index card's own sizing
   and hover-highlight do not leak in. */
.podcast-episode-people {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-2) var(--s0);
    list-style: none;
    margin: 0;
    padding: 0;
}

.podcast-episode-people .podcast-person {
    /* The shared .podcast-person rule sets block-size: 100% for the
       people-index grid, where every card in a row stretches to the
       tallest one. Left unset here, a multi-row credits list overflows
       its own flex container: the first flex line stretches every
       item to its own line height (fine), but the wrapped second line
       renders outside the container's reported height, overlapping
       whatever comes after it in the hero body. */
    align-items: center;
    block-size: auto;
    flex-direction: row;
    gap: var(--s-2);
    min-inline-size: 0;
}

.podcast-episode-people .podcast-person:hover,
.podcast-episode-people .podcast-person:focus-within {
    background-color: transparent;
}

.podcast-episode-people .podcast-person__avatar-frame,
.podcast-episode-people span.podcast-person__avatar {
    aspect-ratio: 1;
    block-size: 2.4rem;
    border: 0.12em solid;
    flex: none;
    inline-size: 2.4rem;
}

.podcast-episode-people span.podcast-person__avatar {
    font-size: var(--type-small);
}

.podcast-episode-people .podcast-person__body,
.podcast-episode-people .podcast-person__body > * {
    margin: 0;
}

.podcast-episode-people .podcast-person__body {
    display: grid;
    gap: var(--s-5);
    min-inline-size: 0;
    padding: 0;
}

.podcast-episode-people .podcast-person__role {
    color: var(--ink-mid);
    font-family: var(--font-sans);
    font-size: var(--type-small);
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.podcast-episode-people .podcast-person__name {
    color: inherit;
    line-height: 1.15;
    text-decoration-thickness: 0.08em;
    text-underline-offset: 0.14em;
}

.podcast-episode-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-2);
    margin-block-start: var(--s-2);
}

.podcast-episode-actions a {
    align-items: baseline;
    border: 0.12em solid;
    color: inherit;
    display: inline-flex;
    font-family: var(--font-sans);
    gap: 0.6ch;
    padding: var(--s-3) var(--s-1);
    text-decoration: none;
}

.podcast-episode-actions a:hover,
.podcast-episode-actions a:focus-visible {
    background: var(--background-color-inverse);
    color: var(--color-inverse);
}

.podcast-episode-action__label {
    font-size: var(--type-small);
    opacity: 0.7;
}

.podcast-episode-action__where {
    font-size: var(--type-small);
    font-weight: 700;
}

@media (min-width: 40rem) {
    .podcast-episode-hero {
        gap: var(--s1);
        grid-template-columns: minmax(0, 13rem) minmax(0, 1fr);
    }

    .podcast-episode-hero--textonly {
        grid-template-columns: minmax(0, 1fr);
    }

    .podcast-episode-hero__art {
        max-inline-size: none;
    }
}

/* ============================================================
   THE LISTEN BAND — player at half the shell, the platform cards in
   the other half, at the same 60rem breakpoint the body layout uses.
   Below it they stack, player first: sampling precedes subscribing,
   so that is source order and it costs nothing (SKR-117).

   SKR-110: the styled player is in the slot now, with the native
   <audio> inside it as the no-script fallback, so the band is balanced
   against the cards the way it was measured to be.
   ============================================================ */
.podcast-listen-band {
    display: grid;
    gap: var(--s1);
    grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 60rem) {
    .podcast-listen-band:not(.podcast-listen-band--single) {
        grid-template-columns: 1fr 1fr;
    }
}

.podcast-listen-band .podcast-player__native {
    margin-block: 0;
}

/* SKR-152 review: the thin, unconditional membership pointer under the
   listen band. Tinted (var(--tint) — the token every other hover/callout
   fill on the site was just unified onto, not a second value) so it reads
   as a distinct element rather than another paragraph, but still one
   line: no heading, no border on any side, padding kept tight rather than
   the block-code register (tint fill + strong left border) the bottom
   callout uses. That register is the argument; this is only a pointer to
   it, so it stays visually lighter on purpose. */
.podcast-episode-listen-support {
    background: var(--tint);
    font-size: var(--type-small);
    margin: var(--s1) 0 0;
    max-inline-size: none;
    padding: var(--s-3) var(--s-1);
}

.podcast-episode-listen-support a {
    color: inherit;
    font-weight: 700;
}

/* ============================================================
   THE PLAYER (SKR-110)

   Ported from docs/design/episode-spike/hifi.css section 8, with the
   mockup's CSS-only fakery taken out: there, `:has()` over a radio
   group stood in for every state the component has. Here the states are
   real — episode_player_controller.js drives a native <audio> — and the
   rules that survive are the ones drawing a state something actually
   sets. Two exceptions are called out where they land: the notch map,
   which was fourteen hand-written percentages and is now written by the
   controller, and the readout's total, which is keyed on the condition
   the template branches on rather than on a class.

   The register is the page's: no heavy box, one solid mark (the play
   button, the same inverted block .status-page__code uses), controls as
   mono text rather than an icon set. This is a publication; it does not
   need a media application on its page.

   No :focus-visible declarations for the controls that have one. The
   global rule at the top of this file draws the ring in var(--color) at
   max(2px, 0.15em). What IS declared below is the pair of rules for
   labels whose input is visually hidden — the ring has to move from the
   input to the pill, and nothing global can know that.
   ============================================================ */
/* .podcast-player-pending (SKR-309) is the panel a preview or an
   unentitled early-access reader gets in place of the player: same
   border and padding, deliberately NOT the .podcast-player class itself,
   because :has(.podcast-player) below is what yields the hero's runtime
   cell to a real player — a panel with no runtime to show must not claim
   that cell too. */
.podcast-player,
.podcast-player-pending {
    /* container, not viewport: 464px is 464px whatever the window is
       doing, which is the same discipline the chapter row follows.
       Concrete border width, not .box: --border-thin is referenced by
       .box and defined nowhere (SKR-143), so .box renders borderless. */
    border: 0.12em solid;
    container-name: player;
    container-type: inline-size;
    padding: var(--s-2);
}

/* THE NO-SCRIPT CONTRACT, in two rules. The <audio controls> ships
   visible and the styled rows ship `hidden`; the controller reverses
   both on connect. A browser that never runs it renders a working
   native player inside the component's border, which is what this page
   rendered before the styled player existed. */
.podcast-player__ui {
    display: grid;
    gap: var(--s-2);
}

.podcast-player__ui[hidden] {
    display: none;
}

.podcast-player--enhanced .podcast-player__native {
    display: none;
}

/* The hero's runtime cell yields to the player, and only to a player that
   exists. Runtime is a property of the CUT you are hearing rather than a
   fact about the page, so it belongs on the edition row — and since that
   row now renders with or without script, this is keyed on the player
   being on the page at all rather than on the enhanced class it used to
   wait for. An episode with no playable cut renders no player and keeps
   its hero runtime, which is then the only place the page says it. */
.podcast-episode-page:has(.podcast-player) .podcast-episode-facts__runtime {
    display: none;
}

/* The icon sprite is markup, not a picture: it defines two symbols and
   draws nothing itself. Sized to nothing so it cannot take a row. */
.podcast-player__sprite {
    block-size: 0;
    inline-size: 0;
    overflow: hidden;
    position: absolute;
}

/* --- Row 0: WHICH CUT THIS IS -------------------------------------
   A LABEL AND A VALUE, in the idiom of
   docs/design/episode-spike/player-a.css — the shape the hero's facts
   row already uses. It answers "which cut is this", which every
   episode has an answer to, rather than "which cut do you want",
   which only some do.

   IT IS QUIET ON PURPOSE. Most episodes carry one edition, so this is
   what nearly every episode page renders: a mono label in ink-mid and
   a value in full ink, no border, no fill, no pointer, nothing
   focusable. A statement of fact set at the weight of a caption. The
   pills are a control and look like one; this is not and does not.

   It also carries the whole no-script story for the edition. The pills
   are inside .podcast-player__ui, which ships hidden, so without this
   row a reader with no JavaScript on a two-edition episode is told
   nothing about which cut the native player is pointed at.
   ------------------------------------------------------------------ */
.podcast-player__edition-fact {
    align-items: baseline;
    column-gap: var(--s-2);
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    /* A margin rather than a gap on .podcast-player, which is not a
       grid container and does not become one for this row: turning it
       into one would re-lay-out every state of the component to place
       a caption. */
    margin: 0 0 var(--s-2);
    row-gap: var(--s-4);
}

.podcast-player__edition-label {
    color: var(--ink-mid);
    font-family: var(--font-sans);
    font-size: var(--type-small);
    letter-spacing: 0.06em;
    text-transform: uppercase;
    white-space: nowrap;
}

.podcast-player__edition-value {
    color: var(--color);
    font-family: var(--font-sans);
    font-size: var(--type-small);
}

.podcast-player__edition-value .podcast-player__edition-runtime {
    font-size: var(--type-small);
}

/* WHERE THERE ARE PILLS, THE PILLS SAY IT. They carry the same two
   facts and carry them as a choice, which is more than this row can
   do — so it goes, and only once the controller has actually drawn
   them. :has(), not a modifier, because the condition is the one the
   template branches on: the row exists or it does not. */
.podcast-player--enhanced:has(.podcast-player__editions) .podcast-player__edition-fact {
    display: none;
}

/* --- Row 1: EDITION -----------------------------------------------
   Edition is CONTENT — which cut of the episode this is — and it
   carries a runtime, because "which cut" is a question about length
   before it is a question about anything else. Audio quality is a
   PREFERENCE, set once and not re-decided per episode, so it is not in
   this row; it is in the settings menu beside speed.

   The members-only cut says MEMBERS on the pill before anyone clicks
   it. That single word is what stops the gate from being a trap.

   A single-edition episode does not render this row at all — a
   segmented control with one segment is a broken control, and the
   runtime is already on the readout. In Twig that is
   `{% if editions|length > 1 %}`, so there is no modifier class here.
   ------------------------------------------------------------------ */
.podcast-player__editions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-3);
}

.podcast-player__editions input {
    opacity: 0;
    pointer-events: none;
    position: absolute;
}

.podcast-player__editions label {
    align-items: baseline;
    border: 0.12em solid;
    cursor: pointer;
    display: inline-flex;
    font-family: var(--font-sans);
    font-size: var(--type-small);
    gap: 0.8ch;
    letter-spacing: 0.04em;
    /* --s-3/--s-2, not --s-4/--s-3. At the tighter pair the pills
       measured 88.2 × 21.5 and 139.8 × 21.5 with 5.3px between them,
       which fails WCAG 2.2 SC 2.5.8 outright, and the spacing exception
       does not rescue a target 5px short in the cross axis with its
       neighbour 5px away. This clears 24px without touching the type
       size, which is the constraint: --s-2 is the floor for type here
       and the pills already sit on it. */
    padding: var(--s-3) var(--s-2);
    text-transform: uppercase;
}

.podcast-player__edition-runtime {
    color: var(--ink-mid);
    font-variant-numeric: lining-nums tabular-nums;
}

.podcast-player__editions input:checked + label {
    background: var(--background-color-inverse);
    color: var(--color-inverse);
}

.podcast-player__editions input:checked + label .podcast-player__edition-runtime {
    color: inherit;
    opacity: 0.7;
}

/* The ring moves to the pill because the input carrying focus is
   invisible. Not a re-declaration of the global rule — a relocation of
   it, and the only place in this component that needs one. */
.podcast-player__editions input:focus-visible + label {
    outline: max(2px, 0.15em) solid;
    outline-offset: 0.15em;
}

/* The gate marker. Present at rest, not sprung on a click. */
.podcast-player__members {
    border: 0.09em solid currentColor;
    line-height: 1.3;
    opacity: 0.85;
    padding: 0 0.5ch;
}

/* --- Row 2: THE SCRUB — two marks, one quantity each ---------------

   An earlier round rejected filling each chapter's LENGTH meter in the
   index with playback progress, because one bar cannot mean two
   quantities: a half-filled length bar reads as "this chapter is half
   as long as it is". That rejection stands and the meters in the rail
   stay length-only, always, in every state of this page.

   The same rejection left a door open — if per-chapter progress is
   wanted it needs its own mark — and this is that mark, twice:

     upper bar   position in the WHOLE EPISODE. The notches are the real
                 chapter starts, cut out of the bar in the page colour
                 so they read on the played and unplayed sides alike. A
                 notch is a gridline, not a second measurement.
     lower rule  the CURRENT CHAPTER, alone. Its width is that chapter's
                 extent on the same axis and its offset puts it under
                 the segment it describes; its fill is position inside
                 it.

   Two marks, two quantities, one axis, and they agree. The notches earn
   their place before playback starts: an empty bar with fourteen ticks
   already says this episode is three long segments and a dozen short
   ones, in 9px of height.
   ------------------------------------------------------------------ */
.podcast-player__scrub {
    padding-block-start: var(--s-4);
    position: relative;
}

.podcast-player__bar {
    background: var(--tint);
    block-size: 0.5rem;
    display: block;
    overflow: hidden;
    position: relative;
}

/* ink-FILL, not tint-strong and not ink-mid. Drawn in tint-strong the
   played portion was very nearly invisible against the tint track — the
   most important quantity on the bar reading as the quietest mark on
   it. See the token's own note: how much of this episode you have heard
   should not come out of the same jar as "this chapter is a
   digression". */
.podcast-player__fill {
    background: var(--ink-fill);
    block-size: 100%;
    display: block;
    /* clamp(), not a bare var(), for the same reason the chapter meter
       clamps: a value outside 0–100% is invalid at computed-value time
       and inline-size falls back to auto — a FULL bar, not an empty
       one. Failing open is the wrong direction for a progress mark. */
    inline-size: clamp(0%, var(--position, 0%), 100%);
}

/* NO BACKGROUND-IMAGE HERE. In the mockup this was fourteen identical
   hand-written gradients and a background-position list of E9's own
   chapter starts, with a fifteenth stop parked off-canvas so the two
   editions could share one rule. Episodes do not have fourteen chapters
   each, so the controller writes both properties from the ACTIVE
   edition's chapter starts over that edition's runtime, and the two
   :has() rules that swapped between two hardcoded maps are gone with
   the maps. Everything a generated list needs to sit on stays here. */
.podcast-player__ticks {
    background-repeat: no-repeat;
    background-size: 0.11rem 100%;
    display: block;
    inset: 0;
    position: absolute;
}

.podcast-player__chapter-bar {
    background: var(--tint-strong);
    block-size: 0.16rem;
    display: block;
    inline-size: clamp(0%, var(--chapter-length, 0%), 100%);
    margin-block-start: 0.16rem;
    margin-inline-start: clamp(0%, var(--chapter-start, 0%), 100%);
}

.podcast-player__chapter-bar > span {
    background: var(--color);
    block-size: 100%;
    display: block;
    inline-size: clamp(0%, var(--chapter-position, 0%), 100%);
}

/* Idle: there is no current chapter, so the lower rule carries nothing
   and says so by not being drawn. visibility rather than display, so
   the row does not change height when playback starts. */
.podcast-player--idle .podcast-player__chapter-bar {
    visibility: hidden;
}

/* A real range input over the drawing: focusable, arrow-keyable, and it
   degrades to a native slider if every rule below is dropped. Its own
   track is transparent — the bar underneath IS the track. Hit area
   1.5rem, drawn mark 9px: a rule you can hit and a rule you can see are
   different objects and only the second one has to be quiet. */
.podcast-player__seek {
    appearance: none;
    background: none;
    block-size: 1.5rem;
    inline-size: 100%;
    inset-block-start: calc(var(--s-4) - 0.5rem);
    inset-inline: 0;
    margin: 0;
    position: absolute;
    z-index: 2;
}

.podcast-player__seek::-webkit-slider-runnable-track {
    background: none;
    block-size: 1.5rem;
}

/* The playhead is a tick of ink standing on the rule — the mark a
   proof-reader would make, not a knob. */
.podcast-player__seek::-webkit-slider-thumb {
    appearance: none;
    background: var(--color);
    block-size: 1.05rem;
    border: 0;
    border-radius: 0;
    inline-size: 0.22rem;
    margin-block-start: 0.225rem;
}

.podcast-player__seek::-moz-range-track {
    background: none;
    block-size: 1.5rem;
}

.podcast-player__seek::-moz-range-thumb {
    background: var(--color);
    block-size: 1.05rem;
    border: 0;
    border-radius: 0;
    inline-size: 0.22rem;
}

/* --- Row 3: TRANSPORT ----------------------------------------------
   Chapter back, 15 back, play, 30 forward, chapter forward, then
   settings. The skips and chapter jumps are mono TEXT, not an icon set,
   because a glyph invented for "previous chapter" has to be learned and
   the word does not. The one exception is the play button, the house's
   single filled control, a triangle in every player ever made.

   The two skip amounts are asymmetric on purpose: 15 back is "what did
   he say", 30 forward is "skip the tangent", and those are different
   distances.

   Speed and audio quality share one disclosure because they are the
   same class of thing — how it plays, not what plays.
   ------------------------------------------------------------------ */
.podcast-player__transport {
    align-items: center;
    display: flex;
    gap: var(--s-3);
}

.podcast-player__key {
    background: none;
    border: 0.12em solid transparent;
    color: var(--ink-mid);
    cursor: pointer;
    flex: 0 0 auto;
    /* Moved off mono (SKR-402): it was kept here as a
       keyboard-legend-style mark ("«CH", "−15", "+30", "CH»"), but
       it's a transparent bordered button, not the "inverted mono
       square" idiom that justifies the other mono holdouts
       (.podcast-player__play, span.podcast-person__avatar,
       .account-feed-card__logo) — it was the only button on the site
       not in sans. Two of its four labels are numerals, which belong
       in sans regardless. */
    font-family: var(--font-sans);
    font-size: var(--type-small);
    letter-spacing: 0.02em;
    padding: var(--s-3) var(--s-3);
    white-space: nowrap;
}

.podcast-player__key:hover,
.podcast-player__key:focus-visible {
    color: var(--color);
}

/* Echoes .status-page__code and .podcast-person__avatar's initials
   block — the inverted mono square is the house's one filled mark. */
.podcast-player__play {
    align-items: center;
    background: var(--background-color-inverse);
    block-size: 2.6rem;
    border: 0;
    color: var(--color-inverse);
    cursor: pointer;
    display: inline-flex;
    flex: 0 0 auto;
    inline-size: 2.6rem;
    justify-content: center;
    padding: 0;
}

.podcast-player__play svg {
    block-size: 1rem;
    fill: currentColor;
    inline-size: 1rem;
}

.podcast-player__gap {
    flex: 1 1 auto;
}

.podcast-player__settings {
    flex: 0 0 auto;
    position: relative;
}

/* ink-mid, not tint-strong. tint-strong is the block-code gutter — a
   decorative rule on a tinted ground — and it measures 1.51:1 against
   the page. That is fine for a quote edge and it is not a control
   boundary: WCAG 2.2 SC 1.4.11 wants 3:1 for the visual boundary of a
   control, and this box IS the control. The same substitution is made
   on the speed/quality options and the chapter badge. */
.podcast-player__settings > summary {
    border: 0.12em solid var(--ink-mid);
    cursor: pointer;
    display: inline-block;
    font-family: var(--font-sans);
    font-size: var(--type-small);
    font-variant-numeric: lining-nums tabular-nums;
    letter-spacing: 0.04em;
    list-style: none;
    /* Same bump as the pills, measured the same way: 33.1 × 21.1 is
       under the 24px SC 2.5.8 floor in the cross axis. */
    padding: var(--s-3) var(--s-2);
    text-transform: uppercase;
}

.podcast-player__settings > summary::-webkit-details-marker {
    display: none;
}

.podcast-player__settings > summary:hover,
.podcast-player__settings > summary:focus-visible {
    border-color: currentColor;
}

.podcast-player__settings[open] > summary {
    background: var(--background-color-inverse);
    border-color: var(--background-color-inverse);
    color: var(--color-inverse);
}

/* Opens downward. Upward it would cover the scrub and the readout — the
   two things a person is most likely to be reading when they reach for
   the speed control. Downward it covers page content, which closing it
   recovers. */
.podcast-player__menu {
    background: var(--background-color);
    border: 0.12em solid;
    inline-size: 13rem;
    inset-block-start: calc(100% + 0.25rem);
    inset-inline-end: 0;
    padding: var(--s-2);
    position: absolute;
    z-index: 5;
}

/* A <p>, not an <h3>. The player sits above the page's first <h2>, so a
   heading inside it would skip a level. The groups point at these with
   aria-labelledby instead. */
.podcast-player__menu-heading {
    color: var(--ink-mid);
    font-family: var(--font-sans);
    font-size: var(--type-small);
    font-weight: 700;
    letter-spacing: 0.06em;
    margin: 0 0 var(--s-4);
    text-transform: uppercase;
}

.podcast-player__options {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-4);
    margin-block-end: var(--s-2);
}

.podcast-player__options:last-child {
    margin-block-end: 0;
}

.podcast-player__options input {
    opacity: 0;
    pointer-events: none;
    position: absolute;
}

.podcast-player__options label {
    border: 0.09em solid var(--ink-mid);
    cursor: pointer;
    font-family: var(--font-sans);
    font-size: var(--type-small);
    font-variant-numeric: lining-nums tabular-nums;
    letter-spacing: 0.04em;
    /* 28.2 × 21.1 with 3.5px gaps was the worst target on the page.
       --s-3 block and --s-2 inline clears 24px in both axes and still
       leaves the five speeds on one row inside the 13rem menu, which is
       the binding constraint. */
    padding: var(--s-3) var(--s-2);
    text-transform: uppercase;
}

.podcast-player__options input:checked + label {
    background: var(--background-color-inverse);
    border-color: var(--background-color-inverse);
    color: var(--color-inverse);
}

.podcast-player__options input:focus-visible + label {
    outline: max(2px, 0.15em) solid;
    outline-offset: 0.12em;
}

/* --- Row 4: THE READOUT --------------------------------------------
   TWO CELLS: the chapter, and elapsed. Chapter is the one thing the
   page does not otherwise say about the moment you are in; elapsed is
   the one number the bar cannot say precisely. A mode word, a chapter
   counter, the edition name and a percentage were all cut for restating
   a value already on screen — the edition is named on the pill, the
   counter counts a list printed beside the player, the percentage is
   the fill on the bar directly above. A status line is not a manifest.

   ELAPSED IS THE ANCHOR, in full ink. The row is otherwise ink-mid, and
   with the mode gone there was nothing on it set in the loud value. The
   cell that answers "is something happening right now" is the clock, so
   the clock takes the ink the mode used to.

   Durations of an hour or more are h:mm:ss. There is no state of this
   player in which a runtime is written 113:39 — the controller formats
   from seconds and never from a supplied label (SKR-147).
   ------------------------------------------------------------------ */
.podcast-player__status {
    align-items: baseline;
    color: var(--ink-mid);
    display: flex;
    font-family: var(--font-sans);
    font-size: var(--type-small);
    font-variant-numeric: lining-nums tabular-nums;
    gap: 0.8ch;
    letter-spacing: 0.04em;
    margin: 0;
    min-inline-size: 0;
}

/* ink-mid, not tint-strong: at 1.51:1 the separator was invisible — a
   mark whose whole job is to be seen between two others. */
.podcast-player__status > * + *::before {
    color: var(--ink-mid);
    content: '\00B7';
    margin-inline-end: 0.8ch;
}

/* The chapter cell is the only one allowed to be long, so it is the
   only one that shrinks and the only one that truncates. `flex: 0 1
   auto`, not `1 1 auto`: letting it GROW pushed the clock to the right
   edge and left an orphaned middot floating in the middle of the line.
   A status line is one left-aligned run, not two groups with a hole
   between them. */
.podcast-player__chapter-name {
    flex: 0 1 auto;
    min-inline-size: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.podcast-player__clock {
    color: var(--color);
    flex: none;
    white-space: nowrap;
}

/* The total, where it survives at all, stays secondary: elapsed is the
   value that changes and the value being asked for. */
.podcast-player__clock .podcast-player__clock-total {
    color: var(--ink-mid);
}

/* The total is SUPPRESSED WHENEVER THE PILL ROW IS THERE, because the
   pill row already prints it: "REGULAR 1:53:39" and "/ 1:53:39" were
   sitting 40px apart inside one 464px box. The trade runs both ways — a
   single-edition episode drops the pill row and keeps the total here.

   Keyed on the PRESENCE OF THE ROW, not on a second edition's class:
   in Twig the row is absent (`{% if editions|length > 1 %}`) rather
   than class-hidden, and this rule has to read the same condition the
   template does. The mockup keyed it on .podcast-player__edition--expanded
   because its gallery needed a one-edition frame drawn from a
   two-edition markup; nothing here does. */
.podcast-player:has(.podcast-player__editions) .podcast-player__clock-total {
    display: none;
}

/* AND ON A SINGLE-EDITION EPISODE THE FACT ROW PRINTS IT. That row used
   to be absent there, which is why the readout was the one place a
   one-edition player showed a total at all; now that the row always
   renders, keeping the total here would put 1:53:39 twice inside one
   464px box for exactly the reason the rule above exists. The markup
   stays and the controller keeps writing it: a value that is right only
   while it is hidden is a bug waiting for the rule above it to change,
   and the dock reuses the same class where nothing suppresses it. */
.podcast-player--one-edition .podcast-player__clock-total {
    display: none;
}

/* Below 20rem the total goes on the episodes that still have one — a
   single-edition player on a phone. Chapter and elapsed never yield;
   there is nothing left to give up before them. */
@container player (max-width: 20rem) {
    .podcast-player__clock-total {
        display: none;
    }
}

/* --- THE GATE — a visitor without the cut selects it ----------------

   1. IT IS NOT A TRAP. The pill reads EXPANDED 2:00:27 MEMBERS before
      it is clicked. Clicking it is therefore "show me what that is",
      and what appears answers that question with this episode's own
      numbers.

   2. THE SELECTION STICKS. Snapping back tells the person the site has
      overruled them, leaves them nothing to read, and throws away the
      only signal they gave. The pill stays chosen and goes DASHED —
      provisional, not disabled. Dimming reads as "we switched this
      off"; dashes read as "pending".

   3. IT OPENS BELOW THE TRANSPORT, at the foot of the player. Not a
      modal: a modal takes the page hostage over an offer nobody asked
      for. Not a toast: a toast expires and this needs a decision. Below
      rather than above because THE TRANSPORT MUST NOT MOVE — the free
      edition may well be playing, and sliding the pause button out from
      under a cursor is the actual hostile act. Every row above the
      panel is pixel-identical to the state before the click, readout
      included, because the panel is the last child and because the
      controller does not touch the timeline for a selection it did not
      honour.

   4. NOTHING WAS TAKEN AWAY, and the panel does not need a line saying
      so. It proves it instead of asserting it.

   5. THE WAY BACK IS A BUTTON. Not a bare <label>, which is not
      keyboard-operable as a control; and no longer a third radio in
      the edition group, which is what it was. That radio put the panel
      on the arrow cycle — regular, gated (open), stay (close) — made a
      two-edition episode present three radios, let the group report
      its value as "Stay on regular" while a pill was painted selected,
      and parked focus on a control whose only visible affordance was
      inside the panel it had just closed. A <button> in the panel is
      focusable, operable, and visible for as long as it is focused,
      and it re-checks the default pill, which is what closes the
      panel.

   6. WHICH PANEL OPENS IS THE CONTROLLER'S, not :has()'s. There is one
      panel per gated cut — an episode can carry a gated `condensed`
      and a gated `expanded` at once — and CSS cannot pair an arbitrary
      pill with an arbitrary panel, so one rule opened the first panel
      whichever pill you chose. The `hidden` attribute does it instead.
      Nothing is given up: this whole subtree is inside
      .podcast-player__ui, which does not exist without the script.
   ------------------------------------------------------------------ */
.podcast-player__gate {
    /* The block-code register — tint ground, tint-strong left edge —
       which is also the membership callout's surface further down the
       page, so the two read as one voice. */
    background: var(--tint);
    border-inline-start: 0.14em solid var(--tint-strong);
    padding: var(--s-2);
}

.podcast-player:has(.podcast-player__edition--gated:checked)
    .podcast-player__edition--gated:checked + label {
    background: none;
    border-style: dashed;
    color: inherit;
}

.podcast-player:has(.podcast-player__edition--gated:checked)
    .podcast-player__edition--gated:checked + label .podcast-player__edition-runtime {
    color: var(--ink-mid);
    opacity: 1;
}

/* Serif: this is editorial copy about the episode, not chrome. */
.podcast-player__gate-body {
    font-size: var(--type-small);
    margin: 0 0 var(--s-2);
    max-inline-size: 52ch;
}

/* The body became a container when gate copy started arriving authored, as its
   own paragraphs, rather than as one generated sentence. The spacing that used
   to belong to the single <p> moves onto them, so a one-paragraph gate occupies
   exactly the space it always did and a two-paragraph one is not run together. */
.podcast-player__gate-body p {
    margin: 0;
}

.podcast-player__gate-body p + p {
    margin-block-start: var(--s-2);
}

.podcast-player__gate-actions {
    align-items: center;
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-3) var(--s-2);
}

.podcast-player__cta {
    background: var(--background-color-inverse);
    border: 0.12em solid var(--background-color-inverse);
    color: var(--color-inverse);
    display: inline-block;
    font-family: var(--font-sans);
    font-size: var(--type-small);
    padding: var(--s-4) var(--s-2);
    text-decoration: none;
}

.podcast-player__cta:hover,
.podcast-player__cta:focus-visible {
    background: transparent;
    border-color: currentColor;
    color: inherit;
}

/* The way back, and it is a plain button now: the selected state goes
   back on the default pill through `input:checked + label` like every
   other selection in this row, so the pair of :has() rules that used to
   put it there by hand are gone with the radio they were compensating
   for. No focus rule either — the global one draws the ring, because
   this is a real focusable control and not a label standing in for one. */
.podcast-player__stay {
    background: none;
    border: 0.12em solid currentColor;
    color: inherit;
    cursor: pointer;
    display: inline-block;
    font-family: var(--font-sans);
    font-size: var(--type-small);
    padding: var(--s-4) var(--s-2);
}

.podcast-player__stay:hover,
.podcast-player__stay:focus-visible {
    background: var(--background-color-inverse);
    border-color: var(--background-color-inverse);
    color: var(--color-inverse);
}

.podcast-player__signin {
    color: var(--ink-mid);
    font-size: var(--type-small);
    text-underline-offset: 0.16em;
}

/* THE MEMBER VARIANT. The identical click for someone entitled to the
   cut: no MEMBERS marker, and — because the controller finds a source
   for it — no dashed pill and no panel. The pill's runtime, the seek
   range and the notch map simply move to the longer cut, because for
   this visitor they really did. One control, two outcomes, one
   component; the difference is PodcastAccessPolicy, not a second
   player. */
.podcast-player--member .podcast-player__members {
    display: none;
}

/* The transport does not wrap and cannot shrink below its min-content,
   so at the bottom of the range it is the row that decides whether the
   player fits inside its own border. At a 320px viewport the player is
   248px and the row's min-content measured 246.5 against a 227.7
   content box. INLINE padding only: the block padding is what carries
   the 24px target height and it is not touched. */
@container player (max-width: 19rem) {
    .podcast-player__transport {
        gap: var(--s-4);
    }

    .podcast-player__editions label {
        letter-spacing: 0;
    }

    .podcast-player__key {
        padding-inline: var(--s-4);
    }

    .podcast-player__settings > summary {
        padding-inline: var(--s-3);
    }
}

/* Motion is opt-in, and the gate deliberately gets no entrance
   animation at any setting: an offer you have to wait 140ms to read has
   already wasted your time, and it replaces nothing and moves nothing,
   so there is no continuity for a transition to preserve. (Recorded as
   a trap: built with `transition: display 140ms allow-discrete` the
   panel opened correctly and never closed, because a discrete display
   transition pins the old value. It looks exactly like a :has()
   invalidation failure and is not one.) */
@media (prefers-reduced-motion: no-preference) {
    .podcast-player__key,
    .podcast-player__editions label,
    .podcast-player__options label,
    .podcast-player__settings > summary,
    .podcast-player__cta,
    .podcast-player__stay {
        transition: background-color 120ms ease-out, border-color 120ms ease-out,
            color 120ms ease-out, opacity 120ms ease-out;
    }
}

/* ============================================================
   THE DOCK (SKR-109)

   Ported from docs/design/episode-spike/hifi.css section 8.7. The
   player is promoted into templates/site/layout.html.twig inside a
   data-turbo-permanent wrapper, and what it puts at the foot of every
   page is this.

   IT IS SHORTER THAN WHAT IT REPLACES. Every pixel of dock height is
   charged twice — to the page AND to the sticky chapter rail — and it
   is charged on a phone first, so the design undercuts the measured
   bar (60px wide, 84px narrow) rather than adding to it. MEASURED:
   55.84px wide and 77.39px narrow, before the type scale (SKR-402;
   see --dock-space's own note below) grew the narrow figure to
   81.9px — still under the bar it replaces, with less margin than it
   had. SKR-405 separately gave the docked transport keys a legal
   WCAG 2.2 SC 2.5.8 target size, without changing this row's height.
   It buys chapter skip and both seek amounts out of the width the
   measured dock spends on a centred episode title, not out of
   height, and the play button stays 2.4rem: nothing about the
   primary control gets smaller in exchange for extra keys.

   Four shapes were considered and rejected before this one: a TALLER
   dock, AUTO-HIDING it on scroll, REMOVING the inline player once
   docked, and FILLING the rail's chapter length bars with playback
   progress. The first two trade the rail's budget or the reader's
   orientation for nothing; the third takes away the component the
   reader was already using; the fourth makes one bar mean two
   quantities.

   The controls are the inline player's own — .podcast-player__key,
   __play, __scrub, __bar, __fill, __ticks, __seek — resized here and
   not redrawn. Two players that look like two players are two players.
   ============================================================ */
.podcast-player-dock {
    background: var(--background-color);
    border-block-start: 0.12em solid;
    container-name: dock;
    container-type: inline-size;
    inset-block-end: 0;
    inset-inline: 0;
    position: fixed;
    z-index: 20;
}

/* Written down rather than left to the UA sheet. `position: fixed` is
   an author declaration and the `display: none` that has to beat it is
   not, so the pair reads as a coincidence unless it is stated. */
.podcast-player-dock[hidden] {
    display: none;
}

.podcast-player-dock__inner {
    align-items: center;
    column-gap: var(--s-2);
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    margin-inline: auto;
    max-inline-size: 104ch;
    padding: var(--s-3) var(--s0);
    row-gap: var(--s-4);
}

/* The dock's two text lines are <p>. Their default 1em block margins
   are what took the first build of this component to 65px — a dock
   TALLER than the one it is supposed to undercut, with the height in
   margin nobody could see. */
.podcast-player-dock p {
    margin: 0;
}

.podcast-player-dock__keys {
    align-items: center;
    display: flex;
    gap: var(--s-4);
}

.podcast-player-dock .podcast-player__play {
    block-size: 2.4rem;
    inline-size: 2.4rem;
}

.podcast-player-dock .podcast-player__play svg {
    block-size: 0.9rem;
    inline-size: 0.9rem;
}

/* SKR-405: measured 24.0 × 19.1 with the docked keys 3.6px apart, a
   WCAG 2.2 SC 2.5.8 failure with no spacing exception available —
   these are the primary transport controls (skip back/forward,
   chapter jump) on a phone. --s-3 clears 24px in the block axis at
   the type scale's --type-small; re-measure after any further change
   to either scale rather than trusting this arithmetic. */
.podcast-player-dock .podcast-player__key {
    font-size: var(--type-small);
    padding: var(--s-3) var(--s-4);
}

/* Chapter jumps only where there is width for them. A reader scrolled
   deep into a page with the dock at the foot is handling a mumbled
   sentence, not jumping segments; the chapter index a screen above is
   for that. */
.podcast-player-dock__chapter-key {
    display: none;
}

@container dock (min-width: 30rem) {
    .podcast-player-dock__chapter-key {
        display: inline-block;
    }
}

/* And never at all on an episode with no chapters, which the container
   query above cannot know. Attribute specificity, not source order:
   the rule it has to beat is inside a container query. */
.podcast-player-dock__chapter-key[hidden] {
    display: none;
}

.podcast-player-dock__now {
    display: grid;
    gap: 0.15rem;
    min-inline-size: 0;
}

.podcast-player-dock__line {
    display: flex;
    font-size: var(--type-small);
    gap: 0.8ch;
    min-inline-size: 0;
    white-space: nowrap;
}

.podcast-player-dock__episode {
    color: var(--ink-mid);
    font-family: var(--font-sans);
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

/* The chapter cell carries the episode title and the chapter both —
   the dock is read away from the episode's own page, where the h1 is
   not there to say which episode this is — so it is the only cell
   allowed to be long and the only one that truncates. */
.podcast-player-dock__chapter {
    overflow: hidden;
    text-overflow: ellipsis;
}

.podcast-player-dock .podcast-player__scrub {
    padding-block-start: 0;
}

.podcast-player-dock .podcast-player__bar {
    block-size: 0.3rem;
}

.podcast-player-dock .podcast-player__seek {
    block-size: 1.1rem;
    inset-block-start: -0.4rem;
}

.podcast-player-dock .podcast-player__seek::-webkit-slider-runnable-track {
    block-size: 1.1rem;
}

.podcast-player-dock .podcast-player__seek::-webkit-slider-thumb {
    block-size: 0.75rem;
    margin-block-start: 0.175rem;
}

.podcast-player-dock .podcast-player__seek::-moz-range-thumb {
    block-size: 0.75rem;
}

.podcast-player-dock__clock {
    align-items: center;
    display: flex;
    font-family: var(--font-sans);
    font-size: var(--type-small);
    font-variant-numeric: lining-nums tabular-nums;
    gap: var(--s-2);
    white-space: nowrap;
}

.podcast-player-dock__clock .podcast-player__clock-total {
    color: var(--ink-mid);
}

/* Below 612px the clock drops to a second row rather than crushing
   the chapter title. That is the trade the measured dock already
   makes, at the same cost, on the screen that can least afford it.
   Pinned to the same 612px as the media query below --dock-space —
   see its note there for why this cannot be written in rem. */
@container dock (max-width: 612px) {
    .podcast-player-dock__inner {
        grid-template-columns: auto minmax(0, 1fr);
        padding-inline: var(--s-2);
    }

    .podcast-player-dock__clock {
        grid-column: 2;
        justify-content: flex-end;
    }
}

/* --dock-space IS THE DOCK'S HEIGHT, AND IT IS 0 WHEN THERE IS NO DOCK.
   Two things have to give exactly that back: the body, so the bar
   covers nothing at the foot of a page, and the episode page's sticky
   chapter rail, whose budget is 100vh minus its gutters minus this.
   That second one is why the dock is HIDDEN when nothing is playing
   rather than merely empty — at 1440x900 an always-present bar costs
   the rail about one visible chapter for the entire session, on every
   page, including the ones nobody has played anything from.

   Keyed on the bar being present rather than on a class the controller
   toggles: a class on the page element would have to be re-applied on
   every Turbo visit, and the bar's own `hidden` attribute is already
   the fact being asked about.

   3.1rem is 55.8px on this site's 18px root at the wide width — still
   the measured dock to within a twentieth of a pixel; the docked
   transport keys sit under the play button there, which is taller, so
   neither SKR-405's key padding nor the type scale touches this row's
   height. 4.55rem is 81.9px at the narrow width, RE-MEASURED after
   the type scale (SKR-402) took .podcast-player-dock__clock from
   12px to 15px: 15 × 1.5 − 12 × 1.5 is 4.5px, exactly the growth over
   the previous 77.4px, and the clock is the tallest thing on this row
   at this width. It is not SKR-405's key padding — the keys share
   this row with .podcast-player__play at a fixed 2.4rem block-size,
   so their own padding never sets the row's height. Re-measure this
   token again if the type scale changes, not if the keys' padding
   does.

   612px, IN BOTH THE CONTAINER QUERY ABOVE AND THE MEDIA QUERY BELOW,
   DELIBERATELY THE SAME NUMBER WRITTEN THE SAME WAY — not two widths
   that happen to agree. The dock is fixed and full-bleed, so its
   container width IS the viewport width, which makes `rem` tempting
   for the container query too. That drifts: a container query's
   `rem` resolves against the root font size, which SKR-400 pinned to
   112.5% of the reader's own browser default rather than a flat
   18px, while a MEDIA query's `rem` is fixed by spec to the INITIAL
   font size (16px) regardless of what the root is set to. The two
   only number the same width when the reader's default happens to be
   16px; at any other default — 20px, say — the container query fires
   well past where the media query already reserved the taller row,
   and the dock covers the foot of the page for the band between the
   two thresholds. `px` is the one unit that means the same thing in
   both query types no matter the reader's font-size preference, which
   is why both are written that way here rather than in a shared
   token. :root cannot be styled from inside `@container dock`, which
   is the other way out — a container query only reaches the
   container's own descendants, and the dock is a descendant of the
   element that carries the token. */
:root {
    --dock-space: 0px;
}

:root:has(.podcast-player-dock:not([hidden])) {
    --dock-space: 3.1rem;
}

@media (max-width: 612px) {
    :root:has(.podcast-player-dock:not([hidden])) {
        --dock-space: 4.55rem;
    }
}

body {
    padding-block-end: var(--dock-space);
}

/* ============================================================
   BODY — chapters lead in source order, notes follow. Below 60rem
   that puts the index directly under the band; at 60rem the grid
   puts the notes back in column one and lifts the same element into
   the sticky rail. One DOM, no duplicated markup, and a screen reader
   gets chapters before notes at every width.
   ============================================================ */
.podcast-episode-layout {
    display: grid;
    gap: var(--s1);
    grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 60rem) {
    .podcast-episode-layout {
        align-items: start;
        grid-template-columns: minmax(0, 1fr) minmax(18rem, 22rem);
    }

    .podcast-episode-layout--no-sidebar {
        grid-template-columns: minmax(0, 1fr);
    }

    .podcast-show-notes {
        grid-column: 1;
        grid-row: 1;
    }

    .podcast-episode-sidebar {
        inset-block-start: var(--s0);
        /* --dock-space is the dock's height, and it is 0px until a
           dock is actually on screen — see the token's own note in the
           dock section above. Every pixel of it is charged twice, to
           the page AND to this budget. */
        max-block-size: calc(100vh - var(--s1) * 2 - var(--dock-space));
        overflow-y: auto;
        overscroll-behavior: contain;
        position: sticky;
        grid-column: 2;
        grid-row: 1;
    }
}

.podcast-episode-section + .podcast-episode-section {
    margin-block-start: var(--s1);
}

.podcast-episode-section > h2 {
    align-items: baseline;
    border-block-end: 0.12em solid;
    display: flex;
    flex-wrap: wrap;
    font-size: var(--s1);
    gap: 0 1ch;
    line-height: 1.15;
    margin: 0 0 var(--s-2);
    padding-block-end: var(--s-4);
}

/* The rail's own heading sticks to the top of the rail while the rail
   scrolls inside itself — a long chapter list can run taller than the
   sticky rail's own viewport budget, and a nested scroll region is the
   price of not truncating the most useful index on the page. */
.podcast-episode-sidebar .podcast-episode-section > h2 {
    background: var(--background-color);
    inset-block-start: 0;
    position: sticky;
    z-index: 1;
}

.podcast-episode-sidebar {
    align-content: start;
    display: grid;
    gap: var(--s0);
}

/* --- The chapter row: one component, two layouts -------------------
   The container query lives on the LIST, so the row lays itself out
   from the width it actually has, not the viewport: folded at a
   1440-wide shell where the rail is 22rem, unwrapped at 834 and 390
   where the rail is full width. Neither is a mobile variant of the
   other.

   SKR-110 completes the port: the duration column is back, now that
   the formatting lives on the model (PodcastEpisode::formatDuration)
   instead of being re-derived in Twig, and --current is drawn because
   there is finally a playback position to mark it from. The length
   METER is unchanged and stays LENGTH — each chapter's share of its
   edition's runtime, never playback progress. That is the scrub's
   lower rule's job, and one bar cannot mean two quantities. */
.podcast-chapter-list {
    container-name: chapters;
    container-type: inline-size;
    list-style: none;
    margin: 0;
    padding: 0;
}

/* SKR-110: the row is a SEEK TARGET now, and it is written for two
   elements because it renders as both. Twig emits a <div>; the player
   promotes it to a real <button> when it connects, and leaves it alone
   when it does not. That is the whole no-script contract for this rail
   — inert text rather than a control that eats a tab stop and does
   nothing — and `:where(div, button)` is what lets one rule serve it
   without the button form winning on specificity. */
.podcast-chapter > :where(div, button) {
    align-items: baseline;
    color: inherit;
    column-gap: var(--s-2);
    display: grid;
    grid-template-columns: 1.7ch minmax(0, 1fr) auto;
    padding-block: var(--s-3);
    padding-inline: var(--s-3);
    row-gap: 0;
    text-align: start;
}

/* The button form arrives carrying the UA's button styling, all of
   which is wrong here: it is a row of a list, not a widget. */
.podcast-chapter > button {
    appearance: none;
    background: none;
    border: 0;
    cursor: pointer;
    font: inherit;
    inline-size: 100%;
}

/* The control hover the platform cards already use — the invert. The
   row previously underlined its title and revealed a small cue, which
   is the treatment for a link in prose, not for a target you click to
   move playback. Inverting is one gesture, matches the cards two feet
   above it, and costs no layout.

   Hover was here before there was anything to click, as a preview of
   the eventual target; focus-visible joins it now that there is. */
.podcast-chapter > :where(div, button):hover,
.podcast-chapter > button:focus-visible {
    background: var(--background-color-inverse);
    color: var(--color-inverse);
}

/* Everything in the row that carries its own ink has to follow the
   invert or it drops out against it: time, duration, title and cue
   because they are quieter than the row and need currentColor to stay
   legible, and the members badge for the opposite reason. SKR-402
   holds .podcast-chapter__badge at full-strength var(--color), not
   --ink-mid, so a gating mark reads at normal contrast — and on an
   inverted row that same full strength is exactly what would make it
   the darkest thing on a dark background if it did not follow too.
   currentColor is the inverse text colour inside these rules, so the
   marks stay related without naming a second set of values. */
.podcast-chapter > :where(div, button):hover .podcast-chapter__time,
.podcast-chapter > button:focus-visible .podcast-chapter__time,
.podcast-chapter > :where(div, button):hover .podcast-chapter__duration,
.podcast-chapter > button:focus-visible .podcast-chapter__duration,
.podcast-chapter > :where(div, button):hover .podcast-chapter__title,
.podcast-chapter > button:focus-visible .podcast-chapter__title,
.podcast-chapter > :where(div, button):hover .podcast-chapter__cue,
.podcast-chapter > button:focus-visible .podcast-chapter__cue,
.podcast-chapter > :where(div, button):hover .podcast-chapter__badge,
.podcast-chapter > button:focus-visible .podcast-chapter__badge {
    color: inherit;
}

/* The badge's border is --ink-mid at rest, quieter than its own text —
   the invert should keep that relationship rather than jump to a full
   currentColor box, which would read louder than the text it outlines.
   50% measured ~3.93:1 in light mode and ~3.33:1 in dark against the
   inverted row — SC 1.4.11's 3:1 for a control boundary, but thin on a
   0.09em hairline. 60% gives ~5.11:1 light / ~4.54:1 dark instead,
   comfortable headroom while still reading clearly quieter than the
   text it outlines. */
.podcast-chapter > :where(div, button):hover .podcast-chapter__badge,
.podcast-chapter > button:focus-visible .podcast-chapter__badge {
    border-color: color-mix(in srgb, currentColor 60%, transparent);
}

.podcast-chapter > :where(div, button):hover .podcast-chapter__meter,
.podcast-chapter > button:focus-visible .podcast-chapter__meter {
    background: color-mix(in srgb, currentColor 22%, transparent);
}

.podcast-chapter > :where(div, button):hover .podcast-chapter__meter > span,
.podcast-chapter > button:focus-visible .podcast-chapter__meter > span {
    background: currentColor;
}

/* The ring is drawn outside the row, on the page ground, so it takes
   the page's ink rather than the inverted row's — otherwise it
   disappears into the background it sits on. Only the colour is set
   here; the width and offset are the global rule's. */
.podcast-chapter > button:focus-visible {
    outline-color: var(--color);
}

/* The cue holds its column always and is drawn only for the chapter
   that is PLAYING — a state the invert cannot express, because hover is
   transient. It no longer appears on hover: it existed to say "this is
   a control", and the invert says that across the whole row now. The
   column stays reserved either way, so nothing reflows as playback
   walks down the list. */
.podcast-chapter > :where(div, button) > .podcast-chapter__cue {
    color: var(--ink-mid);
    font-family: var(--font-sans);
    font-size: var(--type-small);
    grid-column: 1;
    grid-row: 1;
    line-height: 1.5;
    opacity: 0;
}

/* opacity: 1, explicit rather than inherited: an unscoped `.podcast-chapter__time
   { opacity: 0.75 }` written for chapters embedded in raw show-notes prose used to
   reach this rail row too and drop 12px mono text under the 4.5:1 AA floor
   (measured 3.67:1 light, 3.52:1 dark). That rule is gone with formatChapterList(),
   but this one stays scoped and explicit rather than trusting the cascade again. */
.podcast-chapter > :where(div, button) > .podcast-chapter__time {
    color: var(--ink-mid);
    font-family: var(--font-sans);
    font-size: var(--type-small);
    font-variant-numeric: lining-nums tabular-nums;
    grid-column: 2;
    grid-row: 1;
    opacity: 1;
}

/* Body size, not --s-1: shrinking chapter titles would rank the
   episode's own index below the show notes prose sitting beside it. */
.podcast-chapter > :where(div, button) > .podcast-chapter__title {
    grid-column: 2 / -1;
    grid-row: 2;
}

.podcast-chapter > :where(div, button) > .podcast-chapter__duration {
    color: var(--ink-mid);
    font-family: var(--font-sans);
    font-size: var(--type-small);
    font-variant-numeric: lining-nums tabular-nums;
    grid-column: 3;
    grid-row: 1;
    text-align: end;
}

.podcast-chapter > :where(div, button) > .podcast-chapter__meter {
    align-self: center;
    background: var(--tint);
    block-size: 0.28rem;
    display: block;
    grid-column: 2 / -1;
    grid-row: 3;
    margin-block-start: var(--s-3);
}

/* clamp(), not a bare var(): a negative --length (a chapter start past
   durationSeconds) is invalid at computed-value time, and inline-size
   falls back to auto — a FULL bar, not an empty one. Clamping keeps a
   bad value inside 0%–100% instead of failing open. */
.podcast-chapter__meter > span {
    background: var(--ink-fill);
    block-size: 100%;
    display: block;
    inline-size: clamp(0%, var(--length, 0%), 100%);
}

/* The chapter playing. aria-current in the markup; drawn with the
   inline-code tint and an INSET edge rather than a border, so the row
   does not move as playback walks down the list. */
.podcast-chapter--current > :where(div, button) {
    background: var(--tint);
    box-shadow: inset 0.18em 0 0 currentColor;
}

.podcast-chapter--current .podcast-chapter__cue {
    opacity: 1;
}

.podcast-chapter--current .podcast-chapter__title {
    font-weight: 600;
}

/* The members-only chapter is SHOWN on the free edition's list, not
   hidden from it — with the same marker the pill carries. Someone
   deciding whether the longer cut is worth it is entitled to see what
   is in it.

   One row carried three marks for one fact in an earlier round: a
   dashed left border, ink-mid text, and the badge. The border went. It
   was the only one of the three that says nothing on its own — an
   unlabelled dashed edge is not readable as "members", it competes with
   --current's inset edge two rows away, and it was drawn in tint-strong
   at 1.51:1. What is left is quieter text plus the one mark that names
   the reason. */
.podcast-chapter--members .podcast-chapter__title,
.podcast-chapter--members .podcast-chapter__time {
    color: var(--ink-mid);
}

.podcast-chapter__badge {
    border: 0.09em solid var(--ink-mid);
    /* Signal register (SKR-402): see .listing__tag. */
    color: var(--color);
    font-family: var(--font-sans);
    font-size: var(--type-small);
    margin-inline-start: 0.8ch;
    padding: 0 0.5ch;
    white-space: nowrap;
}

@container chapters (min-width: 34rem) {
    .podcast-chapter > :where(div, button) {
        grid-template-columns: 1.7ch 8ch minmax(0, 26ch) minmax(7rem, 1fr) 6ch;
    }

    .podcast-chapter > :where(div, button) > .podcast-chapter__title {
        grid-column: 3;
        grid-row: 1;
    }

    .podcast-chapter > :where(div, button) > .podcast-chapter__meter {
        grid-column: 4;
        grid-row: 1;
        margin-block-start: 0;
    }

    .podcast-chapter > :where(div, button) > .podcast-chapter__duration {
        grid-column: 5;
        grid-row: 1;
    }
}

/* 320px: the row's cue column is the first thing worth giving up, and
   the only thing — never the time, never the title. */
@media (max-width: 24rem) {
    .podcast-chapter > :where(div, button) {
        grid-template-columns: 0 minmax(0, 1fr) auto;
    }

    .podcast-chapter > :where(div, button) > .podcast-chapter__cue {
        display: none;
    }
}

.podcast-show-notes {
    margin: 0;
}

/* THE RESET AND THE RHYTHM MUST HAVE THE SAME SPECIFICITY, or the
   reset wins the start edge and every paragraph renders with no
   spacing between it and the next — which is exactly what a
   `__content p { margin-block: 0 }` reset (0,1,1) does to a
   `> * + *` rhythm rule (0,1,0): it wins regardless of source order,
   and being a shorthand it zeroes the very edge the rhythm rule was
   trying to set. This is NOT the house style of omitting `</p>`
   producing bad nesting — correctly-parsed sibling paragraphs get
   zero gap between them regardless. `> *` for the reset keeps both
   rules at (0,1,0), so source order decides and the later rule wins
   the one edge it sets; first and last child stay flush, which is
   what the section wants since its own h2 already carries the
   spacing above. */
.podcast-show-notes__content {
    color: inherit;
    max-inline-size: 68ch;
}

.podcast-show-notes__content > * {
    margin-block: 0;
}

.podcast-show-notes__content > * + * {
    margin-block-start: var(--s-1);
}

.podcast-show-notes__content :where(h2, h3, h4) {
    border-block-end: 0.08em solid;
    line-height: 1.15;
    margin-block: var(--s1) var(--s-1);
    padding-block-end: var(--s-4);
}

.podcast-show-notes__content :where(ul, ol) {
    padding-inline-start: var(--s0);
}

.podcast-show-notes__content li + li {
    margin-block-start: var(--s-3);
}

.podcast-show-notes__content hr {
    border: 0;
    border-block-start: 0.08em solid;
    margin-block: var(--s0);
}

.podcast-show-notes__content a {
    color: inherit;
    font-weight: 700;
    overflow-wrap: anywhere;
    text-decoration-thickness: 0.08em;
    text-underline-offset: 0.14em;
}

/* ============================================================
   END MATTER — the membership callout, spanning the band with its
   lede and offer in two columns. The lede does not argue this
   episode: it says what the money is for and that the regular cut
   stays free, rather than restating the styled player's gate copy a
   screen further down (that gate is not ported here).
   ============================================================ */
.podcast-episode-endmatter {
    border-block-start: 0.12em solid;
    padding-block-start: var(--s0);
}

.podcast-episode-endmatter > * + * {
    margin-block-start: var(--s0);
}

/* SKR-135's aftershow cross-reference. Deliberately not the tinted box
   .podcast-episode-support gets a few lines down — this is a pointer to
   another episode, not the membership pitch, and giving it the same
   background would read as one component doing two jobs. */
.podcast-episode-companion {
    border-inline-start: 0.14em solid var(--ink-mid);
    padding-inline-start: var(--s-1);
}

.podcast-episode-companion h2 {
    font-size: var(--s0);
    font-weight: 700;
    margin: 0 0 var(--s-2);
}

.podcast-episode-companion :where(p) {
    margin-block: 0 var(--s-1);
    max-inline-size: 54ch;
}

.podcast-episode-companion__cta a {
    color: inherit;
    font-weight: 700;
    text-decoration-thickness: 0.08em;
    text-underline-offset: 0.14em;
}

.podcast-episode-support {
    background: var(--tint);
    border-inline-start: 0.14em solid var(--tint-strong);
    padding: var(--s-1) var(--s0) var(--s0);
}

.podcast-episode-support h2 {
    font-size: var(--s0);
    font-weight: 700;
    margin: 0 0 var(--s-2);
}

/* :where(p), not p: a bare `.podcast-episode-support p` is (0,1,1),
   which beats both __lede's margin-block-end: 0 (0,1,0) and __cta's
   margin-block-end: var(--s-2) (0,1,0) regardless of source order —
   the same specificity trap the show-notes reset/rhythm comment above
   describes. :where() keeps this at (0,1,0) so source order decides. */
.podcast-episode-support :where(p) {
    margin-block: 0 var(--s-1);
    max-inline-size: 54ch;
}

@media (min-width: 60rem) {
    .podcast-episode-support {
        column-gap: var(--s1);
        display: grid;
        grid-template-columns: minmax(0, 1fr) minmax(0, 21rem);
    }

    .podcast-episode-support h2 {
        grid-column: 1 / -1;
    }

    .podcast-episode-support__lede {
        margin-block-end: 0;
    }

    .podcast-episode-support__perks {
        flex-direction: column;
        gap: var(--s-3);
        margin-block-end: var(--s-1);
    }
}

.podcast-episode-support__perks {
    display: flex;
    flex-wrap: wrap;
    font-family: var(--font-sans);
    font-size: var(--type-small);
    gap: var(--s-3) var(--s0);
    list-style: none;
    margin: 0 0 var(--s-1);
    padding: 0;
}

.podcast-episode-support__perks li::before {
    color: var(--ink-mid);
    content: '\2014\a0';
}

.podcast-episode-support__cta {
    margin-block-end: var(--s-2);
}

.podcast-episode-support__cta a {
    background: var(--background-color-inverse);
    border: 0.12em solid var(--background-color-inverse);
    color: var(--color-inverse);
    display: inline-block;
    font-family: var(--font-sans);
    font-size: var(--type-small);
    padding: var(--s-4) var(--s-2);
    text-decoration: none;
}

.podcast-episode-support__cta a:hover,
.podcast-episode-support__cta a:focus-visible {
    background: transparent;
    border-color: currentColor;
    color: inherit;
}

/* Error / status pages: the status code sits as an inverted mono block,
   like the person-initial avatars; actions echo the episode action buttons. */
.status-page {
    display: flex;
    flex-direction: column;
    justify-content: center;
    min-block-size: 60vh;
    inline-size: min(100%, var(--measure));
    margin-inline: auto;
    padding-block: var(--s1);
    padding-inline: 1em;
}

.status-page__code {
    align-self: flex-start;
    background: var(--background-color-inverse);
    color: var(--color-inverse);
    /* Mono stays here on purpose (SKR-402): a specific highlight, not
       chrome — the status code sits as an inverted mono block, the
       same idiom as the person-initial avatars. */
    font-family: var(--font-mono);
    font-size: var(--s2);
    font-weight: 700;
    letter-spacing: 0.08em;
    line-height: 1;
    margin-block: 0;
    padding: var(--s-2) var(--s-1);
}

.status-page h1 {
    /* rem so the rule matches the masthead weight, not the h1's em size */
    border-block-end: 0.15rem solid;
    font-size: clamp(var(--s1), 5vw, var(--s3));
    line-height: 1.05;
    margin-block: var(--s0) var(--s-1);
    max-inline-size: none;
    padding-block-end: var(--s-1);
}

.status-page__message,
.status-page__detail {
    margin-block: 0;
    max-inline-size: 52ch;
}

.status-page__detail {
    margin-block-start: var(--s-2);
}

.status-page__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-2);
    margin-block-start: var(--s1);
    max-inline-size: none;
}

.status-page__actions a {
    border: 0.12em solid;
    color: inherit;
    font-family: var(--font-sans);
    font-size: var(--type-small);
    font-weight: 700;
    padding: var(--s-3) var(--s-1);
    text-decoration: none;
}

.status-page__actions a:hover,
.status-page__actions a:focus-visible {
    background: var(--background-color-inverse);
    color: var(--color-inverse);
}

/* Article paywall: a status-page variant that leads with the piece itself —
   publication context line, article title, then the public summary teaser. */
.article-paywall__context {
    font-family: var(--font-sans);
    font-size: var(--type-small);
    font-weight: 700;
    letter-spacing: 0.08em;
    margin-block: 0;
    text-transform: uppercase;
}

.article-paywall__teaser {
    margin-block-end: var(--s0);
    max-inline-size: 52ch;
}

.account-page {
    padding-inline: 1em;
    padding-block-start: var(--s-2);
    padding-block-end: var(--s1);
    --measure: 72ch;
}

.account-page--dashboard {
    --measure: 86ch;
}

.account-page :where(form, section, article, header, footer, div) {
    max-inline-size: none;
}

.account-hero {
    margin-inline: auto;
}

.account-hero h1,
.account-hero p {
    margin-block: 0;
}

.account-hero p {
    max-inline-size: 58ch;
}

.account-layout,
.account-dashboard-grid,
.account-footer-actions {
    margin-inline: auto;
    max-inline-size: var(--measure);
}

.account-layout {
    display: grid;
    gap: var(--s0);
    grid-template-columns: minmax(0, 1fr);
    margin-block-start: var(--s0);
}

.account-alerts {
    display: grid;
    gap: var(--s-1);
}

.account-card {
    border: 0.15em solid;
    padding: var(--s-1);
}

.account-card--current {
    background: var(--background-color-inverse);
    color: var(--color-inverse);
}

.account-card h2 {
    font-size: var(--s1);
    line-height: 1.1;
    margin-block: 0 var(--s-2);
}

.account-card p {
    margin-block: 0;
}

.account-card p+p,
.account-card p+form,
.account-card form+p,
.account-card form+form,
.account-message,
.account-help {
    margin-block-start: var(--s-2);
}

.account-form {
    display: grid;
    gap: var(--s-2);
    margin-block-start: var(--s-1);
}

.account-field {
    display: grid;
    gap: var(--s-4);
    max-inline-size: min(100%, 34rem);
}

.account-field label,
.account-help,
.account-alert,
.account-message,
.account-button,
.account-link-button,
.account-status,
.account-feed-card__eyebrow,
.account-feed-card__title,
.account-feed-card__button,
.account-feed-card__details summary {
    font-family: var(--font-sans);
}

/* Split out of the group above (SKR-402): "RSS" here is a fixed
   three-letter monogram in an inverted square, the same specific-
   highlight idiom as the person-initial avatars and the status
   code, not a label that reads with the rest of a sentence. */
.account-feed-card__logo {
    font-family: var(--font-mono);
}

.account-field label,
.account-button,
.account-link-button,
.account-status,
.account-feed-card__button,
.account-feed-card__details summary {
    font-weight: 700;
}

.account-field label,
.account-help,
.account-alert,
.account-message,
.account-button,
.account-link-button,
.account-status,
.account-feed-card__button,
.account-feed-card__details summary {
    font-size: var(--type-small);
}

.account-form input:not([type='hidden']) {
    background: var(--background-color);
    border: 0.12em solid;
    color: var(--color);
    font-family: var(--font-sans);
    font-size: var(--type-small);
    line-height: 1.25;
    inline-size: 100%;
    max-inline-size: 100%;
    padding: var(--s-3) var(--s-2);
}

.account-button,
.account-link-button {
    appearance: none;
    -webkit-appearance: none;
    background: var(--background-color);
    border: 0.12em solid;
    color: var(--color);
    cursor: pointer;
    display: inline-block;
    justify-self: start;
    line-height: 1.25;
    padding: var(--s-2) var(--s-1);
    text-align: center;
    text-decoration: none;
}

.account-button--primary,
.account-link-button--primary {
    background: var(--background-color-inverse);
    color: var(--color-inverse);
}

.account-card--current .account-button--primary {
    background: var(--background-color);
    color: var(--color);
}

.account-alert,
.account-message {
    border: 0.12em solid;
    margin-block: 0;
    padding: var(--s-2);
}

.account-message {
    background: var(--background-color-inverse);
    color: var(--color-inverse);
}

.account-auth-method-list {
    display: grid;
    gap: var(--s-2);
    list-style: none;
    margin-block: var(--s-1) 0;
    padding: 0;
}

.account-auth-method-list li {
    align-items: center;
    border-block-start: 0.08em solid;
    display: flex;
    gap: var(--s-2);
    justify-content: space-between;
    padding-block-start: var(--s-2);
}

.account-auth-method-list span,
.account-auth-method-list small {
    display: block;
}

.account-auth-method-list small {
    font-family: var(--font-sans);
    font-size: var(--type-small);
    margin-block-start: var(--s-4);
}

.account-dashboard-grid {
    display: grid;
    gap: var(--s0);
    grid-template-columns: repeat(2, minmax(0, 1fr));
    margin-block-start: var(--s0);
}

.account-card--wide {
    grid-column: 1 / -1;
}

.account-status {
    border: 0.12em solid;
    display: inline-block;
    margin-block-end: var(--s-2);
    padding: var(--s-4) var(--s-2);
}

.account-status--good {
    background: var(--background-color-inverse);
    color: var(--color-inverse);
}

.account-card--current .account-status,
.account-card--current .account-status--good {
    background: var(--background-color);
    color: var(--color);
}

.account-plan {
    font-size: var(--s1);
    font-weight: 700;
    line-height: 1.15;
}

.account-feed-groups {
    display: grid;
    gap: var(--s0);
    margin-block-start: var(--s0);
}

.account-feed-groups--columns {
    grid-template-columns: repeat(2, minmax(0, 1fr));
}

.account-feed-group {
    align-content: start;
    border-block-start: 0.08em solid;
    display: grid;
    gap: var(--s-2);
    padding-block-start: var(--s-1);
}

.account-feed-group h3 {
    font-size: var(--s0);
    line-height: 1.1;
    margin-block: 0;
}

.account-feed-list {
    display: grid;
    gap: var(--s-1);
    grid-template-columns: 1fr;
    list-style: none;
    margin: 0;
    max-inline-size: none;
    padding: 0;
}

.account-feed {
    min-inline-size: 0;
}

.account-feed-card {
    border: 0.15em solid;
    display: grid;
    gap: var(--s-1);
    min-block-size: 100%;
    padding: var(--s-1);
}

.account-feed-card__main {
    align-items: center;
    display: flex;
    gap: var(--s-1);
    min-inline-size: 0;
}

.account-feed-card__logo {
    align-items: center;
    background: light-dark(#fff, #fdfdfc);
    border: 0.12em solid;
    color: #1a1a1a;
    display: inline-flex;
    flex: 0 0 var(--s2);
    font-size: var(--type-small);
    font-weight: 700;
    justify-content: center;
    block-size: var(--s2);
    inline-size: var(--s2);
}

.account-feed-card__copy {
    display: grid;
    gap: var(--s-5);
    min-inline-size: 0;
}

.account-feed-card__eyebrow {
    font-size: var(--type-small);
}

.account-feed-card__title {
    font-size: var(--s0);
    line-height: 1.1;
    overflow-wrap: anywhere;
}

.account-feed-card__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-2);
}

.account-feed-card__button {
    align-items: center;
    appearance: none;
    background: transparent;
    border: 0.12em solid;
    color: inherit;
    cursor: pointer;
    display: inline-flex;
    justify-content: center;
    line-height: 1.2;
    padding: var(--s-3) var(--s-1);
    text-align: center;
    text-decoration: none;
}

.account-feed-card__button:visited {
    color: inherit;
}

.account-feed-card__button:hover,
.account-feed-card__button:focus-visible,
.account-feed-card__button[data-copy-state='copied'] {
    background: var(--background-color-inverse);
    color: var(--color-inverse);
}

.account-feed-card__details {
    border-block-start: 0.08em solid;
    padding-block-start: var(--s-2);
}

.account-feed-card__details summary {
    cursor: pointer;
}

.account-feed-card__details code {
    display: block;
    margin-block-start: var(--s-4);
    overflow-wrap: anywhere;
    white-space: normal;
}

.account-footer-actions {
    border-block-start: 0.15em solid;
    margin-block-start: var(--s0);
    padding-block-start: var(--s-1);
}

@media (max-width: 900px) {

    .account-dashboard-grid,
    .account-feed-groups--columns {
        grid-template-columns: 1fr;
    }
}

@media (max-width: 520px) {

    .account-card,
    .account-footer-actions {
        padding: var(--s-2);
    }

    .account-button,
    .account-link-button,
    .account-feed-card__button {
        inline-size: 100%;
    }
}

/* ============================================================
   ACCOUNT ACCESS REDESIGN (SKR-212)

   One integrated auth-* card replaces the two-card account-*
   layout on templates/security/login.html.twig — email leads, and
   the passkey/provider list sits under a single divider instead of
   living in its own "Other ways to sign in" card. Transcribed from
   docs/design/account-access/login.css onto this file's own tokens:
   serif prose, Berkeley Mono for controls at --s-1, hard 0.12em
   borders, light-dark() colour pairs. auth-* keeps its own
   namespace so it can sit next to the account-* rules above without
   colliding — other /account pages still depend on those.
   ============================================================ */

.auth-page {
    padding-inline: 1em;
    padding-block: var(--s2) var(--s3);
}

.auth-page :where(form, section, header, footer, div, nav) {
    max-inline-size: none;
}

/* The icon sprite is markup, not a picture: it defines the provider
   and passkey glyphs and draws nothing itself. Sized to nothing so
   it cannot take a row — the podcast player's own sprite rule. */
.auth-page__sprite {
    block-size: 0;
    inline-size: 0;
    overflow: hidden;
    position: absolute;
}

.auth-card {
    background: var(--background-color);
    border: 0.15em solid;
    inline-size: min(100%, 26rem);
    margin-inline: auto;
    padding: var(--s1);
}

.auth-card h1 {
    font-size: var(--s1);
    line-height: 1.1;
    margin-block: 0;
}

.auth-card .account-alerts {
    margin-block-end: var(--s1);
}

.auth-lede {
    margin-block: var(--s-2) 0;
}

.auth-form {
    display: grid;
    gap: var(--s-2);
    margin-block-start: var(--s-1);
}

.auth-field {
    display: grid;
    gap: var(--s-4);
}

.auth-field label {
    font-family: var(--font-sans);
    font-size: var(--type-small);
    font-weight: 700;
}

.auth-field input {
    background: var(--background-color);
    border: 0.12em solid;
    color: var(--color);
    font-family: var(--font-sans);
    font-size: var(--type-small);
    inline-size: 100%;
    line-height: 1.25;
    padding: var(--s-3) var(--s-2);
}

/* The chosen email, carried into the password and create steps.
   One row: the address is a fact, changing it is the escape hatch. */
.auth-identity {
    align-items: center;
    border: 0.12em solid;
    display: flex;
    font-family: var(--font-sans);
    font-size: var(--type-small);
    gap: var(--s-2);
    justify-content: space-between;
    margin-block-start: var(--s-1);
    padding: var(--s-3) var(--s-2);
}

.auth-identity a {
    color: inherit;
    font-weight: 700;
    text-decoration-thickness: 0.08em;
    text-underline-offset: 0.16em;
}

.auth-button {
    appearance: none;
    -webkit-appearance: none;
    align-items: center;
    background: var(--background-color);
    border: 0.12em solid;
    color: var(--color);
    cursor: pointer;
    display: flex;
    font-family: var(--font-sans);
    font-size: var(--type-small);
    font-weight: 700;
    gap: 0.7em;
    inline-size: 100%;
    justify-content: center;
    line-height: 1.25;
    padding: var(--s-2) var(--s-1);
    text-align: center;
    text-decoration: none;
}

.auth-button:hover {
    background: var(--tint);
}

.auth-button--primary {
    background: var(--background-color-inverse);
    color: var(--color-inverse);
}

.auth-button--primary:hover {
    background: var(--background-color-inverse);
    opacity: 0.85;
}

.auth-button svg {
    block-size: 1.15em;
    flex: none;
    inline-size: 1.15em;
}

/* "or" between the email flow and the provider list. The rules pick up
   currentColor, so the whole divider quiets down through --ink-mid. */
.auth-divider {
    align-items: center;
    color: var(--ink-mid);
    display: flex;
    font-family: var(--font-sans);
    font-size: var(--type-small);
    gap: var(--s-2);
    letter-spacing: 0.08em;
    margin-block: var(--s-1);
    text-transform: uppercase;
}

.auth-divider::before,
.auth-divider::after {
    border-block-start: 0.08em solid;
    content: '';
    flex: 1;
}

.auth-providers {
    display: grid;
    gap: var(--s-2);
}

/* The passkey button lives in its own <form> (needed for the
   ceremony), and .auth-form carries a margin-block-start for the
   cases where it follows other content directly. Grid items don't
   collapse margins, so left alone that extra margin-block-start
   made the divider-to-passkey gap wider than the gap between the
   rest of the provider buttons. */
.auth-providers .auth-form {
    margin-block-start: 0;
}

.auth-actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-3) var(--s-1);
    font-family: var(--font-sans);
    font-size: var(--type-small);
    margin-block-start: var(--s-2);
}

.auth-actions a {
    color: inherit;
    text-decoration-thickness: 0.08em;
    text-underline-offset: 0.16em;
}

.auth-help {
    color: var(--ink-mid);
    font-family: var(--font-sans);
    font-size: var(--type-small);
    margin-block: calc(-1 * var(--s-3)) 0;
}

/* Two of these ship empty on page load (the login providers row and
   the signup ceremony), waiting for passkey_controller.js's
   setStatus() to write into them. Visually hidden rather than
   display:none, because display:none would prune the live region
   from the accessibility tree before that first write, and the
   WebAuthn-unavailable and ceremony-failure messages are exactly
   that first write. Collapsing still matters: .auth-form is
   display:grid with gap, so an in-flow empty child costs a gap row
   even with no content. And :empty only matches because the
   template closes these <p>s explicitly, unlike the file's usual
   omitted-closing-tag style — an implicit close would leave a
   trailing whitespace text node behind. */
.auth-help:empty {
    block-size: 1px;
    clip-path: inset(50%);
    inline-size: 1px;
    overflow: hidden;
    position: absolute;
    white-space: nowrap;
}

.auth-fineprint {
    color: var(--ink-mid);
    font-size: var(--type-small);
    margin-block: var(--s-1) 0;
}

.auth-fineprint a {
    color: inherit;
}

/* ============================================================
   PODCAST ARCHIVE / EPISODES LISTING (SKR-152)

   Transcribed from docs/design/listing-spike/listing.css onto
   this file's own tokens and podcast-page conventions rather than
   the mockup's standalone --shell/--measure/font-face copies,
   which duplicated what .podcast-page and :root already own here.

   Latest and Start Here are built (analytics-ranked, not curated —
   see App\Service\PodcastStartHereRanking). Threads is not: the
   .podcast.html shape for per-episode topics and named episode
   groupings is still being designed elsewhere, and building against
   a guessed shape means redoing it the moment the real one lands.
   ============================================================ */

/* THE MEASURE TRAP, this page's own case of it. The site-wide reset
   at the top of this file — `* { max-inline-size: var(--measure) }`
   — caps every element by default, and .podcast-page only opts
   section/article/div/nav back out (line ~768). This page's record
   is <ol>/<li>, its picks are <ul>/<li>, its facts are <dl>/<dt>/<dd>,
   so all of them stayed capped at whatever --measure resolves to for
   .podcast-page--episodes (82ch): a 3-column pick grid computing 7px
   narrower than the 888px three columns need, orphaning the third
   card to a row of its own, and the record sitting 111px narrower
   than the head band and pagination it lines up with, so the section
   rule and the pager overhang the list. Same fix .podcast-episode-page
   already uses (line ~1356), scoped to this page's own wrapper. */
.podcast-archive :where(
    section, article, aside, div, nav, header, footer, figure,
    ol, ul, li, dl, dt, dd, p, span, label, a, button, time, form, fieldset
) {
    max-inline-size: none;
}

/* Found in review (blocks-render, via a peer agent, for .podcast-row__hits a
   specifically; the rest found auditing the same cause more broadly): the
   mockup this page was transcribed from opens with a global `a { color:
   inherit; }` reset (listing.css:103), which this port never carried over —
   wrongly assuming an equivalent already existed site-wide. It does not: no
   bare `a {}` rule exists anywhere else in this file. Every anchor this page
   introduces that does not separately set its own color — the row title, the
   feature/pick titles, the chapter-hit links, Clear search, the pager's
   non-current numbers, the Newer/Older steps — was therefore falling
   through to the UA stylesheet's a:link/a:visited (blue/purple), on every
   row, every page, both densities, and in search results. `.podcast-archive
   a`, not a bare `a{}`: scoped to this page's own tree rather than a
   site-wide change nobody asked for here. Author rules beat the user-agent
   stylesheet at equal specificity regardless of source order, so this needs
   no !important and nothing more specific than the a:link/a:visited pair it
   is overriding. Rules that DO set their own color
   (.podcast-pagination__list a[aria-current="page"], the hover/focus
   underline rules) are all higher-specificity than this and are unaffected.

   THE MEMBERSHIP CTA IS NOT IN THIS TREE. Publisher review, with a
   screenshot: "View Fallthrough memberships" still rendered purple on
   /episodes. This page has two top-level regions inside <main>, not one —
   <section class="podcast-archive"> (everything above, closes before the
   foot) and <footer class="podcast-archive-foot"> (the membership band) —
   and this rule only ever covered the first. Given its own rule below
   rather than folded into this one, so the two regions' anchor resets stay
   independently greppable. */
.podcast-archive a {
    color: inherit;
}

.podcast-archive-foot a {
    color: inherit;
}

/* --- THE HEAD BAND — h1 left, search right, one band, no growth. */
.podcast-archive__head {
    display: grid;
    gap: var(--s-1);
    margin-block-end: var(--s1);
}

.podcast-archive__head h1 {
    font-size: clamp(var(--s1), 3.6vw, var(--s2));
    line-height: 1.05;
    margin: 0;
}

@media (min-width: 46rem) {
    .podcast-archive__head {
        align-items: end;
        column-gap: var(--s1);
        grid-template-columns: minmax(0, 1fr) 21rem;
    }

    .podcast-archive__head h1 {
        grid-column: 1;
        grid-row: 1;
    }

    .podcast-archive-search {
        grid-column: 2;
        grid-row: 1;
    }
}

/* --- Search form: a plain GET, one stable parameter. Result URLs
   are ordinary URLs — shareable, cacheable, safe to hand to
   analytics. The label is visible because it names the SCOPE of
   the field, which the button does not. */
.podcast-archive-search {
    display: grid;
    gap: var(--s-3);
    grid-template-columns: minmax(0, 1fr) auto;
}

.podcast-archive-search__label {
    color: var(--ink-mid);
    font-family: var(--font-sans);
    font-size: var(--type-small);
    grid-column: 1 / -1;
    letter-spacing: 0.06em;
    text-transform: uppercase;
}

.podcast-archive-search__field {
    appearance: none;
    background: var(--background-color);
    border: 0.12em solid var(--tint-strong);
    border-radius: 0;
    color: inherit;
    font-family: var(--font-sans);
    font-size: var(--type-small);
    inline-size: 100%;
    min-inline-size: 0;
    padding: var(--s-3) var(--s-2);
}

.podcast-archive-search__field::placeholder {
    color: var(--ink-mid);
}

.podcast-archive-search__submit {
    background: var(--background-color-inverse);
    border: 0.12em solid var(--background-color-inverse);
    color: var(--color-inverse);
    cursor: pointer;
    font-family: var(--font-sans);
    font-size: var(--type-small);
    font-weight: 700;
    letter-spacing: 0.06em;
    padding-inline: var(--s-1);
    text-transform: uppercase;
}

.podcast-archive-search__submit:hover {
    background: transparent;
    border-color: currentColor;
    color: inherit;
}

/* --- Sections: the shipped episode page's heading register,
   verbatim — serif --s1 over a 0.12em rule. */
.podcast-archive-section {
    margin-block-start: var(--s1);
}

.podcast-archive-section h2 {
    font-size: var(--s1);
    line-height: 1.15;
    margin: 0;
}

.podcast-archive-section > h2,
.podcast-archive-section__head {
    border-block-end: 0.12em solid;
    margin-block-end: var(--s-1);
    padding-block-end: var(--s-4);
}

/* Heading and its one control on the same band — no count strip,
   no query echo, no sort state to read back. */
.podcast-archive-section__head {
    align-items: baseline;
    column-gap: var(--s0);
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    row-gap: var(--s-4);
}

/* SKR-405: "Clear search" measured 63.4 × 12.0 at 390px — the width
   already clears the WCAG 2.2 SC 2.5.8 floor, the height did not,
   with no padding of its own to carry it. */
.podcast-archive__reset {
    font-family: var(--font-sans);
    font-size: var(--type-small);
    letter-spacing: 0.06em;
    padding-block: var(--s-4);
    text-transform: uppercase;
    white-space: nowrap;
}

.podcast-archive__lede {
    margin: 0 0 var(--s-1);
    /* A fixed 68ch, not var(--measure): the mockup's --measure was a plain
       68ch at :root, but this page's own --measure is contextually 82ch
       (.podcast-page--episodes), so var(--measure) here was quietly reading
       a wider line length than designed. */
    max-inline-size: 68ch;
}

/* --- FACTS — the episode hero's <dl>, reused at listing scale:
   visually-hidden <dt>s, mono values, lining tabular figures. */
.podcast-facts {
    column-gap: var(--s-1);
    display: flex;
    flex-wrap: wrap;
    font-family: var(--font-sans);
    font-size: var(--type-small);
    margin: 0;
    row-gap: var(--s-4);
}

.podcast-facts > div {
    align-items: baseline;
    display: flex;
    gap: 0.8ch;
}

.podcast-facts dd {
    font-variant-numeric: lining-nums tabular-nums;
    margin: 0;
}

/* --- THE FEATURE — Latest, at size. Repeats row one of the
   record on purpose: Start here and Threads sit between the two
   appearances, so a reader who scrolls past this does not meet
   the newest episode again for several screens. */
/* Art column follows .podcast-episode-listing__art's own treatment
   (border, aspect-ratio, object-fit) rather than a second one — sized
   larger (7rem vs the row's 5.5rem) to carry the feature's own scale.
   --noart collapses to the text-only single column for the episode
   that has none, the same pattern .podcast-episode-listing already
   uses for the same reason. */
.podcast-feature {
    align-items: start;
    border: 0.12em solid;
    column-gap: var(--s0);
    display: grid;
    grid-template-columns: 7rem minmax(0, 1fr);
    padding: var(--s-1) var(--s0);
    position: relative;
}

.podcast-feature--noart {
    grid-template-columns: minmax(0, 1fr);
}

.podcast-feature__body {
    display: grid;
    gap: var(--s-3);
    min-inline-size: 0;
}

.podcast-feature:hover,
.podcast-feature:focus-within {
    background: var(--tint);
}

.podcast-feature__title {
    font-size: clamp(var(--s1), 2.6vw, var(--s2));
    line-height: 1.1;
    margin: 0;
    max-inline-size: 24ch;
}

.podcast-feature__title a,
.podcast-pick__title a {
    text-decoration: none;
}

.podcast-feature__title a::after,
.podcast-pick__title a::after {
    content: "";
    inset: 0;
    position: absolute;
}

.podcast-feature:hover .podcast-feature__title a,
.podcast-feature:focus-within .podcast-feature__title a,
.podcast-pick:hover .podcast-pick__title a,
.podcast-pick:focus-within .podcast-pick__title a {
    text-decoration: underline;
    text-decoration-thickness: 0.08em;
    text-underline-offset: 0.14em;
}

.podcast-feature__summary {
    margin: 0;
    max-inline-size: 68ch;
    /* Found verifying the Latest artwork at 390/320: the archive's episode
       summaries are authored text, not guaranteed to wrap on spaces (one
       compiled entry runs a "Socials:" line together with no separators —
       "Socials:WebsiteBlueskyThreadsX/Twitter..."), and without this a
       single unbroken run pushed the whole two-column card past the
       viewport. .podcast-row__blurb and .podcast-pick__note carry the
       same episode.summary and had the same gap. */
    overflow-wrap: anywhere;
}

/* --- THE PICKS — Start here. Three episodes ranked by first-
   90-day downloads (App\Command\PodcastStartHereRankingCommand),
   not hand-picked: there is no authored "why this one" line, so
   the note is the episode's own summary rather than invented
   editorial copy. */
.podcast-picks {
    display: grid;
    gap: var(--s-1);
    grid-template-columns: repeat(auto-fill, minmax(min(16rem, 100%), 1fr));
    list-style: none;
    margin: 0;
    padding: 0;
}

.podcast-pick {
    align-content: start;
    border: 0.12em solid;
    display: grid;
    gap: var(--s-3);
    padding: var(--s-2) var(--s-1);
    position: relative;
}

.podcast-pick:hover,
.podcast-pick:focus-within {
    background: var(--tint);
}

.podcast-pick__title {
    font-size: var(--s1);
    line-height: 1.12;
    margin: 0;
}

.podcast-pick__note {
    margin: 0;
    /* Carries episode.summaryExcerpt() — see .podcast-feature__summary. */
    overflow-wrap: anywhere;
}

/* --- The density toggle. Publisher review on the two-chip radiogroup
   this used to be: too small to read as a real control, and a
   segmented control that is always in one of its two states restates
   what the record directly beneath it already shows. One checkbox
   instead — native, visually hidden with the site's own
   .visually-hidden, its label swapped between the Hide/Show spans by
   :checked below, so the control names the action rather than the
   state. Still read purely by :has() on #density-compact:checked
   (unchanged id, see the record rules further down) — no JavaScript,
   no URL state, nothing to deep-link or persist. Sized to the 44x44px
   touch minimum: the old chip measured font-size --s-2 (8px) under
   0.15em padding, which is what read as a debug control. */
.podcast-record-toggle {
    display: inline-flex;
}

.podcast-record-toggle label {
    align-items: center;
    border: 0.12em solid var(--tint-strong);
    cursor: pointer;
    display: inline-flex;
    font-family: var(--font-sans);
    font-size: var(--type-small);
    justify-content: center;
    min-block-size: 44px;
    min-inline-size: 44px;
    padding: var(--s-3) var(--s0);
    white-space: nowrap;
}

.podcast-record-toggle input:checked + label {
    background: var(--background-color-inverse);
    border-color: var(--background-color-inverse);
    color: var(--color-inverse);
}

.podcast-record-toggle input:focus-visible + label {
    outline: max(2px, 0.15em) solid var(--color);
    outline-offset: 0.15em;
}

/* The label reads "Hide summaries" while they show and "Show
   summaries" once compact hides them — the action about to happen,
   not a restatement of the state the record beneath it already
   carries. Swapped by the same :checked the record layout reads,
   with no JavaScript. */
.podcast-record-toggle__off {
    display: none;
}

.podcast-record-toggle input:checked + label .podcast-record-toggle__on {
    display: none;
}

.podcast-record-toggle input:checked + label .podcast-record-toggle__off {
    display: inline;
}

/* --- THE RECORD — one row component, two densities.

   B's row is a contents-page line: hanging number, serif title,
   dot leader, ISO date, running time, all on one baseline. A's row
   carries two clamped lines of summary — the scent that matters
   because these titles are puns. Put one under the other naively
   and the leader bridges a gap that is no longer a gap, so each
   density takes the one horizontal guide it needs and neither
   takes both:

     compact       dot leader, no rules between rows.
     descriptions  hairline between rows, no leader; date and
                   running time hold a fixed tabular rail read
                   DOWN the column rather than across to it.

   Below 44rem neither guide applies: the row stacks and both
   densities take the same layout with hairlines. */
.podcast-record {
    list-style: none;
    margin: 0;
    padding: 0;
}

.podcast-row {
    align-items: baseline;
    column-gap: 1ch;
    display: grid;
    grid-template-columns: 3.8ch max-content minmax(0, 1fr);
    padding: var(--s-3);
    position: relative;
    row-gap: var(--s-4);
}

.podcast-row + .podcast-row {
    border-block-start: 0.08em solid var(--tint-strong);
}

.podcast-row:hover,
.podcast-row:focus-within {
    background: var(--tint);
}

.podcast-row__no {
    color: var(--ink-mid);
    font-family: var(--font-sans);
    font-size: var(--type-small);
    font-variant-numeric: lining-nums tabular-nums;
    grid-column: 1;
    grid-row: 1;
}

.podcast-row:hover .podcast-row__no,
.podcast-row:focus-within .podcast-row__no {
    color: inherit;
}

.podcast-row__line {
    grid-column: 2 / -1;
    grid-row: 1;
    min-inline-size: 0;
}

.podcast-row__title {
    text-decoration: none;
}

.podcast-row__title::after {
    content: "";
    inset: 0;
    position: absolute;
}

.podcast-row:hover .podcast-row__title,
.podcast-row:focus-within .podcast-row__title {
    text-decoration: underline;
    text-decoration-thickness: 0.08em;
    text-underline-offset: 0.14em;
}

/* Inline, and only for the people whose presence distinguishes one
   episode from another: a co-host credited on nearly every episode
   would otherwise repeat as a column of identical text. */
.podcast-row__with {
    color: var(--ink-mid);
    font-style: italic;
}

.podcast-row__leader {
    display: none;
}

.podcast-row__date,
.podcast-row__dur {
    color: var(--ink-mid);
    font-family: var(--font-sans);
    font-size: var(--type-small);
    font-variant-numeric: lining-nums tabular-nums;
}

.podcast-row__date {
    grid-column: 2;
    grid-row: 2;
}

.podcast-row__dur {
    grid-column: 3;
    grid-row: 2;
}

.podcast-row__blurb {
    color: var(--ink-mid);
    grid-column: 2 / -1;
    grid-row: 3;
    line-height: 1.4;
    margin: 0;
    /* Fixed 68ch (624px), per the mockup's measured intent — reviewed
       against var(--measure), which resolves to this page's 82ch (827px)
       and ran the clamped excerpt a third longer than designed. */
    max-inline-size: 68ch;
    /* See .podcast-feature__summary: same episode.summary, same unbroken-
       run risk, same fix. */
    overflow-wrap: anywhere;
}

/* Two lines is the budget in the record; results are exempt, see
   below. */
.podcast-row:not(.podcast-row--result) .podcast-row__blurb {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    overflow: hidden;
}

/* The compact density drops the summary at every width. */
.podcast-archive-section--record:has(#density-compact:checked) .podcast-row__blurb {
    display: none;
}

@media (min-width: 44rem) {
    /* --- Descriptions: block rows, hairlines, tabular rail --- */
    .podcast-row {
        grid-template-columns: 3.8ch minmax(0, 1fr) max-content max-content;
    }

    .podcast-row__line {
        grid-column: 2;
    }

    .podcast-row__date {
        grid-column: 3;
        grid-row: 1;
    }

    .podcast-row__dur {
        grid-column: 4;
        grid-row: 1;
        min-inline-size: 7ch;
        text-align: end;
    }

    .podcast-row__blurb {
        grid-row: 2;
    }

    /* --- Compact: single baseline, leader restored -------- */
    .podcast-archive-section--record:has(#density-compact:checked) {
        .podcast-row {
            grid-template-columns: 3.8ch minmax(0, max-content) minmax(2ch, 1fr) max-content max-content;
            padding-block: var(--s-4);
        }

        .podcast-row + .podcast-row {
            border-block-start: 0;
        }

        /* An empty box aligned to the text baseline: its border
           edge IS its synthesised baseline, so the dots land on
           the same line as the title and the date. */
        .podcast-row__leader {
            border-block-end: 0.12em dotted var(--tint-strong);
            display: block;
            grid-column: 3;
            grid-row: 1;
        }

        .podcast-row__date {
            grid-column: 4;
        }

        .podcast-row__dur {
            grid-column: 5;
        }
    }
}

/* --- RESULTS — the same row component, two things relaxed: the
   summary is unclamped so the marked phrase is visible wherever it
   falls, and the row can carry chapter hits. */
mark {
    background: var(--tint-strong);
    color: inherit;
    padding-inline: 0.15em;
}

/* Chapter hits: the strongest thing search can do for someone who
   half remembers an episode — say WHERE in it the phrase occurs.
   These sit above the row's stretched link. */
.podcast-row__hits {
    display: grid;
    gap: var(--s-4);
    grid-column: 2 / -1;
    list-style: none;
    margin: var(--s-4) 0 0;
    padding: 0;
}

.podcast-row__hits a {
    align-items: baseline;
    border-inline-start: 0.14em solid var(--tint-strong);
    display: flex;
    flex-wrap: wrap;
    font-size: var(--type-small);
    gap: 0 1ch;
    padding-inline-start: var(--s-3);
    position: relative;
    text-decoration: none;
    z-index: 1;
}

.podcast-row__hits a:hover .podcast-row__hit-title,
.podcast-row__hits a:focus-visible .podcast-row__hit-title {
    text-decoration: underline;
    text-decoration-thickness: 0.08em;
    text-underline-offset: 0.16em;
}

.podcast-row__hit-time {
    color: var(--ink-mid);
    font-family: var(--font-sans);
    font-size: var(--type-small);
    font-variant-numeric: lining-nums tabular-nums;
    letter-spacing: -0.02em;
}

.podcast-row__hit-title {
    min-inline-size: 0;
}

/* Extended-cut chapters are indexed and shown — they ship inside
   the published episode description, so they are already public
   in every podcast app. The tag is not a label on the obvious: an
   untagged hit is in the free cut, and this is the only way a
   reader learns before clicking that the chapter they just found
   is in the members' edition. */
.podcast-tag {
    border: 0.1em solid var(--tint-strong);
    /* Signal register (SKR-402): see .listing__tag, which names this
       rule's own reasoning as its echo. */
    color: var(--color);
    font-family: var(--font-sans);
    font-size: var(--type-small);
    padding: 0.05em 0.4em;
    white-space: nowrap;
}

/* --- No results: the guidance names the index's actual scope,
   which is the one thing a reader cannot guess and the one thing
   that explains the failure. */
.podcast-archive__empty {
    max-inline-size: 68ch;
}

.podcast-archive__empty p {
    margin-block: 0 var(--s-1);
}

/* --- PAGINATION — numbered, and each number carries the span of
   episodes it holds. "3" tells you nothing you can act on; "3 /
   E38–E19" lets someone hunting E24 land in one click. */
.podcast-pagination {
    align-items: start;
    border-block-start: 0.12em solid;
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-1) var(--s0);
    justify-content: space-between;
    margin-block-start: var(--s1);
    padding-block-start: var(--s-1);
}

.podcast-pagination__list {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-3);
    list-style: none;
    margin: 0;
    padding: 0;
}

/* SKR-405: the bare-numeral pill measured 21.9 × 23.7 at 390px, a
   WCAG 2.2 SC 2.5.8 failure on both axes with the neighbouring pill
   ~5px away — no spacing exception at that gap. --s-4/--s-2 clears
   24px on both axes; re-measure rather than trusting this arithmetic
   if either padding or the type scale moves again. */
.podcast-pagination__list a {
    border: 0.12em solid var(--tint-strong);
    display: grid;
    font-family: var(--font-sans);
    font-variant-numeric: lining-nums tabular-nums;
    justify-items: center;
    line-height: 1.25;
    padding: var(--s-4) var(--s-2);
    text-decoration: none;
}

.podcast-pagination__list a:hover,
.podcast-pagination__list a:focus-visible {
    border-color: currentColor;
}

.podcast-pagination__n {
    font-size: var(--type-small);
    font-weight: 700;
}

.podcast-pagination__span {
    color: var(--ink-mid);
    font-size: var(--type-small);
    letter-spacing: 0.02em;
}

.podcast-pagination__list a[aria-current="page"] {
    background: var(--background-color-inverse);
    border-color: var(--background-color-inverse);
    color: var(--color-inverse);
}

.podcast-pagination__list a[aria-current="page"] .podcast-pagination__span {
    color: inherit;
    opacity: 0.7;
}

/* Newer and Older rather than Previous and Next: the axis is time.
   Neither renders when it would point at nothing, so there is no
   rule here for a disabled step — there is no such thing. */
/* SKR-405: "Older →" measured 37.0 × 16.0 at 390px — the width clears
   24px but the height, carried by only a top padding, did not. A
   matching bottom padding clears the WCAG 2.2 SC 2.5.8 floor without
   the asymmetry the single-sided padding read as. */
.podcast-pagination__step {
    font-family: var(--font-sans);
    font-size: var(--type-small);
    letter-spacing: 0.06em;
    padding-block: 0.5em;
    text-transform: uppercase;
    white-space: nowrap;
}

/* --- THE FOOT — the membership band, in the block-code register
   (tint fill, strong left border), matching the episode page's
   unconditional ask. */
.podcast-archive-foot {
    margin-block-start: var(--s1);
}

.podcast-archive-support {
    background: var(--tint);
    border-inline-start: 0.14em solid var(--tint-strong);
    padding: var(--s-1) var(--s0);
}

.podcast-archive-support h2 {
    font-size: var(--s0);
    font-weight: 700;
    margin: 0 0 var(--s-3);
}

.podcast-archive-support__lede {
    margin: 0;
    max-inline-size: 68ch;
}

.podcast-archive-support__cta {
    font-family: var(--font-sans);
    font-size: var(--type-small);
    font-weight: 700;
    margin: var(--s-3) 0 0;
}

/* At the narrowest widths the tracked mono chrome is what breaks
   first; drop the tracking rather than let anything past the edge. */
@media (max-width: 24rem) {
    .podcast-archive-search__label,
    .podcast-archive__reset,
    .podcast-pagination__step {
        letter-spacing: 0.02em;
    }

    .podcast-row {
        column-gap: 0.6ch;
    }
}
