/* =============================================================
   ARTICLE PAGE - the chrome around an article.

   Everything on blog/show and news/show that is NOT the article
   body: the hero, the listen bar, the chapters rail, the right
   rail, the author block and the recommendation grids.

   The article body itself is styled by article-content.css,
   which is shared with the editor and the preview. That file
   reads ONE variable, --text-main, and it is defined here so
   the body inherits this page's palette. Do not restate body
   rules here -- that is how "preview" stopped matching the
   published page the last time.

   Both blog/show and news/show load this file. They used to
   carry ~600 lines of near-identical inline <style> each, which
   is how one page ended up green and the other purple.
   ============================================================= */

/* -------------------------------------------------------------
   Palette

   Two ramps, no third. Neutral carries the page; cosmic purple
   is the only accent, and it appears on links, the active
   chapter, the play button and the progress line -- nowhere
   else. Category colours are deliberately NOT used as fills
   here: three arbitrary hues in the meta line were the loudest
   thing on the old page.
   ------------------------------------------------------------- */
.article-page {
    /* Neutral */
    --n-base:      #C4C4C4;   /* hairlines, dividers, disabled */
    --n-secondary: #FFFFFF;   /* surfaces: cards, the dock, the ad frame */
    --n-ground:    #F8F8F8;   /* the page itself, one step off the surfaces */
    --n-tertiary:  #3E3E3E;   /* body text */
    --n-wash:      #F5F5F5;   /* the one recessed fill */

    /* Cosmic Purple - Hewa+ */
    --p-base:      #8063FF;   /* the accent */
    --p-secondary: #C2BAFC;   /* hover, soft fills, focus ring */
    --p-tertiary:  #1A1637;   /* headline, and the dark surface */

    /* Derived */
    --rule:     rgba(62, 62, 62, 0.14);
    --muted:    #6B6B72;
    --text-main: var(--n-tertiary);   /* read by article-content.css */

    /* One corner radius for every framed thing on the page -- covers, ads,
       video, thumbnails, merch, the paywall.
     *
     * Pixels, not a percentage. A percentage radius resolves against BOTH
     * axes independently, so the same rule gave a 316x566 video frame 6px
     * across and 11px down, and stretched the 786x145 author card into a
     * squashed pill. Every corner on the page was a different shape. One
     * fixed value is the only way they match.
     *
     * Pills and avatars keep their own 999px / 50%. */
    --radius: 8px;

    /* The public nav is sticky at 3.8rem. Anything that pins or
       is scrolled to has to clear it, or the chapter you clicked
       arrives underneath the header. */
    --nav-h: 3.8rem;
    --pin-top: calc(var(--nav-h) + 24px);

    /* The measure. Wider than this and the eye loses the line
       return on a long read. */
    --measure: 680px;

    background: var(--n-ground);
    color: var(--n-tertiary);
    font-family: 'Raleway', 'Google Sans', sans-serif;
}

/* Black and white, not a tinted near-black. The purple is the
   accent; the moment the page ground is also purple, the accent
   stops being one. */
html.dark .article-page {
    --n-secondary: #262626;   /* surfaces lift off the ground */
    --n-ground:    #1D1D1D;   /* not pure black */
    --n-tertiary:  #F5F5F5;
    --n-wash:      #141414;   /* the one recessed fill, off pure black */
    --n-base:      #3E3E3E;

    /* #8063FF on black is under 4.5:1. The light purple is the
       accent in dark mode. */
    --p-base:      #C2BAFC;
    --p-tertiary:  #FFFFFF;

    --rule:  rgba(255, 255, 255, 0.16);
    --muted: #9B9BA6;
}

/* The layout owns the page background, not the body element --
   the public layout paints <body> for every other page. */
body:has(.article-page) {
    background: #F8F8F8;
}

html.dark body:has(.article-page) {
    background: #1D1D1D;
}

/* Why the chapters would not stay pinned.
 *
 * The public layout wraps every page in <main class="overflow-x-hidden">.
 * `overflow-x: hidden` makes that element a SCROLL CONTAINER, and a sticky
 * child resolves its offsets against the nearest scroll container -- which in
 * that case is a container that never scrolls. So the rail unpinned the moment
 * it left the top of the viewport and rode the page down.
 *
 * `overflow: clip` clips exactly the same way without establishing a scroll
 * container, so sticky goes back to resolving against the viewport. Scoped to
 * this page with :has() so no other layout changes behaviour. */
main:has(.article-page) {
    overflow-x: clip;
    overflow-y: visible;
}

/* -------------------------------------------------------------
   Hero
   ------------------------------------------------------------- */
/* The hero spans the whole grid, not the article measure.
 *
 * Constrained to 680px a headline of any length broke to four lines and read
 * as a narrow column of shouting. It has the full width between the chapter
 * rail and the promo rail to work with, and uses it -- the same 1360px the
 * grid below is centred in, so the two edges line up.
 *
 * The measure exists for BODY text, where a long line loses the eye on the
 * return sweep. A display headline is scanned, not read line by line, and does
 * not have that problem. */
.article-hero {
    max-width: 1360px;
    margin: 0 auto;
    padding: 56px 24px 0;
    text-align: center;
}

.article-eyebrow {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 6px 10px;
    font-size: 0.875rem;
    color: var(--muted);
    margin-bottom: 28px;
}

/* Separators are drawn between items rather than authored as
   loose spans. A literal bullet in the markup ends up orphaned
   at the start of a line when the row wraps. */
.article-eyebrow > * + *::before {
    content: '\00b7';
    margin-right: 10px;
    color: var(--n-base);
}

