/**
 * Patriot University — House Style (Surface B)
 *
 * Applies to standard WordPress Posts ONLY. Never loads on the Echo Knowledge
 * Base, its archives, Pages, or the block editor.
 *
 * Governed by DESIGN.md -> "Surface B — House Style". Enqueued only when
 * pu_is_house_surface() is true; every rule is scoped under .hs so that even a
 * mis-enqueued sheet is inert on a Surface A page.
 *
 * The single exception is `body.pu-house`, which paints the page ground behind
 * the wrapper. It is class-gated and cannot reach a KB page.
 *
 * Load order: after pu-brand. Do NOT add this file to the $css_files array in
 * pu_enqueue_assets() — that array loads on every surface. Guards G2/G7/G8 in
 * tests/test_theme_ekb_guards.py fail the build if any of this drifts.
 */

/* ── House Style faces — self-hosted (DESIGN.md Self-Hosting Constraint)
   Generated by scripts/design/fetch_house_fonts.py — do not hand-edit.
   All three families are SIL OFL 1.1; see THIRD_PARTY.md.
   Google's unicode-range split is kept deliberately: an English page
   downloads only the `latin` files. ─────────────────────────────── */
@font-face {
  font-family: "Archivo Narrow";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("../fonts/ArchivoNarrow-var-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Archivo Narrow";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("../fonts/ArchivoNarrow-var-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Bodoni Moda";
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url("../fonts/BodoniModa-var-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Bodoni Moda";
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url("../fonts/BodoniModa-var-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "EB Garamond";
  font-style: italic;
  font-weight: 400 600;
  font-display: swap;
  src: url("../fonts/EBGaramond-italic-var-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "EB Garamond";
  font-style: italic;
  font-weight: 400 600;
  font-display: swap;
  src: url("../fonts/EBGaramond-italic-var-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "EB Garamond";
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url("../fonts/EBGaramond-var-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "EB Garamond";
  font-style: normal;
  font-weight: 400 600;
  font-display: swap;
  src: url("../fonts/EBGaramond-var-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ══════════════════════════════════════════════════════════════════════
   TOKENS — declared on .hs, never :root
   :root would leak into Surface A's --pu- and --iti- token system. Guard G8
   (tests/test_theme_ekb_guards.py) fails the build if that changes.
   ══════════════════════════════════════════════════════════════════════ */

.hs {
    /* Ink & stock */
    --ink:          #14140F;
    --ink-soft:     #4A4A42;
    --ink-mute:     #626257;   /* corrected from the specimen's #77776C (3.86:1 — fails AA) */
    --paper:        #EFEDE4;
    --stock:        #E2DFD3;
    --stock-deep:   #D3CFC0;   /* rules and bar tracks only — never a text ground */
    --rule:         #14140F;

    /* Second ink — rotates by desk */
    --ink-2:        #C4222B;
    --ink-2-soft:   #C4222B1A;
    --desk-courts:    #7B1F2B;
    --desk-congress:  #1B4B8F;
    --desk-elections: #8A5A00;
    --desk-executive: #1F5C3D;
    --desk-rights:    #2F5D62;
    --desk-economy:   #6B4E9B;
    --desk-corporate: #7A2E5E;
    --desk-press:     #0F5F8A;
    --desk-archive:   #6E5A2E;

    /* Semantic */
    --verified: #1F5C3D;
    --disputed: #8A5A00;
    --false:    #A31621;
    --context:  #1B4B8F;

    /* Faces */
    --face-display: "Bodoni Moda", "Didot", "Playfair Display", serif;
    --face-text:    "EB Garamond", "Iowan Old Style", Georgia, serif;
    --face-utility: "Archivo Narrow", "Helvetica Neue Condensed", Arial, sans-serif;
    --face-data:    ui-monospace, "SF Mono", Menlo, "Courier New", monospace;

    /* Scale */
    /* Type scale, raised for readability 2026-08-25.
     *
     * NOT a flat +10px on every token, which was the request but collapses the
     * scale. The arithmetic: +10 grows micro by 91% and banner by 11%, so
     * headlines fall from 2.0x body to 1.63x and micro labels rise from 0.65x
     * to 0.78x. Everything converges on body size and the page gets LESS
     * differentiated even as the text gets bigger — the opposite of readable.
     *
     * So the base rises and the proportions hold. Body 17 -> 21px (+24%), which
     * matters more than the number suggests: EB Garamond is an old-style face
     * with a modest x-height, so it reads smaller than 21px of a grotesque.
     * Every other step keeps its ratio to body.
     */
    --t-banner:  clamp(3.25rem, 7.5vw, 6.5rem);
    --t-display: clamp(2.4rem, 4.6vw, 4.15rem);
    --t-head:    clamp(1.8rem, 2.6vw, 2.5rem);

    /* Item and widget headlines: body + 12px, per the brief. A SEPARATE token
     * from --t-sub, which drives the dek and .prose h3 — pushing those to 33px
     * would make every summary shout as loudly as the headline above it. */
    --t-title:   2.0625rem;

    --t-sub:     1.4375rem;
    --t-body:    1.3125rem;
    --t-small:   1.125rem;
    --t-caption: 0.9375rem;
    --t-micro:   0.8125rem;

    --lh-tight: 1.02;
    --lh-head:  1.12;
    --lh-body:  1.52;
    --lh-note:  1.34;
    --measure:      66ch;
    --measure-rail: 34ch;

    /* Space — 8pt base, 4pt fine */
    --space-2xs: 4px;
    --space-xs:  8px;
    --space-sm:  12px;
    --space-md:  16px;
    --space-lg:  24px;
    --space-xl:  32px;
    --space-2xl: 48px;
    --space-3xl: 64px;
    --space-4xl: 96px;

    /* Rules — the scotch rule is the house divider */
    --rule-hair:  1px;
    --rule-thin:  2px;
    --rule-thick: 6px;
    /* The masthead's own rule. A token because the mobile drawer has to
       subtract it: the drawer is absolutely positioned against the masthead's
       PADDING box, which excludes this border, while its on-screen top edge
       sits below it. Two rules must agree on one number, so the number gets a
       name. */
    --rule-masthead: 12px;
    --radius:     0;          /* the house sets no round corners */

    /* Motion — inside DESIGN.md's 300ms cap */
    --ease: cubic-bezier(0.2, 0.7, 0.3, 1);
    --dur:  180ms;
}

/* Night edition — explicit reader choice */
.hs[data-edition="night"] {
    --ink:        #ECEAE1;
    --ink-soft:   #B3B1A6;
    --ink-mute:   #929086;   /* corrected from #86847A (4.41:1 on night stock) */
    --paper:      #14140F;
    --stock:      #1F1F18;
    --stock-deep: #2C2C23;
    --rule:       #ECEAE1;
    --ink-2:      #F0575F;
    --ink-2-soft: #F0575F26;
    --desk-courts:    #E5788A;
    --desk-congress:  #7FA9E8;
    --desk-elections: #D9A441;
    --desk-executive: #6FC095;
    --desk-rights:    #7FC4C9;
    --desk-economy:   #B49AE0;
    --desk-corporate: #CE86B4;
    --desk-press:     #6FB8DF;
    --desk-archive:   #C9B071;
    --verified: #6FC095;
    --disputed: #D9A441;
    --false:    #F0575F;
    --context:  #7FA9E8;
}

/* Night edition — system preference, when the reader has not chosen.
   Surface A follows prefers-color-scheme, so without this the chrome and the
   article would disagree. Same token block; :not([data-edition="day"]) lets an
   explicit "day" choice win over the OS. */
@media (prefers-color-scheme: dark) {
    .hs:not([data-edition="day"]) {
        --ink:        #ECEAE1;
        --ink-soft:   #B3B1A6;
        --ink-mute:   #929086;
        --paper:      #14140F;
        --stock:      #1F1F18;
        --stock-deep: #2C2C23;
        --rule:       #ECEAE1;
        --ink-2:      #F0575F;
        --ink-2-soft: #F0575F26;
        --desk-courts:    #E5788A;
        --desk-congress:  #7FA9E8;
        --desk-elections: #D9A441;
        --desk-executive: #6FC095;
        --desk-rights:    #7FC4C9;
        --desk-economy:   #B49AE0;
        --desk-corporate: #CE86B4;
        --desk-press:     #6FB8DF;
        --desk-archive:   #C9B071;
        --verified: #6FC095;
        --disputed: #D9A441;
        --false:    #F0575F;
        --context:  #7FA9E8;
    }
}

/* Desk assignment — the second ink encodes the beat.
 *
 * Two scopes, deliberately. On an ARTICLE the desk is a property of the page and
 * is set on the .hs root, as it always has been. On a LISTING every row is a
 * different story, so the desk is set per row and these must resolve at any
 * depth. The original rule said "never per element"; that held while the only
 * house surface was a single post. It stops holding the moment one page shows
 * ten stories from eight beats, which is what the chassis does.
 *
 * The descendant form only ever REASSIGNS --ink-2 inside a subtree that already
 * opted in by carrying [data-desk]. It cannot leak: nothing outside .hs matches,
 * and an element with no [data-desk] keeps whatever the root resolved to.
 */
.hs[data-desk="courts"],    .hs [data-desk="courts"]    { --ink-2: var(--desk-courts); }
.hs[data-desk="congress"],  .hs [data-desk="congress"]  { --ink-2: var(--desk-congress); }
.hs[data-desk="elections"], .hs [data-desk="elections"] { --ink-2: var(--desk-elections); }
.hs[data-desk="executive"], .hs [data-desk="executive"] { --ink-2: var(--desk-executive); }
.hs[data-desk="rights"],    .hs [data-desk="rights"]    { --ink-2: var(--desk-rights); }
.hs[data-desk="economy"],   .hs [data-desk="economy"]   { --ink-2: var(--desk-economy); }
.hs[data-desk="corporate"], .hs [data-desk="corporate"] { --ink-2: var(--desk-corporate); }
.hs[data-desk="press"],     .hs [data-desk="press"]     { --ink-2: var(--desk-press); }
.hs[data-desk="archive"],   .hs [data-desk="archive"]   { --ink-2: var(--desk-archive); }

/* The page ground behind .hs. The ONLY house declaration outside .hs scope:
   <html>/<body> sit outside the wrapper and would otherwise keep Surface A's
   parchment behind a night-edition article. Class-gated, so it can never
   reach a KB page. Guard G8 allows exactly this selector. */
body.pu-house { background: #EFEDE4; }
@media (prefers-color-scheme: dark) {
    body.pu-house:not([data-edition="day"]) { background: #14140F; }
}
body.pu-house[data-edition="night"] { background: #14140F; }
body.pu-house[data-edition="day"]   { background: #EFEDE4; }

/* ══════════════════════════════════════════════════════════════════════
   BASE — and the neutralizing block
   theme.css styles bare elements globally (h1-h6, p, a, blockquote, hr,
   code, pre, img) and sets font/colour on body.pu-body. Those inherit into
   .hs. Every override below is deliberate; specificity does the work, so
   there is no !important anywhere in this file.
   ══════════════════════════════════════════════════════════════════════ */

.hs {
    min-height: 100dvh;
    /* The page must never scroll horizontally. `clip` rather than `hidden`:
       it contains overflow without creating a scroll container, so nothing
       inside gains an unwanted scrollport.

       The specific offender is inherited, not ours: the primary menu renders
       depth-2 submenus as position:absolute / visibility:hidden, and their
       boxes extend past the viewport at tablet widths. Surface A has the same
       defect (a KB article measured 821px against a 768px viewport on
       2026-08-13) — it is a components.css problem, and components.css is
       fingerprint-pinned and out of scope here. This contains the symptom on
       Surface B only; the shared fix belongs in its own change. */
    overflow-x: clip;
    background: var(--paper);
    color: var(--ink);
    font-family: var(--face-text);
    font-size: var(--t-body);
    line-height: var(--lh-body);
    -webkit-font-smoothing: antialiased;
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}

.hs *,
.hs *::before,
.hs *::after { box-sizing: border-box; }

.hs ::selection { background: var(--ink-2); color: var(--paper); }
.hs :focus-visible { outline: var(--rule-thin) solid var(--ink-2); outline-offset: 3px; }

.hs a {
    color: inherit;
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-underline-offset: 0.18em;
    transition: color var(--dur) var(--ease);
}
.hs a:hover { color: var(--ink-2); }

.hs h1, .hs h2, .hs h3, .hs h4, .hs h5, .hs h6 {
    font-family: var(--face-display);
    color: var(--ink);
    letter-spacing: -0.01em;
}

.hs img { max-width: 100%; height: auto; display: block; }

/* theme.css sets `p { margin-bottom; max-width: 70ch }` globally. Inside .hs
   that reached the kicker (visibly truncating its rule), the dek and the
   byline. Reset here; .prose and each component set their own metrics. */
.hs p { max-width: none; margin: 0; }

.hs .wrap {
    max-width: 1220px;
    margin: 0 auto;
    padding: 0 var(--space-lg);
}
@media (min-width: 768px) {
    .hs .wrap { padding: 0 var(--space-xl); }
}

/* ── Utility type ──────────────────────────────────────────────────── */

.hs .u-micro {
    font-family: var(--face-utility);
    font-size: var(--t-micro);
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
    line-height: 1.3;
}
.hs .u-caption { font-family: var(--face-utility); font-size: var(--t-caption); line-height: var(--lh-note); }
.hs .u-data    { font-family: var(--face-data); font-size: var(--t-caption); letter-spacing: -0.01em; }
.hs .u-mute    { color: var(--ink-mute); }
.hs .u-ink2    { color: var(--ink-2); }

/* ── Rules — the scotch rule is the house divider ──────────────────── */

.hs .scotch {
    border: 0;
    margin: var(--space-xl) 0;
    padding: 0 0 3px;
    height: 0;
    border-top: var(--rule-thick) solid var(--rule);
    border-bottom: var(--rule-hair) solid var(--rule);
    background: transparent;
}
.hs .scotch--light { border-top-width: var(--rule-thin); }
.hs .hair { border: 0; border-top: var(--rule-hair) solid var(--ink-mute); margin: var(--space-lg) 0; }

/* ══════════════════════════════════════════════════════════════════════
   CHROME — folio masthead and colophon
   A compact folio strip, not the specimen's full banner: that is a
   publication front-page device, wrong on an article page.
   ══════════════════════════════════════════════════════════════════════ */

/* `position: relative` is load-bearing for the MOBILE DRAWER, not for the
 * masthead's own layout.
 *
 * header-house.php keeps the JS class contract verbatim — `.pu-header__nav`,
 * `.pu-header__menu-toggle`, `#primary-nav` — but the drawer also has a CSS
 * contract that was never reproduced: `theme.css` positions it
 * `absolute; top: 100%`, which needs a positioned ancestor. On Surface A that
 * is `.pu-header` (sticky). Here the masthead was `static`, so the drawer's
 * containing block fell through to the initial containing block and `top: 100%`
 * resolved to one viewport height — measured 844px on a 390x844 phone, i.e.
 * the entire menu rendered below the fold and could not be reached.
 *
 * `position: relative` with `z-index: auto` creates no stacking context, so
 * nothing else in the masthead is reordered by this.
 */
.hs .hs-masthead {
    position: relative;
    border-top: var(--rule-masthead) solid var(--ink);
    padding-top: var(--space-md);
    margin-bottom: var(--space-xl);
}
/* The drawer's height, corrected for the masthead's rule.
 *
 * theme.css caps the mobile drawer at `calc(100dvh - 100%)`, where `100%` is the
 * containing block — an absolutely positioned element's containing block is its
 * ancestor's PADDING box, which excludes `border-top`. The masthead carries a
 * 12px scotch rule above that padding box, so the drawer's on-screen top edge
 * sits 12px lower than `top: 100%` reports and it overran the viewport by
 * exactly that much. Measured: `top` resolved to 193.812px against a 205.812px
 * masthead, rect top 206, bottom 856 on an 844px viewport.
 *
 * Subtracting the same token the border uses keeps the two in step; a second
 * literal `12px` here would be a number waiting to drift.
 */
@media (max-width: 767px) {
    .hs .pu-header__nav {
        max-height: calc(100dvh - 100% - var(--rule-masthead));

        /* AND IT MUST PAINT ABOVE THE PAGE.
         *
         * Reported from a device: the Echo KB search widget's heading and input
         * rendered straight through the open menu, and the chatbot's launcher
         * sat on top of it too.
         *
         * Measured: the drawer is `z-index: auto` and NO ancestor creates a
         * stacking context — `position: relative` on the masthead deliberately
         * does not, which is what the note above it says. So the drawer competed
         * in the root stacking context by paint order and lost to anything
         * positioned later: `.widg-widget-doc-search-container` (z-index auto,
         * later in the document) and the AI Engine launcher at z-index 1-2.
         *
         * Surface A never had this problem, and not by its own merit: its drawer
         * sits inside `.pu-header { z-index: 1000 }` and inherits that context.
         * Worth knowing — if that z-index is ever removed, Surface A breaks in
         * exactly this way.
         *
         * 1000 matches `.pu-header` so the two surfaces agree, and it stays far
         * below the chat WINDOW's own z-index, so opening the chat still covers
         * the menu rather than fighting it.
         */
        z-index: 1000;
    }
}

.hs .hs-folio {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-md);
    justify-content: space-between;
    align-items: baseline;
    padding-bottom: var(--space-xs);
    border-bottom: var(--rule-hair) solid var(--rule);
}
.hs .hs-folio__brand {
    font-family: var(--face-display);
    font-weight: 900;
    font-size: 1.375rem;
    letter-spacing: 0.02em;
    text-transform: uppercase;
    text-decoration: none;
    line-height: 1;
}
.hs .hs-folio__meta {
    display: flex;
    gap: var(--space-md);
    align-items: baseline;
    flex-wrap: wrap;
}

/* Primary nav — RESTYLED, never renamed. theme.js binds the mobile menu to
   .pu-header__menu-toggle / #primary-nav / .pu-header__nav by literal
   selector; changing these class names silently kills mobile navigation. */
.hs .pu-header__nav .pu-nav-list {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-md);
    list-style: none;
    margin: 0;
    padding: var(--space-xs) 0 0;
}
.hs .pu-header__nav a {
    font-family: var(--face-utility);
    font-size: var(--t-caption);
    font-weight: 600;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    text-decoration: none;
}
.hs .pu-header__nav a:hover { color: var(--ink-2); text-decoration: underline; }

.hs .pu-header__menu-toggle {
    background: transparent;
    border: var(--rule-thin) solid var(--ink);
    border-radius: var(--radius);
    color: var(--ink);
    cursor: pointer;
    padding: 6px;
}

.hs .hs-edition {
    display: flex;
    gap: var(--space-xs);
    align-items: center;
}

.hs .hs-colophon {
    border-top: var(--rule-thick) solid var(--ink);
    margin-top: var(--space-3xl);
    padding: var(--space-lg) 0 var(--space-2xl);
    font-family: var(--face-utility);
    font-size: var(--t-caption);
    color: var(--ink-mute);
}
.hs .hs-colophon a { color: var(--ink-soft); }
.hs .hs-colophon ul {
    list-style: none;
    margin: 0 0 var(--space-md);
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-md);
}

/* ══════════════════════════════════════════════════════════════════════
   ARTICLE
   ══════════════════════════════════════════════════════════════════════ */

.hs .hs-article { padding-bottom: var(--space-2xl); }

/* Until the apparatus rail exists (Phase 2a), an article is a single column.
   Centre it: left-aligning a 66ch measure inside a 1220px wrap leaves ~45% of
   the viewport empty on the right, which reads as a broken layout rather than
   as deliberate asymmetry. When the rail arrives the article takes the full
   wrap and the spread splits into prose + rail. */
.hs .hs-article:not(:has(.rail)) {
    max-width: 76ch;
    margin-inline: auto;
}

.hs .kicker {
    font-family: var(--face-utility);
    font-size: var(--t-micro);
    font-weight: 700;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--ink-2);
    display: flex;
    align-items: center;
    gap: var(--space-xs);
    margin-bottom: var(--space-xs);
}
.hs .kicker a { text-decoration: none; color: inherit; }
.hs .kicker::after {
    content: "";
    flex: 1;
    height: 0;
    border-top: var(--rule-thin) solid var(--ink-2);
}

.hs .hed {
    font-family: var(--face-display);
    /* Pinned, not auto — see the opsz note on .pu-kb-articles__title in
       chassis.css. `auto` resolves opsz to the rendered size, which at display
       scale drives Bodoni's horizontal hairlines below one device pixel and
       makes an em dash in a headline disappear. */
    font-optical-sizing: none;
    font-variation-settings: "opsz" 9, "wght" 700;
    font-weight: 700;
    font-size: var(--t-display);
    line-height: var(--lh-head);
    letter-spacing: -0.015em;
    margin: 0 0 var(--space-sm);
    max-width: 22ch;
}

.hs .dek {
    font-family: var(--face-text);
    font-style: italic;
    font-size: var(--t-sub);
    color: var(--ink-soft);
    margin: 0 0 var(--space-md);
    max-width: 52ch;
}

.hs .byline {
    font-family: var(--face-utility);
    font-size: var(--t-caption);
    color: var(--ink-mute);
    border-top: var(--rule-hair) solid var(--stock-deep);
    padding-top: var(--space-xs);
    margin-bottom: var(--space-lg);
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-sm);
    align-items: baseline;
}
.hs .byline a { color: inherit; }

/* Apparatus suppressed (D2) — RSS feed items carry the typography but not the
   editorial apparatus. A kicker rule and a dek stub read as broken on a
   two-sentence auto-import. */
.hs .hs-article--bare .kicker::after { content: none; }
.hs .hs-article--bare .hed { max-width: 32ch; font-size: var(--t-head); }
.hs .hs-article--bare .hs-source {
    font-family: var(--face-utility);
    font-size: var(--t-caption);
    color: var(--ink-mute);
    margin-top: var(--space-lg);
    padding-top: var(--space-xs);
    border-top: var(--rule-hair) solid var(--stock-deep);
}

/* ── The spread: prose + apparatus rail (rail populated in Phase 2a) ── */

.hs .spread {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--space-lg);
}

/* Grid items default to min-width:auto, which means they refuse to shrink below
   their content's MIN-CONTENT width. Measured on the live post at 320px:
   .spread was 272px while .prose inside it was 324px, giving the page a
   horizontal scrollbar. The long token in that article was the symptom; this is
   the cause, and it would recur with any wide table or unbreakable string.
   min-width:0 lets the track govern and the content wrap or scroll. */
.hs .spread > * { min-width: 0; }
/* Two columns ONLY when a rail exists. Phase 1 ships no rail (that is 2a), and
   an unconditional grid reserved a rail-width column of dead space beside the
   prose. :has() degrades to the single-column default, which is correct. */
@media (min-width: 1024px) {
    /* Beside the prose, not beneath it: drop the end-matter spacing and the
       heavy rule the stacked base uses. Mobile-first (spec §4.1), inverted from
       `@media (max-width: 1023px)`. Both values are restored explicitly rather
       than left to cascade — the base now sets them, so "unset" has to be
       said. */
    .hs .rail {
        margin-top: 0;
        border-top: var(--rule-thin) solid var(--rule);
    }

    .hs .spread:has(.rail) {
        /* The prose track is sized to the MEASURE, not 1fr. With 1fr the track
           filled the container while the text stayed capped at 66ch, so the
           rail was pushed to the far right and a note sat a long way from the
           marker it annotates — which defeats the point of a margin rail. */
        grid-template-columns: minmax(0, var(--measure)) minmax(200px, var(--measure-rail));
        gap: var(--space-2xl);
        justify-content: start;
    }
}

/* THE BASE STATE: the rail stacks under the prose, with a rule and enough
   space above to read as end-matter rather than an orphaned column. The
   two-column spread at >=1024px is the enhancement, and demotes this to a thin
   hairline because a heavy rule makes no sense beside the prose. */
.hs .rail {
    font-family: var(--face-utility);
    font-size: var(--t-caption);
    line-height: var(--lh-note);
    margin-top: var(--space-2xl);
    border-top: var(--rule-thick) solid var(--rule);
    padding-top: var(--space-xs);
}
.hs .rail__item {
    display: grid;
    grid-template-columns: 2.2em 1fr;
    gap: var(--space-xs);
    padding: var(--space-xs) 0;
    border-bottom: var(--rule-hair) solid var(--stock-deep);
}
.hs .rail__marker {
    font-family: var(--face-data);
    font-size: var(--t-micro);
    color: var(--ink-2);
    padding-top: 0.15em;
}

/* Footnote references as house markers. The converter emits
   <sup class="footnote-ref"><a href="#fn:id">N</a></sup>; styling that
   directly means no DOM rewriting is needed to get the house look. */
.hs .prose .footnote-ref {
    font-family: var(--face-data);
    font-size: 0.72em;
    vertical-align: 0.35em;
    margin-left: 0.12em;
}
.hs .prose .footnote-ref a {
    color: var(--ink-2);
    background: var(--ink-2-soft);
    padding: 0.1em 0.28em;
    border-bottom: 1px solid var(--ink-2);
    text-decoration: none;
}
.hs .prose .footnote-ref a:hover,
.hs .prose .footnote-ref a:focus-visible {
    background: var(--ink-2);
    color: var(--paper);
}

/* The rail note itself. `.rail__marker` and `.rail__item` are already defined
   above; this is the note body inside the row. */
.hs .rail__note > p { margin: 0 0 var(--space-2xs); }
.hs .rail__note > p:last-child { margin-bottom: 0; }
.hs .rail__note a { color: var(--ink-soft); }

.hs .marker {
    font-family: var(--face-data);
    font-size: 0.72em;
    vertical-align: 0.35em;
    color: var(--ink-2);
    background: var(--ink-2-soft);
    padding: 0.1em 0.28em;
    margin-left: 0.12em;
    border-bottom: 1px solid var(--ink-2);
    text-decoration: none;
    cursor: help;
}
.hs .marker:hover,
.hs .marker:focus-visible { background: var(--ink-2); color: var(--paper); }

/* ══════════════════════════════════════════════════════════════════════
   PROSE — the element map

   Selectors here are DESCENDANT, not direct-child. The publish pipeline wraps
   post bodies in container divs (e.g. <div class="pu-showcase">), so `.prose >
   h2` matched nothing on a real post: headings silently fell back to the
   generic .hs h1-h6 rule, losing the scotch rule and the type scale while
   still looking plausibly styled.
   This is what makes existing Markdown posts look right with no authoring
   change. Every rule targets what the Markdown pipeline actually emits.
   ══════════════════════════════════════════════════════════════════════ */

.hs .prose {
    max-width: var(--measure);
    /* Long unbreakable tokens — file names in inline code, bare URLs — have no
       wrap opportunity and force the whole column wider than the viewport. At
       320px the wrap padding (2x24) plus a list indent (24) leaves 248px, and a
       37-char <code> filename measured 296px, pushing the document to 348px and
       giving the page a horizontal scrollbar. Measured on the live post. */
    overflow-wrap: break-word;
}

/* `anywhere` rather than `break-word`: identifier-like tokens (FILE-NAME.md,
   long_function_names) have no break opportunity at all, so break-word alone
   still overflows them. */
.hs .prose code,
.hs .prose a { overflow-wrap: anywhere; }

.hs .prose > * + * { margin-top: var(--space-md); }

.hs .prose p {
    margin: 0 0 var(--space-md);
    max-width: var(--measure);
}

.hs .prose h2 {
    font-family: var(--face-display);
    font-weight: 700;
    font-size: var(--t-head);
    line-height: var(--lh-head);
    margin: var(--space-2xl) 0 var(--space-md);
    padding-bottom: var(--space-2xs);
    border-bottom: var(--rule-thick) solid var(--rule);
}
.hs .prose h3 {
    font-family: var(--face-display);
    font-weight: 500;
    font-size: var(--t-sub);
    line-height: var(--lh-head);
    margin: var(--space-xl) 0 var(--space-xs);
}
.hs .prose h4 {
    font-family: var(--face-utility);
    font-weight: 700;
    font-size: var(--t-small);
    letter-spacing: 0.08em;
    text-transform: uppercase;
    margin: var(--space-lg) 0 var(--space-xs);
}

.hs .prose ul,
.hs .prose ol { max-width: var(--measure); padding-left: var(--space-lg); margin: 0 0 var(--space-md); }
.hs .prose li { margin-bottom: var(--space-2xs); }
.hs .prose li::marker { color: var(--ink-2); }

/* Markdown blockquote reads as the house pull quote */
.hs .prose blockquote {
    font-family: var(--face-display);
    font-weight: 400;
    font-style: italic;
    font-size: clamp(1.25rem, 2.4vw, 1.75rem);
    line-height: 1.22;
    color: var(--ink);
    background: transparent;
    border: 0;
    border-left: var(--rule-thick) solid var(--ink-2);
    border-radius: var(--radius);
    margin: var(--space-xl) 0;
    padding: 0 0 0 var(--space-md);
    max-width: var(--measure);
}
.hs .prose blockquote p { font-size: inherit; margin-bottom: var(--space-xs); }
.hs .prose blockquote cite,
.hs .prose blockquote footer {
    display: block;
    font-family: var(--face-utility);
    font-style: normal;
    font-size: var(--t-caption);
    color: var(--ink-mute);
    margin-top: var(--space-xs);
}

.hs .prose hr {
    border: 0;
    height: 0;
    margin: var(--space-2xl) 0;
    padding-bottom: 3px;
    border-top: var(--rule-thick) solid var(--rule);
    border-bottom: var(--rule-hair) solid var(--rule);
}

.hs .prose figure { margin: var(--space-xl) 0; }
.hs .prose figcaption {
    font-family: var(--face-utility);
    font-size: var(--t-caption);
    line-height: var(--lh-note);
    color: var(--ink-mute);
    margin-top: var(--space-xs);
    border-top: var(--rule-hair) solid var(--stock-deep);
    padding-top: var(--space-2xs);
}

.hs .prose code {
    font-family: var(--face-data);
    font-size: 0.9em;
    background: var(--stock);
    border-radius: var(--radius);
    padding: 0.1em 0.3em;
}
.hs .prose pre {
    font-family: var(--face-data);
    background: var(--stock);
    border: var(--rule-hair) solid var(--stock-deep);
    border-radius: var(--radius);
    padding: var(--space-md);
    overflow-x: auto;
    max-width: 100%;
}
.hs .prose pre code { background: transparent; padding: 0; }

/* Tables — scroll inside their own container, never widen the page */
.hs .prose table {
    width: 100%;
    border-collapse: collapse;
    font-family: var(--face-utility);
    font-size: var(--t-small);
    margin: var(--space-xl) 0;
}
.hs .prose caption {
    text-align: left;
    font-family: var(--face-utility);
    font-size: var(--t-caption);
    color: var(--ink-mute);
    padding-bottom: var(--space-xs);
}
.hs .prose th {
    font-family: var(--face-utility);
    font-size: var(--t-micro);
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    text-align: left;
    border-bottom: var(--rule-thin) solid var(--ink);
    padding: var(--space-xs);
}
.hs .prose td {
    border-bottom: var(--rule-hair) solid var(--stock-deep);
    padding: var(--space-xs);
    vertical-align: top;
}
.hs .prose tbody tr:hover { background: var(--ink-2-soft); }

/* Wide tables scroll inside their own box rather than widening the page.
   Two selectors because there are two shapes: WordPress block content arrives
   wrapped in .wp-block-table, but the Markdown pipeline emits a BARE <table>,
   which the wrapper rule never matched. `display: block` is what makes a table
   scrollable; the row/cell children keep their own display types. */
.hs .prose .wp-block-table { overflow-x: auto; max-width: 100%; }
.hs .prose table {
    display: block;
    max-width: 100%;
    overflow-x: auto;
}

/* Markdown footnotes — the endnote form. Phase 2a lifts these into the rail
   at >=1024px; until then they stay a readable endnote list. */
.hs .prose .footnotes {
    margin-top: var(--space-2xl);
    padding-top: var(--space-xs);
    border-top: var(--rule-thin) solid var(--rule);
    font-family: var(--face-utility);
    font-size: var(--t-caption);
    line-height: var(--lh-note);
    color: var(--ink-soft);
}

/* ══════════════════════════════════════════════════════════════════════
   COMPONENTS used by article chrome
   ══════════════════════════════════════════════════════════════════════ */

.hs .chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-family: var(--face-utility);
    font-size: var(--t-micro);
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    padding: 3px 8px;
    border: var(--rule-hair) solid currentColor;
    border-radius: var(--radius);
}
.hs .chip::before { content: ""; width: 7px; height: 7px; background: currentColor; }
.hs .chip--verified { color: var(--verified); }
.hs .chip--disputed { color: var(--disputed); }
.hs .chip--false    { color: var(--false); }
.hs .chip--context  { color: var(--context); }

.hs .btn {
    font-family: var(--face-utility);
    font-size: var(--t-small);
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    padding: 8px 14px;
    border-radius: var(--radius);
    border: var(--rule-thin) solid var(--ink);
    background: var(--ink);
    color: var(--paper);
    cursor: pointer;
    text-decoration: none;
    transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.hs .btn:hover { background: var(--ink-2); border-color: var(--ink-2); color: var(--paper); }
.hs .btn--ghost { background: transparent; color: var(--ink); }
.hs .btn--ghost:hover { background: var(--ink); color: var(--paper); border-color: var(--ink); }

/* Shared partials rendered inside the house wrapper (share bar, author box,
   after-content widgets, comments). They keep their .pu-* markup and
   components.css rules; these overrides only re-tune the parts that would
   otherwise read as Surface A inside a House Style article. */
.hs .pu-share-bar,
.hs .pu-author-box,
.hs .pu-after-content {
    font-family: var(--face-utility);
    font-size: var(--t-caption);
    border-radius: var(--radius);
    background: transparent;
    border-top: var(--rule-hair) solid var(--stock-deep);
    margin-top: var(--space-xl);
    padding-top: var(--space-md);
}
.hs .pu-author-box img { border-radius: var(--radius); }

/* ══════════════════════════════════════════════════════════════════════
   MOTION — DESIGN.md caps transitions at 300ms; --dur is 180ms.
   No scroll reveals, no parallax, no page transitions.
   ══════════════════════════════════════════════════════════════════════ */

@media (prefers-reduced-motion: reduce) {
    .hs { --dur: 1ms; }
}
