  /* ==========================================================================
     Notebook article — the reading/detail template.
     --------------------------------------------------------------------------
     Shared by every /notebooks/<slug> page. styles.css supplies the chrome
     (tokens, nav, .page grid, left rail, footer); this file styles only the
     article: the header block, the prose reading column, and the article foot.

     The prose column is a fixed measure (~68ch) for comfortable long-form
     reading; figures, tables and pullquotes may break wider. Yellow (--magic)
     is used only as an accent (blockquote rule, link hover, pull marker) per
     the brand rule — never as a flooded surface.
     ========================================================================== */

  /* ── CODE FONT ──────────────────────────────────────────────────────────
     JetBrains Mono for actual code (pre/code) — a proper code face with clear
     glyph disambiguation (0/O, 1/l/I). Self-hosted; also the code font on the
     OS site. Aeonik Fono stays the UI mono (eyebrows, meta, rail, captions). */
  @font-face {
    font-family: 'JetBrains Mono';
    src: url('/fonts/jetbrains/jetbrains-mono-latin-400-normal.woff2') format('woff2');
    font-weight: 400; font-style: normal; font-display: swap;
  }
  @font-face {
    font-family: 'JetBrains Mono';
    src: url('/fonts/jetbrains/jetbrains-mono-latin-400-italic.woff2') format('woff2');
    font-weight: 400; font-style: italic; font-display: swap;
  }
  @font-face {
    font-family: 'JetBrains Mono';
    src: url('/fonts/jetbrains/jetbrains-mono-latin-500-normal.woff2') format('woff2');
    font-weight: 500; font-style: normal; font-display: swap;
  }
  @font-face {
    font-family: 'JetBrains Mono';
    src: url('/fonts/jetbrains/jetbrains-mono-latin-700-normal.woff2') format('woff2');
    font-weight: 700; font-style: normal; font-display: swap;
  }

  /* min-width:0 lets this grid item shrink below its content's intrinsic width,
     so wide <pre> / display-math scroll internally instead of stretching the
     page (the shared mobile .page collapses to a 1fr track with a min-content
     floor). */
  .content.article { padding: 0 64px; min-width: 0; }

  /* The article's single column width. Figures and tables take this measure too.

     Deliberately NOT expressed in `ch`. It used to be `68ch`, which looks like
     one value but resolves against each element's own font-size, so every block
     got a different column: body text 884px, figures 820px (hard-coded),
     pre/tables 900px (hard-coded), captions 612px at 14px, and h2 effectively
     uncapped at 1030px because 68ch of 34px type exceeds the container. Four
     staggered right edges from what read like one rule.

     55rem is the same 884px the 18px body face produced at 68 characters, but
     it means the same thing everywhere, and being root-relative it still scales
     with a reader's font-size preference. */
  :root { --measure: 55rem; }

  /* ── ARTICLE HEADER ─────────────────────────────────────────────────────── */
  .article-head { padding: 72px 0 44px; border-bottom: 1px solid var(--ink); }

  .crumb {
    display: inline-flex; align-items: center; gap: 8px;
    font-family: 'Aeonik Fono', 'Aeonik Fono Fallback', 'SF Mono', Menlo, monospace;
    font-size: 11px; letter-spacing: 0.14em; text-transform: uppercase;
    color: var(--muted); margin-bottom: 28px;
    border-bottom: 1px solid transparent; padding-bottom: 2px;
    /* WCAG 2.2 AA 2.5.8 needs 24x24. Grown with top padding and pulled back
       by an equal negative margin, so the hit area gains height upward while
       the box, the text baseline and the underline stay exactly where they
       were — measured 19.5px before, 25.5px after. */
    padding-top: 6px; margin-top: -6px;
  }
  /* Hover is an enhancement, not a state. On touch, tapping a link applies
     :hover and leaves it applied until the next tap elsewhere: measured on a
     390px touch context, .band-row stayed padded 16px, .entry 12px, .deeper's
     rule 2px and .social's underline ink — so a row you merely visited kept
     reading as selected. Guarding on (hover: hover) means pointers keep every
     effect and touch gets none of the residue. Each rule is wrapped where it
     stands rather than collected into one block, so cascade order is
     untouched. */
  @media (hover: hover) {
    .crumb:hover { color: var(--ink); border-bottom-color: var(--magic); }
  }

  /* No measure cap: this is a label row, not reading text, and it follows the
     shared .sec-head device from the main pages — English label left, Japanese
     companion pushed to the far right of the content column. Capped to
     var(--measure) the JP label stopped 150px inside the header's right edge,
     where every other page in the site takes it flush. The body text below
     keeps the measure; a header rule spanning wider than its prose is the
     pattern .sec-head already establishes. */
  .eyebrow {
    display: flex; justify-content: space-between;
    font-family: 'Aeonik Fono', 'Aeonik Fono Fallback', 'SF Mono', Menlo, monospace;
    font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase;
    color: var(--muted); margin-bottom: 22px;
  }
  .eyebrow .ja { color: var(--subtle); }

  .article-head h1 {
    font-weight: 500;
    font-size: clamp(34px, 4.6vw, 60px);
    line-height: 1.02; letter-spacing: -0.032em;
    text-wrap: balance; max-width: 22ch;
    margin-bottom: 28px;
  }

  .byline {
    display: flex; align-items: center; flex-wrap: wrap; gap: 14px;
    font-family: 'Aeonik Fono', 'Aeonik Fono Fallback', 'SF Mono', Menlo, monospace;
    font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase;
    color: var(--muted); margin-bottom: 30px;
  }
  /* Two lines rather than one run: attribution first (what this is, who wrote
     it), publication metadata second (when, how long, and the PDF where there
     is one). As a single six-item dot-separated string the type tag, author,
     date and reading time all carried equal weight and read as one undifferentiated
     line. flex-basis forces the break; the parent's 14px gap sets the leading. */
  .byline-meta {
    flex-basis: 100%;
    display: flex; align-items: center; flex-wrap: wrap; gap: 14px;
  }
  .byline .sep { color: var(--rule); }
  .byline a { border-bottom: 1px solid var(--rule); padding-bottom: 1px; }
  @media (hover: hover) {
    .byline a:hover { border-bottom-color: var(--magic); }
  }

  /* Type-tag chips — same variants as the index, re-declared for this page. */
  .type-tag { border: 1px solid var(--ink); padding: 3px 8px 4px; color: var(--ink); letter-spacing: 0.16em; font-size: 10px; }
  .type-tag.tw { background: transparent; }
  .type-tag.es { background: var(--ink); color: var(--bg); }
  .type-tag.wp { background: var(--magic); }
  .type-tag.fe { background: transparent; border-style: dashed; }

  .standfirst {
    font-size: 22px; line-height: 1.5; letter-spacing: -0.012em;
    color: var(--ink); max-width: 60ch; text-wrap: pretty;
  }
  .standfirst .key { background: linear-gradient(transparent 60%, var(--magic) 60%); padding: 0 2px; }

  /* ── SHARE (top meta-bar) ───────────────────────────────────────────────
     A quiet row of monochrome icons bracketed by a hairline above and the
     header's ink rule below. X/LinkedIn/Email are plain intent links (work with
     JS off); site.js progressively prepends a "Copy link" and native "Share"
     button where supported. Each control carries an aria-label + tooltip. */
  .article-share {
    display: flex; justify-content: space-between; align-items: center;
    gap: 12px 24px; flex-wrap: wrap;
    margin-top: 30px; padding-top: 14px;
    border-top: 1px solid var(--rule);
  }
  .article-share .share-label {
    font-family: 'Aeonik Fono', 'Aeonik Fono Fallback', 'SF Mono', Menlo, monospace;
    font-size: 11px; letter-spacing: 0.18em; text-transform: uppercase; color: var(--subtle);
  }
  .share-links { display: flex; align-items: center; gap: 6px; margin-right: -7px; }
  .share-btn {
    position: relative;
    display: inline-flex; align-items: center; justify-content: center;
    width: 32px; height: 32px; padding: 7px; margin: 0;
    color: var(--ink); background: none; border: 0; cursor: pointer;
    appearance: none; -webkit-appearance: none; -webkit-tap-highlight-color: transparent;
  }
  .share-btn svg { width: 100%; height: 100%; display: block; }
  /* Animated yellow underline — the site's link-hover idiom, no layout shift. */
  .share-btn::after {
    content: ''; position: absolute; left: 7px; right: 7px; bottom: 2px; height: 2px;
    background: var(--magic); transform: scaleX(0); transform-origin: center;
    transition: transform 0.14s ease;
  }
  .share-btn:focus-visible::after { transform: scaleX(1); }
  @media (hover: hover) {
    .share-btn:hover::after { transform: scaleX(1); }
  }
  /* The yellow underline stays as the hover idiom, but it cannot be the focus
     indicator: --magic on the paper ground is 1.03:1, so keyboard focus was
     effectively invisible on every control in the row. The ring comes back (inherited
     from styles.css) and the underline now accompanies it rather than replacing
     it. Offset pulled in to 2px so the 32px buttons' rings don't collide. */
  /* Deliberately left at 32px on touch, though the house standard is 44. Four
     buttons sit in a 6px-gap row, so the pitch is 38px. Growing the boxes to 44
     forces the glyphs 26px apart and breaks the row's rhythm; extending only the
     hit area with an overlay makes neighbouring targets overlap by 6px, so the
     wrong icon gets tapped. An accurate 32px target on a 38px pitch beats both.
     2.5.8 is satisfied either way. */
  .share-btn:focus-visible { outline-offset: 2px; }
  .share-btn.share-copy.copied { color: var(--muted); }
  .share-btn.share-copy.copied::after { transform: scaleX(1); }

  /* ── PROSE READING COLUMN ───────────────────────────────────────────────── */
  .prose { padding: 48px 0 0; }
  .prose > * { max-width: var(--measure); }

  .prose p {
    font-size: 18px; line-height: 1.7; letter-spacing: -0.005em;
    margin-bottom: 24px; text-wrap: pretty;
  }
  .prose > p:first-of-type,
  .prose p.lead {
    font-size: 21px; line-height: 1.55; letter-spacing: -0.010em;
    color: var(--ink); margin-bottom: 28px;
  }

  .prose h2 {
    font-weight: 500; font-size: clamp(26px, 3vw, 34px);
    line-height: 1.1; letter-spacing: -0.022em;
    margin: 56px 0 18px; scroll-margin-top: calc(var(--nav-h) + 20px); text-wrap: balance;
  }

  .prose h3 {
    font-weight: 500; font-size: 20px; line-height: 1.25;
    letter-spacing: -0.012em; margin: 36px 0 12px; scroll-margin-top: calc(var(--nav-h) + 20px);
  }
  .prose h4 {
    font-weight: 600; font-size: 16px; line-height: 1.3;
    letter-spacing: -0.006em; color: var(--ink);
    margin: 28px 0 10px; scroll-margin-top: calc(var(--nav-h) + 20px);
  }
  /* Optional mono label heading — for short, index-like subheads (AURA arch prose) */

  .prose a {
    border-bottom: 1px solid var(--subtle); padding-bottom: 1px;
    transition: border-bottom-color 0.15s;
  }
  @media (hover: hover) {
    .prose a:hover { border-bottom: 2px solid var(--magic); padding-bottom: 0; }
  }

  .prose strong { font-weight: 500; }
  .prose em { font-style: italic; }
  .prose .braid-emph { background: none; border-bottom: 2px solid var(--magic); padding: 0 0 1px; }

  /* Lists */
  .prose ul, .prose ol { margin: 0 0 24px; padding-left: 1.4em; }
  .prose li { font-size: 18px; line-height: 1.7; letter-spacing: -0.005em; margin-bottom: 8px; padding-left: 4px; }
  .prose li::marker { color: var(--subtle); }
  .prose ul { list-style: none; padding-left: 0; }
  .prose ul > li { position: relative; padding-left: 22px; }
  .prose ul > li::before {
    content: ''; position: absolute; left: 2px; top: 0.62em;
    width: 6px; height: 1px; background: var(--ink);
  }
  .prose li > ul, .prose li > ol { margin-top: 8px; margin-bottom: 8px; }

  /* Blockquote — quiet quote with a yellow accent rule */
  .prose blockquote {
    margin: 32px 0; padding-left: 24px;
    border-left: 3px solid var(--magic);
    font-size: 21px; line-height: 1.5; letter-spacing: -0.012em;
    color: var(--ink); max-width: 58ch;
  }
  .prose blockquote p { font-size: inherit; line-height: inherit; }
  .prose blockquote p:last-child { margin-bottom: 0; }
  .prose blockquote cite {
    display: block; margin-top: 12px; font-style: normal;
    font-family: 'Aeonik Fono', 'Aeonik Fono Fallback', 'SF Mono', Menlo, monospace;
    font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--muted);
  }


  /* Figures — break a little wider than the text measure; never upscale the image */
  /* No width of its own: figures take the same measure as the text via
     `.prose > *`. This was a hard-coded 820px, which put every figure 64px
     narrower than the 884px body column — one of three unrelated literals
     (820 figures, 900 pre, 900 tables) giving the article four staggered
     right edges. */
  .prose figure { margin: 40px 0; }
  .prose figure img {
    display: block; max-width: 100%; height: auto;
    border: 1px solid var(--rule); background: #fff;
  }
  .prose figure svg {
    display: block; width: 100%; height: auto;
    border: 1px solid var(--rule); background: var(--beige);
  }
  /* Captions are flowing prose, so they are laid out as prose.
     This was `display: flex; justify-content: space-between`, which is the right
     shape for a short label/tag pair (the idiom .hero-cap and .artifact-cap use
     elsewhere) but wrong here: in a flex container every text run, every inline
     <a>, and every MathJax \(…\) span becomes its own flex item, and
     space-between then pushes them to opposite edges. A source link ended up
     stranded on its own line with the closing full stop flung to the far right,
     and the Ω symbols in the domain-deformation caption were spread across the
     line with gaps. No figcaption in any article uses .tag, so nothing depended
     on the two-part layout.

     Type follows the same logic: 10px tracked uppercase is a label treatment,
     and it was being applied to sentences — one caption here runs ~60 words.
     The mono uppercase idiom is kept for the figure number, which IS a label,
     and the description is set as readable body text. */
  .prose figcaption {
    display: block;
    margin-top: 14px;
    /* Flush with the figure, not with the prose measure. --measure is 68ch, but
       ch resolves against the element's own font-size: at the caption's 14px
       that is 612px, where the 18px body prose gets 884px. The caption was
       therefore sitting 208px narrower than the 820px image above it, which
       reads as an accident rather than a decision. A caption belongs to its
       figure and should align to its edges. */
    max-width: 100%;
    font-family: 'Aeonik Pro', 'Aeonik Fallback', Arial, sans-serif;
    font-size: 14px; line-height: 1.65; letter-spacing: -0.003em;
    text-transform: none; color: var(--muted);
    text-wrap: pretty;
  }
  /* The figure number: brand mono label, ink so it anchors the caption. */
  .prose figcaption .fig-n {
    font-family: 'Aeonik Fono', 'Aeonik Fono Fallback', 'SF Mono', Menlo, monospace;
    font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase;
    color: var(--ink); margin-right: 9px; white-space: nowrap;
  }

  /* Inline + block code (JetBrains Mono) */
  .prose code {
    font-family: 'JetBrains Mono', ui-monospace, 'SF Mono', Menlo, monospace;
    font-size: 0.85em; background: var(--beige);
    padding: 1px 5px; border-radius: 2px;
    /* Explicit ink rather than inherited: inline code appears inside captions and
       small meta text where the inherited colour is --subtle, and --subtle on the
       --beige chip measures 4.04:1 — under AA. Ink on beige is 12.9:1. */
    color: var(--ink);
  }
  .prose pre {
    max-width: var(--measure); margin: 28px 0; padding: 20px 22px;
    background: #FBF8F6; border: 1px solid var(--rule);
    overflow-x: auto; line-height: 1.6;
  }
  .prose pre code {
    font-size: 13px; background: none; padding: 0; border-radius: 0;
    color: var(--ink); white-space: pre;
  }

  /* Syntax highlighting — pre-rendered by highlight.js at port time (zero runtime
     JS). Restrained, warm light theme tuned to the code panel (#FBF8F6): a small
     harmonious palette rather than a loud rainbow, to match the brand's reserve. */
  /* Contrast measured against the panel, not the page: every colour below clears
     4.5:1 on #FBF8F6. Comment was #8a8a93 (3.24:1) and number #b85c1e (4.33:1) —
     both under AA on the surface a technical reader spends longest on. Darkened
     within the same hue so the palette's character is unchanged; comment stays
     the lightest token, since comments should still recede. */
  .prose .hljs-comment, .prose .hljs-quote { color: #6f6f78; font-style: italic; }
  .prose .hljs-keyword, .prose .hljs-selector-tag,
  .prose .hljs-literal, .prose .hljs-operator, .prose .hljs-meta { color: #9c3587; }
  .prose .hljs-string, .prose .hljs-regexp, .prose .hljs-meta .hljs-string,
  .prose .hljs-addition { color: #17794a; }
  .prose .hljs-number, .prose .hljs-symbol, .prose .hljs-bullet { color: #a04f16; }
  .prose .hljs-title, .prose .hljs-title.function_,
  .prose .hljs-section, .prose .hljs-name,
  .prose .hljs-title.class_, .prose .hljs-type, .prose .hljs-built_in { color: #245bbf; }
  .prose .hljs-attr, .prose .hljs-attribute, .prose .hljs-property,
  .prose .hljs-variable, .prose .hljs-template-variable { color: inherit; }
  /* params was in the `inherit` group above, which made every function signature
     render as undifferentiated ink — 17 tokens in the Python blocks with no
     visible tokenisation. Muted separates the signature from the body without
     adding another hue to a deliberately small palette. */
  .prose .hljs-params { color: var(--muted); }
  /* subst (f-string interpolations, 10 uses) had no rule at all, so it fell
     through to ink by accident. Its children carry their own token colours, so
     inherit is right here — but stated, not implied. */
  .prose .hljs-subst { color: inherit; }
  .prose .hljs-deletion { color: #b3261e; }
  .prose .hljs-strong { font-weight: 600; }
  .prose .hljs-emphasis { font-style: italic; }

  /* Tables */
  .prose .table-wrap { max-width: var(--measure); margin: 28px 0; overflow-x: auto; }
  .prose table { width: 100%; border-collapse: collapse; font-size: 15px; letter-spacing: -0.004em; }
  .prose thead th {
    font-family: 'Aeonik Fono', 'Aeonik Fono Fallback', 'SF Mono', Menlo, monospace;
    font-size: 10px; letter-spacing: 0.12em; text-transform: uppercase;
    color: var(--muted); text-align: left; font-weight: 500;
    padding: 0 20px 12px 0; border-bottom: 1px solid var(--ink);
  }
  .prose tbody td { padding: 12px 20px 12px 0; border-bottom: 1px solid var(--rule); line-height: 1.5; vertical-align: top; }
  .prose tbody tr:last-child td { border-bottom: none; }
  .prose tbody td:last-child, .prose thead th:last-child { padding-right: 0; }

  /* Horizontal rule */
  .prose hr { border: none; border-top: 1px solid var(--rule); margin: 44px 0; max-width: var(--measure); }

  /* Footnote / reference markers + list */
  .prose sup a { border: none; }
  .prose sup a::before { content: '['; }
  .prose sup a::after { content: ']'; }

  /* ── ARTICLE FOOT ───────────────────────────────────────────────────────── */
  /* Contributor credit closing each notebook. Mirrors index.css's .signoff —
     the site's existing idiom for an italic attribution line ending a piece of
     long-form prose.

     The separating rule hangs off the "Contributors" heading, not off the
     paragraph: on the paragraph it drew BETWEEN the heading and the line that
     heading labels, cutting the two apart. :has() lets the markup stay a plain
     <h3>Contributors</h3> with no class or wrapper to remember when a new
     notebook is added. If :has() is unavailable the rule simply does not draw,
     which is a missing hairline, not a broken layout. */
  .prose h3:has(+ .contributors) {
    margin-top: 44px;
    padding-top: 20px;
    border-top: 1px solid var(--rule);
  }
  .prose .contributors {
    font-size: 16px;
    line-height: 1.6;
    font-style: italic;
    color: var(--muted);
  }
  .prose .contributors a { color: inherit; }

  .article-foot {
    border-top: 1px solid var(--ink); margin-top: 64px; padding: 40px 0 88px;
    display: grid; grid-template-columns: 1fr auto; gap: 24px; align-items: baseline;
  }
  .foot-nav { display: flex; gap: 28px; flex-wrap: wrap; }
  .foot-nav a {
    display: inline-flex; align-items: center; gap: 8px;
    font-family: 'Aeonik Fono', 'Aeonik Fono Fallback', 'SF Mono', Menlo, monospace;
    font-size: 12px; letter-spacing: 0.12em; text-transform: uppercase; color: var(--ink);
    border-bottom: 1px solid transparent; padding-bottom: 3px;
    /* WCAG 2.2 AA 2.5.8 needs 24x24. Grown with top padding and pulled back
       by an equal negative margin, so the hit area gains height upward while
       the box, the text baseline and the underline stay exactly where they
       were — measured 22px before, 25px after. */
    padding-top: 3px; margin-top: -3px;
  }
  @media (hover: hover) {
    .foot-nav a:hover { border-bottom-color: var(--magic); }
  }
  .article-foot .meta {
    font-family: 'Aeonik Fono', 'Aeonik Fono Fallback', 'SF Mono', Menlo, monospace;
    font-size: 10px; letter-spacing: 0.16em; text-transform: uppercase;
    color: var(--subtle); text-align: right; line-height: 1.7;
  }

  /* Rail "back to notebooks" link, sits above the rail foot */
  .rail-back {
    display: inline-flex; align-items: center; gap: 8px;
    font-family: 'Aeonik Fono', 'Aeonik Fono Fallback', 'SF Mono', Menlo, monospace;
    font-size: 11px; letter-spacing: 0.12em; text-transform: uppercase;
    color: var(--muted); margin-bottom: 20px;
    border-bottom: 1px solid transparent; padding-bottom: 2px; align-self: start;
    /* WCAG 2.2 AA 2.5.8 needs 24x24. Grown with top padding and pulled back
       by an equal negative margin, so the hit area gains height upward while
       the box, the text baseline and the underline stay exactly where they
       were — measured 19.5px before, 25.5px after. */
    padding-top: 6px; margin-top: -6px;
  }
  @media (hover: hover) {
    .rail-back:hover { color: var(--ink); border-bottom-color: var(--magic); }
  }

  /* Article rails scroll rather than clip (long TOCs). This used to be an
     article-only override; styles.css now applies it to every rail, so the rule
     lives there and this block is intentionally empty of declarations. */

  /* ── MATH (MathJax, on the math article only) ───────────────────────────── */
  .prose .math-display {
    margin: 26px 0; max-width: 100%;
    overflow-x: auto; overflow-y: hidden;   /* long equations scroll, not overflow */
  }
  .prose .math-display mjx-container { margin: 0 !important; }
  .prose mjx-container[display="true"] { text-align: left !important; }
  /* Inline math should never introduce its own line-height jumps */
  .prose mjx-container:not([display="true"]) { display: inline; }

  /* ── RESPONSIVE ─────────────────────────────────────────────────────────── */
  @media (max-width: 960px) {
    .content.article { padding: 0 24px; }
    .article-head { padding: 48px 0 36px; }
    .prose { padding: 36px 0 0; }
    .prose > *, .prose pre, .prose .table-wrap { max-width: 100%; }
    .article-foot { grid-template-columns: 1fr; }
    .article-foot .meta { text-align: left; }
  }