.article-eyebrow a {
    color: var(--p-base);
    text-decoration: none;
}

.article-eyebrow a:hover {
    text-decoration: underline;
    text-underline-offset: 3px;
}

/* The eyebrow and the byline stay near the middle -- they are short lines and
   look adrift stretched across 1300px. Only the headline takes the full span. */
.article-eyebrow,
.article-byline {
    max-width: var(--measure);
    margin-left: auto;
    margin-right: auto;
}

.article-title {
    font-size: clamp(2rem, 5.2vw, 3.5rem);
    font-weight: 700;
    line-height: 1.08;
    letter-spacing: -0.022em;
    color: var(--p-tertiary);
    margin: 0 0 32px;
    overflow-wrap: anywhere;
    hyphens: auto;
}

/* Amharic headlines have taller glyphs and no useful hyphenation. */
html[lang="am"] .article-title,
.article-title:lang(am) {
    line-height: 1.25;
    hyphens: none;
}

/* --- The compact byline ------------------------------------- */
.article-byline {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: 8px 14px;
    font-size: 0.9rem;
    color: var(--muted);
}

.byline-person {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    text-decoration: none;
    color: var(--n-tertiary);
    font-weight: 600;
}

.byline-person:hover {
    color: var(--p-base);
}

.byline-avatar {
    width: 28px;
    height: 28px;
    border-radius: 50%;
    object-fit: cover;
    background: var(--n-wash);
    flex-shrink: 0;
}

.byline-avatar--initial {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--p-tertiary);
    color: #fff;
    font-size: 0.75rem;
    font-weight: 700;
}

html.dark .byline-avatar--initial {
    background: var(--p-base);
    color: #1A1637;
}

.byline-with {
    color: var(--muted);
    font-weight: 400;
}

/* Collaborators are credited, not co-billed. Smaller, so the writer of record
   still reads as the writer of record. */
.byline-collabs {
    display: inline-flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px 10px;
    font-size: 0.8125rem;
}

.byline-collabs .byline-avatar {
    width: 22px;
    height: 22px;
}

.byline-collabs .byline-person {
    font-weight: 500;
    color: var(--muted);
}

.byline-collabs .byline-person:hover {
    color: var(--p-base);
}

/* -------------------------------------------------------------
   Listen bar

   One row, hairline above and below: listen on the left, like
   and share on the right. These were three separate pill
   buttons floating under the headline before, which read as a
   toolbar rather than as part of the article.
   ------------------------------------------------------------- */
.listen-bar {
    max-width: var(--measure);
    margin: 44px auto 0;
    padding: 12px 24px 0;
    border-top: 1px solid var(--rule);
    border-bottom: 1px solid var(--rule);
}

.listen-row {
    display: flex;
    align-items: center;
    gap: 16px;
    padding-bottom: 12px;
}

.listen-side {
    display: flex;
    align-items: center;
    gap: 14px;
}

.listen-side--end {
    margin-left: auto;
}

.listen-play {
    width: 34px;
    height: 34px;
    border-radius: 50%;
    border: 1px solid var(--rule);
    background: transparent;
    color: var(--p-base);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    flex-shrink: 0;
    transition: background 0.15s, border-color 0.15s, color 0.15s;
}

.listen-play:hover:not(:disabled) {
    background: var(--p-base);
    border-color: var(--p-base);
    color: #fff;
}

.listen-label {
    font-size: 0.9rem;
    font-weight: 500;
    color: var(--n-tertiary);
    background: transparent;
    border: none;
    padding: 0;
    cursor: pointer;
    white-space: nowrap;
}

.listen-time {
    font-size: 0.85rem;
    color: var(--muted);
    font-variant-numeric: tabular-nums;
}

.listen-rate {
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--muted);
    background: transparent;
    border: 1px solid var(--rule);
    border-radius: 999px;
    padding: 2px 9px;
    cursor: pointer;
}

.listen-rate:hover {
    color: var(--p-base);
    border-color: var(--p-base);
}

.listen-transport {
    display: inline-flex;
    align-items: center;
    gap: 10px;
}

.listen-icon-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    padding: 0;
    background: transparent;
    border: none;
    border-radius: 999px;
    color: var(--muted);
    font-size: 0.8rem;
    cursor: pointer;
    transition: color 0.15s, background 0.15s;
}

.listen-icon-btn:hover {
    color: var(--p-base);
    background: var(--n-wash);
}

.listen-bar.is-unavailable .listen-play,
.listen-bar.is-unavailable .listen-label {
    color: var(--muted);
    cursor: default;
}

/* --- Progress -------------------------------------------------
 *
 * One 2px line doing two jobs. On a recording the range input sits over it and
 * scrubs; on synthesised speech there is no timeline to scrub, so the input is
 * hidden and the line is a read-only indicator. Same markup either way, which
 * is what keeps the bar and the dock from needing separate treatments.
 * ------------------------------------------------------------- */
.listen-progress {
    position: relative;
    height: 2px;
    background: var(--rule);
    overflow: visible;
}

.listen-progress-fill {
    position: absolute;
    inset: 0;
    background: var(--p-base);
    transform: scaleX(0);
    transform-origin: 0 50%;
    transition: transform 0.2s linear;
}

.listen-seek {
    position: absolute;
    inset: -7px 0;
    width: 100%;
    height: 16px;
    margin: 0;
    opacity: 0;
    cursor: pointer;
    accent-color: var(--p-base);
}

/* The input stays invisible. It is the hit area, nothing else.
 *
 * It used to go to opacity: 1 on hover, which did not reveal "the handle" --
 * it revealed the browser's own range control, thumb and track, unstyled, in
 * whatever grey the platform draws. In the dock that thumb also sat half
 * outside the rounded bottom edge and was sliced off by the overflow clip.
 *
 * The handle below is drawn instead, so it is the right size and the right
 * colour, and it still only appears when someone is aiming at the bar. */
.listen-progress-handle {
    position: absolute;
    top: 50%;
    left: calc(var(--listen-pos, 0) * 100%);
    width: 11px;
    height: 11px;
    border-radius: 999px;
    background: var(--p-base);
    box-shadow: 0 0 0 2px var(--n-secondary);
    transform: translate(-50%, -50%) scale(0);
    transition: transform 0.15s ease;
    pointer-events: none;
}

.listen-progress:hover .listen-progress-handle,
.listen-seek:focus-visible ~ .listen-progress-handle,
.listen-seek:active ~ .listen-progress-handle {
    transform: translate(-50%, -50%) scale(1);
}

/* Not seekable -- speech synthesis has no timeline to scrub -- so there is
   nothing to grab and the bar is a read-only indicator. */
.listen-progress:has(.listen-seek[hidden]) .listen-progress-handle {
    display: none;
}

/* --- The floating dock ---------------------------------------
 *
 * Appears once a reading is under way and the inline bar has scrolled away.
 * Bottom-docked: the layout already has a sticky header, and every player a
 * reader has used puts transport at the bottom edge.
 * ------------------------------------------------------------- */
.listen-dock {
    position: fixed;
    left: 50%;
    bottom: 20px;
    z-index: 70;
    width: min(680px, calc(100vw - 32px));
    transform: translateX(-50%);
    background: var(--n-secondary);
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
    overflow: hidden;
}

html.dark .listen-dock {
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.6);
}

.listen-dock.is-open {
    animation: listen-dock-in 0.22s ease-out;
}

@keyframes listen-dock-in {
    from { transform: translate(-50%, 16px); opacity: 0; }
    to   { transform: translate(-50%, 0);    opacity: 1; }
}

.listen-dock-inner {
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 10px 14px;
}

.listen-play--dock {
    width: 38px;
    height: 38px;
    border: none;
    background: var(--p-base);
    color: #fff;
    flex-shrink: 0;
}

html.dark .listen-play--dock {
    color: #1A1637;
}

.listen-play--dock:hover:not(:disabled) {
    background: var(--p-base);
    opacity: 0.88;
    color: #fff;
}

html.dark .listen-play--dock:hover:not(:disabled) {
    color: #1A1637;
}

/* Scrolls back to the passage being read -- someone who wandered off to check
   something else should not have to hunt for their place. */
.listen-dock-title {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 1px;
    background: transparent;
    border: none;
    padding: 0;
    text-align: left;
    cursor: pointer;
}

.listen-dock-eyebrow {
    font-size: 0.625rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.09em;
    color: var(--p-base);
}

.listen-dock-name {
    font-size: 0.875rem;
    font-weight: 600;
    color: var(--n-tertiary);
    max-width: 100%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.listen-dock-title:hover .listen-dock-name {
    color: var(--p-base);
}

.listen-dock-transport {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-shrink: 0;
}

.listen-time--dock {
    font-size: 0.8rem;
    white-space: nowrap;
}

/* Stop, not just dismiss. Pause keeps your place and leaves the dock up; this
   ends the reading and clears the highlight. One control doing both is how
   people lose their position trying to get rid of a bar. */
.listen-dock-close {
    margin-left: 4px;
    border-left: 1px solid var(--rule);
    border-radius: 0;
    padding-left: 12px;
    width: auto;
}

/* Inset from the bottom edge and the rounded corners.
 *
 * Flush at the bottom, the bar ran into the 8px radius and had its ends
 * sliced off, and the handle -- which is centred on the track -- lost its
 * lower half to the same overflow clip. Six pixels of clearance is enough
 * for the handle to sit whole inside the dock, and the bar reads better for
 * not being jammed into the corner. */
.listen-progress--dock {
    height: 3px;
    margin: 0 14px 8px;
    border-radius: 999px;
}

.listen-progress--dock .listen-progress-fill {
    border-radius: 999px;
}

@media (max-width: 760px) {
    .listen-dock {
        left: 0;
        right: 0;
        bottom: 0;
        width: 100%;
        transform: none;
        border-radius: var(--radius) var(--radius) 0 0;
        border-bottom: none;
    }

    @keyframes listen-dock-in {
        from { transform: translateY(100%); }
        to   { transform: translateY(0); }
    }

    .listen-dock-inner {
        gap: 10px;
    }

    /* Skip and speed go; play, the title and stop are what a thumb needs. */
    .listen-dock-transport .listen-icon-btn:not(.listen-dock-close),
    .listen-dock-transport .listen-rate,
    .listen-time--dock {
        display: none;
    }
}

@media (prefers-reduced-motion: reduce) {
    .listen-dock.is-open {
        animation: none;
    }

    .listen-progress-fill {
        transition: none;
    }
}

.listen-action {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    background: transparent;
    border: none;
    padding: 0;
    font-size: 0.9rem;
    font-weight: 500;
    color: var(--muted);
    cursor: pointer;
    text-decoration: none;
    transition: color 0.15s;
}

.listen-action:hover {
    color: var(--p-base);
}

.listen-action.is-liked {
    color: var(--p-base);
}

.listen-action span {
    font-variant-numeric: tabular-nums;
}

/* Where the reading has got to.
 *
 * By dimming everything else rather than decorating the current paragraph.
 * The first attempt was a 2px rule in the left margin, which on a column that
 * starts at the page edge sat half over the text and read as a rendering
 * fault rather than as a marker.
 *
 * Recessing the rest is what a reader following along actually wants -- their
 * eye goes to the one paragraph at full contrast -- and it needs no extra
 * markup inside .article-body, which matters because that HTML is served
 * verbatim to the mobile apps. */
.article-body.is-narrating > *,
.article-body.is-narrating li {
    opacity: 0.38;
    transition: opacity 0.35s ease, background-color 0.35s ease;
}

.article-body.is-narrating .is-speaking,
.article-body.is-narrating .is-speaking * {
    opacity: 1;
}

.article-body .is-speaking {
    position: relative;
    background-color: color-mix(in srgb, var(--p-base) 10%, transparent);
    box-shadow: 0 0 0 8px color-mix(in srgb, var(--p-base) 10%, transparent);
    border-radius: 3px;
}

/* Ads, merch and the related cards are not being read, and fading them in and
   out as the voice moves down the page is distracting rather than helpful. */
.article-body.is-narrating .ad-slot,
.article-body.is-narrating .merch-mini-card,
.article-body.is-narrating .related-inline,
.article-body.is-narrating .article-cover {
    opacity: 1;
}

/* Toast, for "link copied" -- the public layout carries no toast component. */
.article-flash {
    position: fixed;
    left: 50%;
    bottom: 32px;
    transform: translateX(-50%);
    z-index: 90;
    padding: 10px 20px;
    background: var(--p-tertiary);
    color: #fff;
    font-size: 0.875rem;
    font-weight: 600;
    border-radius: 999px;
    opacity: 1;
    transition: opacity 0.4s;
}

html.dark .article-flash {
    background: var(--p-base);
    color: #1A1637;
}

.article-flash.is-out {
    opacity: 0;
}

/* -------------------------------------------------------------
   Reading progress
   ------------------------------------------------------------- */
.reading-progress {
    position: fixed;
    top: 0;
    left: 0;
    height: 2px;
    width: 100%;
    transform: scaleX(0);
    transform-origin: 0 50%;
    background: var(--p-base);
    z-index: 60;
    pointer-events: none;
}

/* -------------------------------------------------------------
   The three-column grid

   Chapters | article | rail. The article column is centred in
   the viewport, not in the remaining space, so the measure does
   not shift when an article has no chapters.
   ------------------------------------------------------------- */
.article-grid {
    display: grid;
    grid-template-columns:
        minmax(0, 1fr)
        minmax(0, var(--measure))
        minmax(0, 1fr);
    column-gap: 48px;
    max-width: 1360px;
    margin: 0 auto;
    padding: 40px 24px 0;

    /* NOT align-items: start.
     *
     * A sticky element can only travel inside its own containing block, and
     * `start` shrinks each column to its content -- so the chapters unpinned
     * after about two hundred pixels and scrolled away with the page. The
     * columns have to span the full row height for sticky to mean anything. */
    align-items: stretch;
}

.article-column {
    grid-column: 2;
    min-width: 0;
}

.chapters-column {
    grid-column: 1;
    justify-self: end;
    width: 100%;
    max-width: 300px;
}

.rail-column {
    grid-column: 3;
    justify-self: start;
    width: 100%;
    max-width: 320px;
}

/* -------------------------------------------------------------
   Chapters
   ------------------------------------------------------------- */
.chapters {
    position: sticky;
    top: var(--pin-top);
    font-size: 0.8125rem;
    line-height: 1.4;
}

.chapters-heading {
    font-size: 0.6875rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.09em;
    color: var(--muted);
    padding-bottom: 10px;
    margin-bottom: 12px;
    border-bottom: 1px solid var(--rule);
}

.chapters-list {
    display: flex;
    flex-direction: column;
    gap: 12px;
    list-style: none;
    margin: 0;
    padding: 0;
    max-height: calc(100vh - var(--pin-top) - 80px);
    overflow-y: auto;
    scrollbar-width: thin;
    scrollbar-color: var(--n-base) transparent;
}

.chapters-list::-webkit-scrollbar {
    width: 6px;
}
.chapters-list::-webkit-scrollbar-track {
    background: transparent;
}
.chapters-list::-webkit-scrollbar-thumb {
    background: var(--n-base);
    border-radius: 3px;
}

.chapters-list a {
    display: block;
    color: var(--muted);
    text-decoration: none;
    border-left: 2px solid transparent;
    padding-left: 12px;
    margin-left: -14px;
    transition: color 0.15s, border-color 0.15s;
}

.chapters-list a:hover {
    color: var(--n-tertiary);
}

.chapters-list a.is-active {
    color: var(--p-base);
    border-left-color: var(--p-base);
    font-weight: 600;
}

.ad-slot {
    width: 100%;
    max-width: calc(var(--ad-w) * 1px);
    margin: 24px auto;
    display: flex;
    flex-direction: column;
    align-items: center;
}

.ad-slot-label {
    font-size: 0.65rem;
    text-transform: uppercase;
    color: var(--muted);
    margin-bottom: 6px;
    /* See article-content.css: the slot and the ad inside it can be different
       widths now, so the label follows the ad rather than the slot edge. */
    align-self: center;
}

/* Width deliberately not forced either: the component carries w-full with its
   own max-width, and restating it here only re-opens the same hole. */
.ad-slot .ad-banner-wrapper {
    margin: 0 !important;
}

/* The h2 an active h3 sits under. Marked, but not competing with the
   sub-chapter the reader is actually in. */
.chapters-list a.is-ancestor {
    color: var(--n-tertiary);
    border-left-color: var(--n-base);
    font-weight: 600;
}

/* Sub-chapters: h3s, indented under the h2 above them. */
.chapters-list .is-sub a {
    padding-left: 26px;
    font-size: 0.78125rem;
    opacity: 0.85;
}

/* The slot under the chapters.
 *
 * Hidden by default and shown only when the window is tall enough to have
 * spare room below the list. CSS cannot ask "is there space left in this
 * column", so viewport height is the honest proxy -- on a short window the
 * chapters need all of it. */
.chapters-ad {
    display: none;
}

@media (min-height: 820px) {
    .chapters-ad {
        display: block;
        margin-top: 28px;
        padding-top: 20px;
        border-top: 1px solid var(--rule);
    }

    /* The list gives up the height the ad takes, rather than the two fighting
       for the same space and the column overflowing. */
    .chapters-ad ~ * {
        min-height: 0;
    }

    .chapters:has(.chapters-ad) .chapters-list {
        max-height: calc(100vh - var(--pin-top) - 700px);
    }
}

/* The mobile form: a disclosure above the article. A table of
   contents that lives at the BOTTOM of a phone screen is a
   table of contents nobody uses. */
.chapters-mobile {
    display: none;
    max-width: var(--measure);
    margin: 0 auto;
    padding: 20px 24px 0;
}

.chapters-mobile > summary {
    cursor: pointer;
    list-style: none;
    font-size: 0.75rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.09em;
    color: var(--muted);
    padding: 12px 0;
    border-bottom: 1px solid var(--rule);
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.chapters-mobile > summary::-webkit-details-marker {
    display: none;
}

.chapters-mobile[open] > summary i {
    transform: rotate(180deg);
}

.chapters-mobile > summary i {
    transition: transform 0.2s;
}

.chapters-mobile .chapters-list {
    padding: 16px 0;
    max-height: none;
}

/* The heading a chapter link scrolls to has to clear the sticky
   nav. Doing it in CSS rather than with a JS scroll offset means
   a deep link pasted into the address bar lands correctly too. */
.article-body h2,
.article-body h3 {
    scroll-margin-top: var(--pin-top);
}

/* The chapter you just clicked.
 *
 * The highlight is on .ch-text -- an inline span ArticleOutline wraps the
 * heading's words in -- not on the heading box, so it hugs the words the way a
 * followed link highlights the passage it landed on. A background on the
 * heading itself paints the whole column and reads as a banner.
 *
 * box-decoration-break: clone is what makes a heading that wraps onto two
 * lines get the padding on BOTH lines instead of one long ragged block.
 *
 * It fades on its own; a permanent highlight is still sitting there three
 * chapters later. */
.article-body .ch-text {
    border-radius: 4px;
    box-decoration-break: clone;
    -webkit-box-decoration-break: clone;
    transition: background-color 0.35s ease, color 0.35s ease;
}

.article-body h2.is-targeted .ch-text,
.article-body h3.is-targeted .ch-text {
    background-color: var(--p-secondary);
    /* Fixed dark ink, not --text-main: the band is light purple in both
       themes, and the dark theme's near-white text vanishes on it. */
    color: #1A1637 !important;
    padding: 0.08em 0.24em;
    margin: 0 -0.24em;
}

/* -------------------------------------------------------------
   Cover image

   Square. The rounded 24px box was rounding the LETTERBOX on a
   wide image, so the corners were transparent and the picture
   itself stayed square -- rounded on a phone, square on a
   desktop, and nobody could say why.
   ------------------------------------------------------------- */
.article-cover {
    margin: 0 0 12px;
}

.article-cover img {
    display: block;
    width: 100%;
    height: auto;
    max-height: 560px;
    object-fit: cover;
    border-radius: var(--radius);
}

.article-cover-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 280px;
    background: var(--n-wash);
    color: var(--p-base);
    font-size: 5rem;
    border-radius: var(--radius);
}

.article-cover + .caption-true {
    display: block;
    margin-bottom: 40px;
}

/* -------------------------------------------------------------
   Inline embeds

   article-content.css gives .article-body iframe a 20px radius
   and a drop shadow. That file is shared with the editor and the
   preview, so it is overridden here rather than edited: a video
   in the body now matches the cover, the ads and the rail.
   ------------------------------------------------------------- */
.article-page .article-body iframe {
    border-radius: var(--radius) !important;
    box-shadow: none !important;
}

/* -------------------------------------------------------------
   Inline related article

   One interruption grammar for the body, shared with the merch
   mini-card: a hairline block, no shadow, no lift.
   ------------------------------------------------------------- */
.related-inline {
    display: flex;
    align-items: center;
    gap: 18px;
    margin: 40px 0;
    padding: 18px 0;
    border-top: 1px solid var(--rule);
    border-bottom: 1px solid var(--rule);
    text-decoration: none;
    color: inherit;
}

.related-inline img {
    width: 96px;
    height: 72px;
    object-fit: cover;
    background: var(--n-wash);
    border-radius: var(--radius);
    flex-shrink: 0;
}

.related-inline-eyebrow {
    font-size: 0.6875rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.09em;
    color: var(--p-base);
    margin-bottom: 6px;
}

.related-inline-title {
    font-size: 1.0625rem;
    font-weight: 600;
    line-height: 1.35;
    color: var(--n-tertiary);
}

.related-inline:hover .related-inline-title {
    color: var(--p-base);
}

/* -------------------------------------------------------------
   Soft paywall
   ------------------------------------------------------------- */
.paywall {
    position: relative;
    min-height: 460px;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
}

.paywall-teaser {
    position: absolute;
    inset: 0;
    overflow: hidden;
    pointer-events: none;
    user-select: none;
    font-size: 1.125rem;
    line-height: 1.7;
    color: var(--n-tertiary);
    mask-image: linear-gradient(to bottom, black 0, black 90px, transparent 240px);
    -webkit-mask-image: linear-gradient(to bottom, black 0, black 90px, transparent 240px);
}

.paywall-card {
    position: relative;
    z-index: 2;
    text-align: center;
    padding: 40px 32px;
    background: var(--n-secondary);
    border: 1px solid var(--rule);
    border-radius: var(--radius);
}

.paywall-icon {
    font-size: 1.5rem;
    color: var(--p-base);
    margin-bottom: 20px;
    display: block;
}

.paywall-card h2 {
    font-size: 1.625rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--p-tertiary);
    margin: 0 0 10px;
}

.paywall-card > p {
    font-size: 1rem;
    line-height: 1.6;
    color: var(--muted);
    margin: 0 0 28px;
}

.paywall-cta {
    display: inline-block;
    padding: 14px 32px;
    background: var(--p-base);
    color: #fff;
    font-weight: 700;
    letter-spacing: 0.02em;
    text-decoration: none;
    border-radius: 999px;
    transition: opacity 0.15s;
}

.paywall-cta:hover {
    opacity: 0.88;
}

.paywall-note {
    margin-top: 16px;
    font-size: 0.875rem;
    color: var(--muted);
}

.paywall-note a {
    color: var(--p-base);
    text-decoration: underline;
    text-underline-offset: 3px;
}

/* -------------------------------------------------------------
   Right rail

   Pinned, and drifting slower than the article. The drift is
   applied by article-page.js as a transform on .rail-drift; the
   sticky viewport and the overflow clip are here so the column
   still behaves if the script never runs.
   ------------------------------------------------------------- */
.rail {
    position: sticky;
    top: var(--pin-top);
    max-height: calc(100vh - var(--pin-top) - 24px);
    overflow: hidden;
}

.rail-drift {
    display: flex;
    flex-direction: column;
    gap: 32px;
    will-change: transform;
}

.rail-heading {
    font-size: 0.6875rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.09em;
    color: var(--muted);
    padding-bottom: 10px;
    margin-bottom: 14px;
    border-bottom: 1px solid var(--rule);
}

.rail-video {
    position: relative;
    width: 100%;
    padding-top: 56.25%;
    background: var(--n-wash);
    border-radius: var(--radius);
    overflow: hidden;
}

.rail-video--short {
    padding-top: 177.77%;
}

.rail-video iframe {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: 0;
}

.rail-card {
    display: flex;
    gap: 14px;
    padding: 14px 0;
    border-bottom: 1px solid var(--rule);
    text-decoration: none;
    color: inherit;
}

.rail-card:first-of-type {
    padding-top: 0;
}

.rail-card img {
    width: 72px;
    height: 54px;
    object-fit: cover;
    background: var(--n-wash);
    border-radius: var(--radius);
    flex-shrink: 0;
}

.rail-card-cat {
    font-size: 0.6875rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--p-base);
    margin-bottom: 4px;
}

.rail-card-title {
    font-size: 0.875rem;
    font-weight: 600;
    line-height: 1.35;
    color: var(--n-tertiary);
    display: -webkit-box;
    -webkit-line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.rail-card:hover .rail-card-title {
    color: var(--p-base);
}

/* -------------------------------------------------------------
   Author block

   The big Follow cards, at the foot of the article rather than
   in the rail. Someone decides whether to follow a writer after
   reading them, not before.
   ------------------------------------------------------------- */
.article-authors {
    margin-top: 64px;
    padding-top: 32px;
    border-top: 1px solid var(--rule);
    display: flex;
    flex-direction: column;
    gap: 16px;
}

.article-section-heading {
    font-size: 0.6875rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.09em;
    color: var(--muted);
    margin-bottom: 4px;
}

/* The author card is a shared component and carries its own
   inline styling. These strip the parts that belong to the old
   look -- the card border and radius -- without forking it. */
.article-authors .author-card,
.article-authors .profile-card {
    background: transparent !important;
    border: 1px solid var(--rule) !important;
    border-radius: var(--radius) !important;
    box-shadow: none !important;
}

.article-authors .profile-card:hover {
    transform: none;
    box-shadow: none;
}

.article-sources {
    margin-top: 48px;
    padding-top: 32px;
    border-top: 1px solid var(--rule);
}

.article-sources-body {
    font-size: 0.9375rem;
    line-height: 1.8;
    color: var(--muted);
    white-space: pre-line;
    overflow-wrap: anywhere;
}

.article-sources-body a {
    color: var(--p-base);
    text-decoration: underline;
    text-underline-offset: 3px;
}

/* -------------------------------------------------------------
   Bottom recommendation grids
   ------------------------------------------------------------- */
.article-recs {
    max-width: 1100px;
    margin: 80px auto 0;
    padding: 0 24px;
}

.article-recs + .article-recs {
    margin-top: 64px;
}

.article-recs:last-of-type {
    padding-bottom: 96px;
}

.recs-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 16px;
    padding-top: 32px;
    margin-bottom: 28px;
    border-top: 1px solid var(--rule);
}

.recs-head h2 {
    font-size: 1.375rem;
    font-weight: 700;
    letter-spacing: -0.01em;
    color: var(--p-tertiary);
    margin: 0;
}

.recs-viewall {
    font-size: 0.875rem;
    font-weight: 600;
    color: var(--p-base);
    text-decoration: none;
    white-space: nowrap;
}

.recs-viewall:hover {
    text-decoration: underline;
    text-underline-offset: 3px;
}

.recs-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 32px;
}

.rec-card {
    display: flex;
    flex-direction: column;
    text-decoration: none;
    color: inherit;
}

.rec-card-img {
    width: 100%;
    aspect-ratio: 3 / 2;
    object-fit: cover;
    background: var(--n-wash);
    /* 2px, not the page's 8px. A grid of thumbnails reads as photography
       with a hard edge, and the same corner that suits one large cover
       makes several small ones look like app icons. Matches the cards on
       the blog and news listings, which is the point -- the two used to
       disagree by 6px on the same card. */
    border-radius: 2px;
    margin-bottom: 14px;
}

.rec-card-cat {
    font-size: 0.6875rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--p-base);
    margin-bottom: 6px;
}

.rec-card-title {
    font-size: 1.0625rem;
    font-weight: 600;
    line-height: 1.35;
    color: var(--n-tertiary);
    margin-bottom: 10px;
}

.rec-card:hover .rec-card-title {
    color: var(--p-base);
}

.rec-card-meta {
    margin-top: auto;
    font-size: 0.8125rem;
    color: var(--muted);
}

/* -------------------------------------------------------------
   Ads

   The frame is the only recessed fill on the page, so an ad
   reads as bought space rather than as editorial.
   ------------------------------------------------------------- */
.ad-slot {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 6px;
    margin: 40px 0;
}

.rail .ad-slot {
    align-items: stretch;
    margin: 0;
}

.ad-slot-label {
    font-size: 0.625rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.11em;
    color: var(--muted);
}

/* An empty slot has to be visible.
 *
 * The frame was --n-wash, one step off the page -- which worked when an ad sat
 * on a white card inside a grey body, and stopped working the moment the page
 * itself went white. A dashed edge says "space, currently unsold" without
 * competing with the article the way a filled panel would. A live ad covers
 * all of this with its own image. */
/* Appearance only. The BOX belongs to the component.
 *
 * This used to force width, max-width, height and aspect-ratio from --ad-w /
 * --ad-h, which come from the size the slot was DECLARED at. The component
 * sizes itself from the ad actually SERVED -- those are not the same thing,
 * because getSingleAdForLocation() picks by location alone, so a slot booked
 * as a 728x90 is routinely handed a 300x250.
 *
 * When that happened the !important max-width let the square grow to the full
 * 680px column while the component's own inline aspect-ratio (300/250, which
 * beats a stylesheet) kept it square: a 300x250 ad rendered 680x567. Capping
 * the SLOT at the declared width still stops anything overflowing the column;
 * the ad inside it keeps its own true size and centres. */
.article-page .ad-banner-wrapper {
    border-radius: var(--radius) !important;
    border: 1px dashed var(--n-base) !important;
    box-shadow: none !important;
    background: transparent !important;
    margin: 0 !important;
}

/* The whole creative, never a cropped one.
 *
 * The component ships object-cover, and article-content.css additionally
 * absolutely-positions ad images to fill their wrapper. Together that means an
 * uploaded creative whose proportions do not exactly match the slot it was
 * booked into gets cropped -- and advertisers send whatever they have, so that
 * is the normal case, not the edge case. On a narrow screen, where the box was
 * also being squashed, it took the edges off: the logo and the call to action
 * are what live there.
 *
 * contain letterboxes instead. A slightly smaller ad is a cosmetic problem; a
 * beheaded one is a conversation with the advertiser. */
.article-page .ad-banner-wrapper img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    object-position: center;
}

/* The rail is at most 320px, so an IAB unit booked there fits as it is. The
   component's own max-width lets it down when the rail is narrower; forcing
   100% here did the opposite, stretching a 300px unit to whatever the rail
   happened to be. */
.article-page .rail .ad-banner-wrapper {
    margin-left: auto !important;
    margin-right: auto !important;
}

/* -------------------------------------------------------------
   Back to top

   Appears after a screenful. Sits above the listen dock rather than over it,
   so someone listening while reading keeps both.
   ------------------------------------------------------------- */
.back-to-top {
    position: fixed;
    right: 24px;
    bottom: 24px;
    z-index: 65;
    width: 42px;
    height: 42px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background: var(--n-secondary);
    color: var(--n-tertiary);
    border: 1px solid var(--rule);
    border-radius: 999px;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
    cursor: pointer;
    opacity: 0;
    visibility: hidden;
    transform: translateY(8px);
    transition: opacity 0.2s, transform 0.2s, visibility 0.2s, background 0.15s, color 0.15s;
}

html.dark .back-to-top {
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.6);
}

.back-to-top.is-visible {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
}

.back-to-top:hover {
    background: var(--p-base);
    border-color: var(--p-base);
    color: #fff;
}

html.dark .back-to-top:hover {
    color: #1A1637;
}

/* Out of the dock's way while it is up. */
.listen-dock:not([hidden]) ~ .back-to-top {
    bottom: 96px;
}

@media (prefers-reduced-motion: reduce) {
    .back-to-top {
        transition: opacity 0.2s, visibility 0.2s;
        transform: none;
    }
}

/* -------------------------------------------------------------
   Merchandise

   x-merch-card and x-merch-mini-card are shared with the home
   page, so they are not rewritten -- they are re-dressed here,
   inside .article-page only. Left alone they bring their own
   look to this one: drop shadows, hover lift
   and a 1rem radius, none of which belong next to the article.
   ------------------------------------------------------------- */
/* max-w-3xl is 768px and the article column is 680px, so the card hung 44px
   off each side -- and once <main> started clipping, the right side was the
   half you lost. Nothing inline may be wider than the measure it sits in. */
/* The ad wrapper is deliberately NOT in this selector. Forcing it to 100%
   overrode the max-width the component sets from the served ad's real size,
   which is how a 300x250 came to fill the column. The .ad-slot cap keeps ads
   inside the measure instead. */
.article-page .article-body .merch-mini-card {
    max-width: 100% !important;
    margin-left: auto !important;
    margin-right: auto !important;
}

.article-page .merch-mini-card {
    border-radius: var(--radius) !important;
    border-color: var(--rule) !important;
    box-shadow: none !important;
    background: var(--n-secondary) !important;
}

.article-page .merch-mini-card:hover {
    transform: none !important;
    box-shadow: none !important;
    border-color: var(--p-base) !important;
}

/* The gradient that fades the product photo into the card has the
   old surface colours baked into its Tailwind classes. */
.article-page .merch-mini-card > div[class*="bg-gradient-to-r"] {
    background: linear-gradient(
        to right,
        var(--n-secondary) 0%,
        var(--n-secondary) 48%,
        transparent 100%
    ) !important;
}

.article-page .article-body .merch-mini-card .merch-mini-title {
    font-family: 'Raleway', 'Google Sans', sans-serif !important;
    font-size: 1.0625rem !important;
    font-weight: 700 !important;
    color: var(--n-tertiary) !important;
}

.article-page .merch-mini-card .merch-mini-btn {
    background: var(--p-base) !important;
    border-radius: 999px !important;
    box-shadow: none !important;
    font-weight: 600 !important;
}

html.dark .article-page .merch-mini-card .merch-mini-btn,
html.dark .article-page .merch-mini-card .merch-mini-btn span,
html.dark .article-page .merch-mini-card .merch-mini-btn i {
    color: #1A1637 !important;
}

/* The rail's full card. */
.article-page .rail .group:hover {
    transform: none;
}

.article-page .rail .aspect-square {
    border-radius: var(--radius);
    border-color: var(--rule);
    background: var(--n-wash);
}

.article-page .rail .group h3 {
    font-family: 'Raleway', 'Google Sans', sans-serif;
    color: var(--n-tertiary);
}

.article-page .rail .group:hover h3 {
    color: var(--p-base);
}

/* Price, category eyebrow and the buy button, onto the accent. The card
   carries `primary` now rather than a hard-coded crimson, so this mostly
   agrees with it already -- it still runs because the rail's accent is
   --p-base, which differs from `primary` in dark. */
.article-page .rail .group [class*="text-primary"] {
    color: var(--p-base) !important;
}

.article-page .rail .group a[class*="rounded-full"] {
    background: var(--p-base) !important;
    color: #fff !important;
}

html.dark .article-page .rail .group a[class*="rounded-full"] {
    color: #1A1637 !important;
}

/* -------------------------------------------------------------
   Responsive

   Below 1100px the three columns become one: chapters move to a
   disclosure above the article, the rail drops to the bottom.
   ------------------------------------------------------------- */
@media (max-width: 1180px) {
    .article-grid {
        column-gap: 32px;
    }

    .chapters-column {
        max-width: 180px;
    }
}

@media (max-width: 1100px) {
    .article-grid {
        display: block;
        max-width: var(--measure);
        padding-top: 32px;
    }

    .chapters-column {
        display: none;
    }

    .chapters-mobile {
        display: block;
    }

    .rail-column {
        max-width: none;
        margin-top: 64px;
        padding-top: 32px;
        border-top: 1px solid var(--rule);
    }

    /* Nothing to pin to on one column, and nothing to drift
       against -- the rail is just the next thing you read. */
    .rail {
        position: static;
        max-height: none;
        overflow: visible;
    }

    .rail-drift {
        transform: none !important;
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 32px 40px;
        align-items: start;
    }
}

@media (max-width: 760px) {
    .article-hero {
        padding-top: 36px;
    }

    /* The author card lays itself out as one row -- avatar, name, Follow --
     * with the rows beneath indented 66px to clear the avatar. On a phone the
     * Follow button gets squeezed against the name and the indented rows run
     * out of width, which is the misalignment.
     *
     * The card is a shared component, so it is re-laid-out here rather than
     * forked: the row wraps, Follow drops to its own line under the name, and
     * the indents go. */
    .article-authors .author-card > div:first-child {
        flex-wrap: wrap;
        row-gap: 12px;
    }

    .article-authors .author-card .follow-btn {
        order: 3;
        width: 100%;
        justify-content: center;
    }

    .article-authors .author-card > div:not(:first-child),
    .article-authors .author-card p {
        padding-left: 0 !important;
    }

    .listen-bar {
        flex-wrap: wrap;
        gap: 12px;
    }

    .listen-side--end {
        margin-left: auto;
    }

    .rail-drift {
        grid-template-columns: minmax(0, 1fr);
    }

    .recs-grid {
        grid-template-columns: minmax(0, 1fr);
        gap: 36px;
    }

    .related-inline {
        gap: 14px;
    }

    .related-inline img {
        width: 72px;
        height: 56px;
    }

    .article-recs {
        margin-top: 56px;
    }
}

@media (prefers-reduced-motion: reduce) {
    .rail-drift {
        transform: none !important;
    }

    .reading-progress {
        display: none;
    }
}
