/* --- Lifted from layout.css by build_pdf.py --- */
/* --- Action buttons ---
   Neutral and hover only. There is deliberately no latched pressed
   state: :active releases the moment the pointer lifts, so a click
   returns the button to neutral immediately. */
.header-actions { display: flex; align-items: center; gap: var(--btn-gap); flex: none; }
.action-button {
  width: var(--btn-size);
  height: var(--btn-size);
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  border-radius: var(--btn-radius);
  background: transparent;
  color: var(--c-icon);
  cursor: pointer;
  transition: background-color var(--duration) var(--ease);
}
.action-button:hover { background: var(--c-btn-hover); }
/* Subtle lift on hover — the icon rises a hair as the bubble drops in. */
.action-button svg { transition: transform var(--duration) var(--ease); }
.action-button:hover svg { transform: translateY(-1px); }
/* --- Tooltip bubble ---
   Lives below the button, revealed on hover and keyboard focus, and
   retracted the moment either ends. aria-hidden because the accessible
   name already sits on the button itself. */
.action-button { position: relative; }
.action-button__bubble {
  position: absolute;
  top: calc(100% + var(--bubble-offset));
  left: 50%;
  padding: var(--bubble-pad);
  border-radius: var(--bubble-radius);
  background: var(--c-bubble-bg);
  color: var(--c-bubble-text);
  box-shadow: var(--shadow-bubble);
  font-family: var(--f-sans);
  font-size: var(--t-tip-size);
  line-height: var(--t-bubble-lh);
  letter-spacing: var(--t-bubble-ls);
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transform: translate(-50%, -4px);
  transition: opacity 140ms var(--ease), transform 140ms var(--ease);
}
.action-button:hover .action-button__bubble,
.action-button:focus-visible .action-button__bubble,
.action-button.is-tipped .action-button__bubble {
  opacity: 1;
  transform: translate(-50%, 0);
}
/* Every label is centred on the icon it names, at every width - the same
   as the print viewer's bar. The last icon sits against the header's
   right edge, so the label is only inside the window because it is set
   at --size-tip; the bar keeps a little clearance for it below. */

/* On touch there is no hover to reveal the label, so a tap does it. */

.action-button:focus-visible {
  outline: 2px solid var(--c-focus);
  outline-offset: 2px;
}
.action-button svg { display: block; width: 18px; height: 18px; }
.action-button svg path { stroke: currentColor; }

/* ============================================================
   FONTS
   ------------------------------------------------------------
   Both families are on Google Fonts; they are served from
   src/fonts/ instead so the reader and the print sheet render
   the same with no network, and so the PDF export embeds real
   outlines rather than whatever the machine falls back to.

   woff2 only - every browser this book targets supports it.
   latin plus latin-ext, because the German legal terms in the
   text reach past the latin subset.

   To add a weight: drop the file in src/fonts/ and add a block
   here. Nothing else refers to font files.
   ============================================================ */

/* --- Roboto Serif — display, headings, footnote markers ---- */
@font-face {
  font-family: 'Roboto Serif';
  font-style: normal;
  font-weight: 400;
  font-display: block;
  src: url('fonts/roboto-serif-latin-400-normal.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: 'Roboto Serif';
  font-style: normal;
  font-weight: 400;
  font-display: block;
  src: url('fonts/roboto-serif-latin-ext-400-normal.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: 'Roboto Serif';
  font-style: normal;
  font-weight: 500;
  font-display: block;
  src: url('fonts/roboto-serif-latin-500-normal.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: 'Roboto Serif';
  font-style: normal;
  font-weight: 500;
  font-display: block;
  src: url('fonts/roboto-serif-latin-ext-500-normal.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: 'Roboto Serif';
  font-style: normal;
  font-weight: 600;
  font-display: block;
  src: url('fonts/roboto-serif-latin-600-normal.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: 'Roboto Serif';
  font-style: normal;
  font-weight: 600;
  font-display: block;
  src: url('fonts/roboto-serif-latin-ext-600-normal.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;
}

/* --- TASA Orbiter — running text, UI, labels --------------- */
@font-face {
  font-family: 'TASA Orbiter';
  font-style: normal;
  font-weight: 400;
  font-display: block;
  src: url('fonts/tasa-orbiter-latin-400-normal.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: 'TASA Orbiter';
  font-style: normal;
  font-weight: 400;
  font-display: block;
  src: url('fonts/tasa-orbiter-latin-ext-400-normal.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: 'TASA Orbiter';
  font-style: normal;
  font-weight: 500;
  font-display: block;
  src: url('fonts/tasa-orbiter-latin-500-normal.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: 'TASA Orbiter';
  font-style: normal;
  font-weight: 500;
  font-display: block;
  src: url('fonts/tasa-orbiter-latin-ext-500-normal.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: 'TASA Orbiter';
  font-style: normal;
  font-weight: 600;
  font-display: block;
  src: url('fonts/tasa-orbiter-latin-600-normal.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: 'TASA Orbiter';
  font-style: normal;
  font-weight: 600;
  font-display: block;
  src: url('fonts/tasa-orbiter-latin-ext-600-normal.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;
}


/* ============================================================
   BRAND
   ------------------------------------------------------------
   Primitives only: the raw palette, the typefaces, the scale.
   Nothing here refers to a place in the UI.

   To rebrand the book, change this file and nothing else.
   tokens.css maps these primitives onto roles ("sidebar
   background", "table header"), and the component styles only
   ever read the roles. That indirection is what makes a colour
   or type change safe.
   ============================================================ */
:root {

  /* --- Blue ramp ------------------------------------------ */
  --blue-50:   #EDF4FF;   /* sidebar, table header, highlight fill */
  --blue-75:   #E0EDFF;   /* chip fill                             */
  --blue-100:  #D4E5FF;   /* hover fill, box border                */
  --blue-150:  #C9DFFF;   /* progress track                        */
  --blue-200:  #BDD7FF;   /* mobile drag bar                       */
  --blue-500:  #146EF5;   /* the brand blue                        */
  --blue-700:  #385D94;   /* icon stroke                           */
  --blue-900:  #002D70;   /* section labels                        */
  --blue-950:  #002660;   /* book title, breadcrumb parent         */
  --blue-ink:  #00112A;   /* sidebar item text                     */

  /* --- Neutral ramp --------------------------------------- */
  --white:     #FFFFFF;
  --grey-25:   #FCFCFC;   /* page background          */
  --grey-50:   #FAFBFC;   /* code block, zebra list    */
  --grey-100:  #F5F6F8;   /* card footer              */
  --grey-150:  #F0F0F1;   /* sidebar, bar, table row  */
  --grey-250:  #DEDEE0;   /* hairline on grey-150     */
  --grey-200:  #E4E7EC;   /* container border         */
  --grey-225:  #D2D5DB;   /* container stroke         */
  --grey-300:  #C8C8C8;   /* footnote rule            */
  --grey-400:  #8F97A6;   /* eyebrow                  */
  --grey-500:  #5B6B85;   /* small labels             */
  --grey-900:  #1C1C1C;   /* body copy                */

  /* Chart fills. Light enough that the value sits on them in navy
     rather than needing reversed type. */
  --blue-125:  #C7DDFF;   /* cost bar, chart 5        */
  --blue-300:  #6FA5FA;   /* chart 4                  */
  --red-125:   #FFD4CE;   /* lost-media bar           */
  --red-700:   #B3261E;   /* figures on a chart fill  */

  /* Status colours. Each solid carries white text at 5:1 or better, and
     each tint carries its own solid at 6:1 or better. */
  --green-800:  #145F30;   /* verdict: full switch     */
  --green-700:  #1E7B3C;   /* verdict: federated       */
  --green-50:   #E9F7EE;   /* green cluster note       */
  --amber-700:  #A05A00;   /* verdict: partial switch  */
  --amber-800:  #8A4B00;   /* amber note text          */
  --amber-50:   #FFF4E5;   /* amber cluster note       */
  --red-800:    #98201A;   /* red note text            */
  --red-50:     #FDECEA;   /* red cluster note         */
  --black:     #000000;

  /* --- Typefaces ------------------------------------------
     Both load from Google Fonts in template.html. Swapping a
     family means changing it in both places.                 */
  --font-serif: 'Roboto Serif', 'PT Serif', Georgia, serif;
  --font-sans:  'TASA Orbiter', 'Inter', system-ui, -apple-system, sans-serif;
  --font-mono:  ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;

  --weight-regular:  400;
  --weight-medium:   500;
  --weight-semibold: 600;

  /* --- Type scale -----------------------------------------
     14px is a hard floor; nothing in the book renders smaller.
     Each step carries its own line height and tracking, because
     tighter tracking on display sizes is part of the design,
     not an accident.

     The whole scale sits four points above where it started: the
     reader is read at arm's length on a wide screen, and every step
     moved together so the relationships between them are unchanged.
     Table cells are the exception - see --size-table below.       */
  --size-display: 48px;  --lh-display: 1.1;  --track-display: -0.05em;
  --size-title:   36px;  --lh-title:   1.0;  --track-title:   -0.05em;
  --size-heading: 28px;  --lh-heading: 1.3;  --track-heading: -0.04em;
  --size-lead:    24px;  --lh-lead:    1.25; --track-lead:    -0.04em;
  --size-body:    20px;  --lh-body:    1.6;  --track-body:    -0.03em;
  --size-small:   18px;  --lh-small:   1.4;  --track-small:   -0.03em;
  --size-ui:      18px;  --lh-ui:      1.3;  --track-ui:      -0.05em;
  --size-min:     18px;
  /* Footnote markers only — the in-text superscript and the numeral in
     the footnote list. Deliberately below --size-min: they are reference
     glyphs, not text to be read. */
  --size-marker:  12px;
  /* Smaller again on a phone, where the marker sits in 16px prose. */
  --size-marker-sm: 10px;
  /* Footnote text, also below the floor by choice: sources and links
     are apparatus, read on purpose rather than in the flow. */
  --size-footnote: 16px;
  --size-caption: 18px;
  /* The figure in a stat square, and the same figure in the stacked
     variant where the row is tighter. */
  /* Held back from the step with the tables and the chrome: a figure
     column has to hold its longest value on one line, and four points
     put 'Up to 94%' onto two and the headcount sentence onto three. */
  --size-stat:    30px;
  --size-stat-sm: 28px;
  /* --- Table cells ----------------------------------------
     A cell reads at the same size as the prose around it. Holding them
     back kept the columns inside the text measure, and the price was a
     table set two steps under the paragraph that introduces it; the
     tables are let out past the measure instead - see .article__wide in
     article.css - and the type follows the body.
     --size-table-cap is the grade table's own header row.           */
  --size-table:     20px;
  --size-table-sm:  20px;
  /* The comparison matrix runs eight columns wide and sets its header row
     a step under the cells - that is what this size is for. */
  --size-table-cap: 16px;
  /* --- Chrome ---------------------------------------------
     The sidebar and the bar across the top, also held back from the
     step. They are not the read: they are the furniture around it, and
     furniture that grows with the text stops being furniture. The
     contents list has to hold thirty rows in a fixed column without
     wrapping every one of them, and a label in the bar has a pill to
     stay inside. Same values the scale had before it moved.        */
  --size-chrome:    16px;
  --size-chrome-sm: 14px;
  --size-chrome-lg: 32px;
  /* The label that appears under an icon on hover. Below the floor by
     choice, and for the same reason as the footnote apparatus: it is the
     icon's name, read for a second and then gone, not text in the page.
     Small also keeps it inside the window - the icons sit at the end of
     the bar and the label is centred on the one it belongs to. */
  --size-tip:       12px;

  /* --- Shape and depth ------------------------------------ */
  --radius-sm:  6px;
  --radius-md:  8px;
  --radius-pill: 100px;

  --shadow-raised: 0 4px 4px rgba(201, 201, 201, 0.25);
  --shadow-float:  0 4px 19.7px rgba(157, 157, 157, 0.24);

  /* --- Motion ---------------------------------------------
     All of it is suppressed under prefers-reduced-motion.     */
  --speed-fast:   120ms;
  /* Long enough for a state under the cursor to be seen arriving,
     short enough that it never delays the pointer. */
  --speed-hover:  200ms;
  --speed-medium: 340ms;
  --speed-slow:   720ms;
  --ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
  --ease-out-soft: cubic-bezier(0.22, 1, 0.36, 1);
}

/* ============================================================
   THE SCALE ON A PHONE
   One place, one set of numbers. Every component in the book is
   built from these primitives, so a phone gets a coherent scale
   by restating the scale rather than by patching thirty
   components one at a time - which is how the reader ended up
   with 16px prose under a 24px component header.

   The steps keep their desktop relationships and all move down
   together: body 16, the blue component header a step above it,
   the small print a step below.

   The breakpoint is the reader's own, the one mobile.css uses.
   The print bundle restates every one of these in pdf.css after
   this file, so a sheet is never touched by this block.
   ============================================================ */
/* Between the phone layout and a full desktop the sidebar takes a fixed
   slice of the window, so the reading pane - which is what bounds how far
   a table may reach - is only a little wider than the text measure.
   Body-sized cells do not fit there, so in this band the cells stay where
   they were. The step comes in at 1440, where the pane finally has the
   room for it. */
@media (min-width: 1024px) and (max-width: 1439px) {
  :root {
    --size-table:     16px;
    --size-table-sm:  16px;
    --size-table-cap: 14px;
  }
}

@media (max-width: 1023px) {
  :root {
    --size-display: 34px;
    --size-title:   28px;
    --size-heading: 22px;
    --size-lead:    18px;
    --size-body:    16px;
    --size-small:   14px;
    --size-ui:      14px;
    --size-min:     14px;
    --size-caption: 14px;
    --size-footnote: 14px;
    --size-stat:    24px;
    --size-stat-sm: 22px;
    /* A stacked row is a block of its own with the whole width to use,
       so a cell reads at the body size here too. */
    --size-table:     16px;
    --size-table-sm:  16px;
    --size-table-cap: 16px;
  }
}


/* ============================================================
   TOKENS
   ------------------------------------------------------------
   Roles, not values. Every component reads from here; nothing
   reads a hex code directly.

   Geometry originates in Figma (layout-desktop, node 569:884).
   ============================================================ */
:root {

  /* ---------- Colour: surfaces ---------- */
  --c-page-bg:        var(--grey-25);
  --c-sidebar-bg:     var(--grey-150);
  --c-topbar-bg:      var(--white);
  --c-foot-bg:        var(--grey-100);
  /* grey-50 sits ~2% off white, which reads as no stripe at all once a
     table is more than a few rows deep. */
  --c-row-alt:        var(--grey-150);
  --c-bar-track:      var(--grey-100);
  --c-bar-cost:       var(--blue-125);
  --c-bar-lost:       var(--red-125);
  --c-bar-text-cost:  var(--blue-500);
  --c-bar-text-lost:  var(--red-700);
  --c-case-bg:        var(--blue-50);
  --c-case-rule:      var(--blue-125);
  --c-cal-head-bg:    var(--blue-50);
  --c-cal-num-bg:     var(--blue-500);
  --c-cal-num-fg:     var(--white);
  --c-mark-bg:        var(--blue-100);
  --c-logo-tip-bg:    var(--blue-950);
  --c-logo-tip-fg:    var(--white);
  --c-quote-from:     var(--blue-500);
  --c-quote-to:       var(--blue-950);
  --c-quote-text:     var(--white);
  --c-check:          var(--blue-500);
  --c-perm-lock:      var(--grey-400);
  --c-grade-weak:     var(--red-700);

  /* Cluster notes: a tint with its own text colour. */
  --c-note-green-bg:  var(--green-50);
  --c-note-green-fg:  var(--green-800);
  --c-note-amber-bg:  var(--amber-50);
  --c-note-amber-fg:  var(--amber-800);
  --c-note-red-bg:    var(--red-50);
  --c-note-red-fg:    var(--red-800);
  /* Verdicts: a solid with white text. */
  --c-verdict-full:   var(--green-800);
  --c-verdict-fed:    var(--green-700);
  --c-verdict-part:   var(--amber-700);
  --c-verdict-stay:   var(--red-700);
  --c-verdict-fg:     var(--white);
  --c-cluster-bg:     var(--blue-50);
  --c-rule-soft:      var(--grey-200);

  /* Partner tiers: one ladder, read as a cascade from the entry rung to
     the premium one. Each step keeps its own text colour so the light
     rungs stay as readable as the dark ones. */
  /* Five steps, brightest first, so the biggest share reads first. */
  --c-slice-1:        var(--blue-500);
  --c-slice-2:        var(--blue-950);
  --c-slice-3:        var(--blue-700);
  --c-slice-4:        var(--blue-300);
  --c-slice-5:        var(--blue-125);
  --c-stat-solid-bg:  var(--blue-500);
  --c-stat-solid-fg:  var(--white);
  --c-box-bg:         var(--blue-50);
  --c-table-head:     var(--blue-500);
  /* The drag bar sits on a sidebar of the same colour, so the hairline
     is what separates them. Same neutral family, one step darker. */
  --c-handle:         var(--grey-150);
  --c-handle-line:    var(--grey-250);
  --c-chip-bg:        var(--blue-75);
  --c-code-bg:        var(--grey-50);

  /* ---------- Colour: text ---------- */
  --c-brand:          var(--blue-500);
  --c-prose:          var(--grey-900);
  --c-level-1:        var(--blue-900);   /* sidebar section labels */
  --c-level-2:        var(--blue-500);   /* sidebar chapter labels */
  --c-level-3:        var(--blue-ink);   /* sidebar item labels    */
  --c-book-title:     var(--blue-950);
  --c-chapter-num:    var(--blue-500);
  --c-chapter-name:   var(--black);
  --c-chapter-title:  var(--black);
  --c-crumb-parent:   var(--blue-950);
  --c-crumb-current:  var(--blue-500);
  --c-eyebrow:        var(--grey-400);
  --c-label:          var(--grey-500);
  --c-deep:           var(--blue-950);

  /* ---------- Colour: lines and edges ---------- */
  --c-rule:           var(--grey-300);   /* footnote separator */
  --c-card-border:    var(--grey-225);   /* cards and images   */
  --c-border:         var(--grey-200);
  --c-box-border:     var(--blue-100);
  --c-scrollbar:      var(--blue-150);
  --c-progress-track: var(--blue-150);
  --c-progress-fill:  var(--blue-500);
  --c-zebra:          var(--grey-50);
  --c-meta:           var(--grey-400);

  /* ---------- Colour: controls ---------- */
  --c-icon:           var(--blue-700);
  --c-btn-hover:      var(--blue-100);
  --c-focus:          var(--blue-500);
  --c-bubble-bg:      var(--blue-950);   /* dark navy, not brand blue */
  /* A footnote tooltip is a link, so it keeps the brand blue that says
     so, while the icon tooltips stay navy. */
  /* The panel a footnote opens on hover. Light blue with the ink of the
     sidebar's own labels on it, and a dark shadow held at a low opacity:
     enough depth to lift the panel off the page, not enough to read as a
     drop shadow in its own right. */
  --c-fn-tip-bg:      var(--blue-50);
  --c-fn-tip-text:    var(--blue-ink);
  --c-fn-tip-border:  var(--blue-100);
  --shadow-fn-tip:    0 6px 18px rgb(0 17 42 / 0.14),
                      0 1px 3px rgb(0 17 42 / 0.10);
  --c-takeaway-bg:    var(--blue-950);
  --c-takeaway-text:  var(--white);
  --c-pull-quote:     var(--blue-500);
  --c-bubble-text:    var(--white);

  /* Expressed as channels so the gradient can vary alpha. Matched to
     the pane so the ramp dies with no seam; use 255 255 255 for a
     pure-white fade. */
  --c-fade-rgb: 252 252 252;

  /* ---------- Print-view viewer chrome ---------- */
  --c-viewer-bar-bg:    var(--grey-150);
  /* Same hairline the mobile drag bar uses - the pill sits on the same
     grey, so it takes the same edge. */
  --c-viewer-bar-line:  var(--grey-250);
  --c-viewer-btn-bg:    var(--blue-500);
  --c-viewer-btn-hover: var(--blue-700);
  --c-viewer-btn-text:  var(--white);
  --c-viewer-card-bg:   var(--white);
  --c-viewer-card-line: var(--blue-150);
  /* One surface behind the sheets, and the fade ramps to exactly it.
     base.css paints the body --c-page-bg (#FCFCFC) for the reader; the
     print viewer needs its own, darker surface so a white sheet reads
     as a sheet, and the two must not disagree or the backdrop shows a
     seam where the band ends. Keep these two in step. */
  /* Same surface as the reader's sidebar, so the two views read as one
     product. Keep the fade channels in step with it. */
  --c-viewer-surface:  var(--grey-150);
  --c-viewer-fade-rgb: 240 240 241;   /* = --grey-150 */

  /* ---------- Typefaces ---------- */
  --f-serif: var(--font-serif);
  --f-sans:  var(--font-sans);
  --f-mono:  var(--font-mono);
  --w-regular:  var(--weight-regular);
  --w-medium:   var(--weight-medium);
  --w-semibold: var(--weight-semibold);
  --t-min-size: var(--size-min);
  --t-marker-size: var(--size-marker);   /* footnote reference marker */
  --t-footnote-size: var(--size-footnote);
  --t-caption-size: var(--size-caption);  /* figure captions */
  --t-lead-size:    var(--size-lead);     /* blue component headers */
  --t-pull-size:     var(--size-lead);    /* mobile.css steps it down */

  /* ---------- Type: table cells ----------
     The one place the scale's four-point step does not reach: a cell's
     width is its column's width, and the tables were balanced at these
     sizes. --t-table-size is a cell that reads as prose, --t-table-sm-size
     the data table's own tighter cell, --t-table-cap-size the grade
     table's small header row. */
  --t-table-size:     var(--size-table);
  --t-table-sm-size:  var(--size-table-sm);
  --t-table-cap-size: var(--size-table-cap);

  /* ---------- Type: article ---------- */
  /* The chapter h1 sits on the title step, not the display step above
     it. It keeps the display line height: --lh-title is 1.0, which
     collides the chapter number with the title on a two-line heading. */
  --t-h1-size:   var(--size-title);   --t-h1-lh:   var(--lh-display); --t-h1-ls:   var(--track-display);
  --t-h2-size:   var(--size-heading); --t-h2-lh:   var(--lh-heading); --t-h2-ls:   var(--track-heading);
  --t-body-size: var(--size-body);    --t-body-lh: var(--lh-body);    --t-body-ls: var(--track-body);
  --t-card-size: var(--size-small);
  --t-figure-size: var(--size-title);          /* number highlights */
  --t-byline-size: var(--size-lead);   --t-byline-lh: var(--lh-lead);   /* author bylines */

  /* ---------- Type: sidebar ----------
     Built from the chrome sizes, not the article's: the contents list
     and the bar stay where they were when the reading type stepped up.
     Line heights and tracking are shared with the rest of the scale -
     only the sizes are held back. */
  --t-l1-size:   var(--size-chrome);    --t-l1-lh:   var(--lh-ui);    --t-l1-ls:   var(--track-ui);
  --t-l2-size:   var(--size-chrome-sm); --t-l2-lh:   var(--lh-ui);    --t-l2-ls:   var(--track-ui);
  --t-l3-size:   var(--size-chrome-sm); --t-l3-lh:   var(--lh-small); --t-l3-ls:   var(--track-small);
  --t-book-size: var(--size-chrome-lg); --t-book-lh: var(--lh-title); --t-book-ls: var(--track-title);
  --t-sub-size:  var(--size-chrome-sm); --t-sub-lh:  var(--lh-ui);    --t-sub-ls:  var(--track-small);
  --t-prog-size: var(--size-chrome-sm); --t-prog-lh: var(--lh-ui);    --t-prog-ls: var(--track-ui);

  /* ---------- Type: chrome ----------
     The breadcrumb, and every tooltip bubble - the one under an action
     button, the footnote's, the logo wall's. One component, one size. */
  --t-crumb-size:  var(--size-chrome-sm); --t-crumb-lh:  var(--lh-ui); --t-crumb-ls:  var(--track-heading);
  --t-bubble-size: var(--size-chrome-sm); --t-bubble-lh: var(--lh-ui); --t-bubble-ls: var(--track-heading);
  /* An action icon's own label is set smaller than the tooltips that
     carry a sentence - the footnote's and the logo wall's keep
     --t-bubble-size. */
  --t-tip-size:    var(--size-tip);

  /* ---------- Shape and depth ---------- */
  --radius:        var(--radius-md);
  --btn-radius:    var(--radius-md);
  --bubble-radius: var(--radius-sm);
  /* Temporarily flat. Restore with var(--shadow-raised). */
  --shadow-topbar: none;
  --shadow-bubble: var(--shadow-float);


  /* ---------- The contents rule ----------
     The line that passes under a row in the contents on hover. A section
     draws it in its own colour; a chapter draws it as the brand gradient,
     left to right, so the two levels read apart at a glance. */
  /* The lit colour: what a contents row goes when it is pointed at or is
     the row the reader is on. The rule tokens that used to sit beside it
     went with the underline. */
  --toc-lit:          var(--c-brand);

  /* ---------- Motion ---------- */
  --duration:        var(--speed-fast);
  --hover-duration:  var(--speed-hover);
  --dd-duration:     var(--speed-medium);
  --ease:            var(--ease-standard);
  --reveal-duration: var(--speed-slow);
  --reveal-stagger:  90ms;
  --reveal-ease:     var(--ease-out-soft);

  /* ============================================================
     LAYOUT
     The sidebar is fluid and the reading column is fixed, so the
     window gains or loses width on the left, never in the measure.
     ============================================================ */
  --sidebar-w:   clamp(360px, 27vw, 400px);
  --article-w:   744px;
  /* Prose fills the column. It used to stop at 616px inside a 744px
     column, which left running text ragged against the full-width
     cards and tables sitting above and below it. */
  --measure-w:   var(--article-w);
  --article-top: 122px;
  --inset-x:      24px;
  --inset-top:    29px;

  --topbar-h:        62px;
  --topbar-inset-x:  26px;
  --btn-size:        32px;
  --btn-gap:         12px;
  --crumb-gap:       10px;
  --bubble-pad:       6px;
  --bubble-offset:    8px;
  --scrollbar-w:      6px;
  --chevron-size:    24px;

  --logo-webflow-w: 118px;
  --logo-magier-w:  116px;

  /* ---------- Rhythm ---------- */
  --gap-block:          36px;  /* sidebar blocks                   */
  --gap-row:            20px;  /* rows within a sidebar group      */
  --gap-section-group:  32px;  /* between Introduction / Sections  */
  --gap-section:        46px;  /* between article sections         */
  --gap-heading:        29px;  /* heading to body                  */
  --gap-para:           20px;  /* paragraph rhythm                 */
  --gap-block-el:       22px;  /* extra air around tables, figures */
  --gap-verdict:        32px;  /* between the verdicts in a totals box */
  --gap-standfirst:     36px;  /* chapter title to standfirst      */
  --gap-chapter-line:    4px;  /* chapter number to chapter title  */
  --indent-item:        12px;  /* items under a section label      */
  --chapter-num-w:      17px;  /* reserved for "01".."13"          */
  --chapter-num-gap:    14px;
  --section-clearance:  72px;  /* read by app.js for scroll offset */

  /* ---------- Reading pane fades ---------- */
  /* How far a table's frame reaches past the text measure on each side.
     Enough that the columns breathe at body size, close enough that the
     table still reads as belonging to the column it interrupts. */
  --table-bleed: 140px;

  --fade-top:     56px;
  --fade-bottom: 104px;

  /* ============================================================
     MOBILE  (< 1024px)
     --mobile-header-h is read by app.js for the drawer's collapsed
     height. Change it here, not in the script.
     ============================================================ */
  /* 194px puts the drag bar's top edge just above the book title, which
     sits at 173px in the collapsed drawer. At 210px the title's first
     13px showed under the progress bar. */
  --mobile-header-h: 194px;
  /* The drawer's open height. The stylesheet is the only place it is
     decided; app.js measures the result rather than recomputing it. */
  --mobile-drawer-h: min(88vh, 760px);
  --mobile-topbar-h:  64px;
  --mobile-inset:     24px;
  --handle-h:         24px;
}


/* ============================================================
   RESET
   ============================================================ */
*, *::before, *::after { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0;
  background: var(--c-page-bg);
  font-family: var(--f-sans);
  /* Caveat: never hyphenate, never break words mid-word. */
  hyphens: none;
  -webkit-hyphens: none;
  overflow-wrap: normal;
  word-break: normal;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
ul { list-style: none; margin: 0; padding: 0; }
h1, h2, h3, p { margin: 0; }
a { text-decoration: none; color: inherit; }

/* A word that arrives hyphenated is marked at build time rather than
   left to the browser, which would treat the hyphen as a legal break and
   produce something that reads exactly like hyphenation. */
/* A table cell used to be exempted, on the grounds that a column has no
   width to hold a long compound whole. It reads exactly like the
   hyphenation being avoided, though, wherever it happens - so cells keep
   their compounds whole too, and the columns are sized to take them. */
.nb { white-space: nowrap; }

/* Text that is there for a screen reader and not for the page. */
.u-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}


/* ============================================================
   ARTICLE
   ============================================================ */
.content {
  padding: var(--article-top) 0 160px;
  overscroll-behavior: contain;
}

.article {
  width: var(--article-w);
  max-width: calc(100% - 96px);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  gap: var(--gap-section);
}

.chapter {
  display: flex;
  flex-direction: column;
  gap: var(--gap-section);
  scroll-margin-top: var(--section-clearance);
  /* Explicit, so a short view cannot collapse narrower than the column. */
  width: 100%;
  align-self: stretch;
  min-width: 0;
}
.chapter > *,
.chapter-intro,
.section__body { width: 100%; min-width: 0; }
/* Chapters are all in the document; only the current one is rendered. */
.chapter[hidden] { display: none; }
/* A section opener is a short front page for the chapters beneath it. */
.chapter--section .chapter-heading__num { color: var(--c-chapter-num); }

.article section {
  display: flex;
  flex-direction: column;
  gap: var(--gap-heading);
  /* Clearance so a targeted heading never lands against the top bar. */
  scroll-margin-top: var(--section-clearance);
}

/* Prose runs the full column width, same as the tables and figures. */
.article p,
.article ul,
.article ol,
.footnotes__list { max-width: var(--measure-w); }
/* Except inside a box that has stepped outside the column: the box set
   its own width, and prose capped at the measure left a quarter of it
   empty on the right. What is in there is reference text read a line at a
   time - a verdict, a score band - not a page of running prose. */
.cluster p,
.cluster ul,
.cluster ol { max-width: none; }

/* --- Level 1 --- */
.chapter-intro {
  display: flex;
  flex-direction: column;
  gap: var(--gap-standfirst);
}
.chapter-heading {
  font-family: var(--f-serif);
  font-weight: var(--w-medium);
  font-size: var(--t-h1-size);
  line-height: var(--t-h1-lh);
  letter-spacing: var(--t-h1-ls);
  text-wrap: balance;
}
.chapter-heading__num   { display: block; color: var(--c-chapter-num); }
.chapter-heading__title { display: block; color: var(--c-chapter-title); }

.chapter-standfirst {
  font-family: var(--f-sans);
  font-size: var(--t-body-size);
  line-height: var(--t-body-lh);
  letter-spacing: var(--t-body-ls);
  color: var(--c-prose);
  text-wrap: pretty;
}

/* --- Level 2 --- */
.section-heading {
  font-family: var(--f-serif);
  font-weight: var(--w-medium);
  font-size: var(--t-h2-size);
  line-height: var(--t-h2-lh);
  letter-spacing: var(--t-h2-ls);
  color: var(--c-prose);
  text-wrap: balance;
}

/* --- Level 3 --- */
.article p,
.article li,
.article td,
.article th,
.figure figcaption,
.footnotes__list {
  font-family: var(--f-sans);
  font-weight: var(--w-regular);
  font-size: var(--t-body-size);
  line-height: var(--t-body-lh);
  letter-spacing: var(--t-body-ls);
  color: var(--c-prose);
}
/* A cell is set a step below the prose: its width is its column's width,
   so type that grows takes the measure with it and the balance the
   columns were set to comes apart. Written after the rule above rather
   than lifted out of it, so a cell still inherits everything else. */
.article td,
.article th { font-size: var(--t-table-size); }
.article p { text-wrap: pretty; }
.section__body { display: flex; flex-direction: column; gap: var(--gap-para); }
/* Block elements need more separation from running text than paragraphs
   need from each other. */
/* --- A table takes the room it needs ---------------------------------
   The measure is set for prose - 744px is about the number of words an
   eye tracks comfortably across a line - and a table is not read that
   way. Now that cells are set at body size, holding a seven-column
   matrix inside the paragraph column would squeeze every one of them.
   A table's frame steps outside the measure instead - by a set amount
   either side rather than filling the pane, which on a wide window left
   a three-column table stretched across a metre of screen.

   The step is capped by the pane less 24px, so a narrow window keeps its
   margin. --pane-w is written onto the article by app.js, because the
   pane is the scroll container and its width is not something CSS in
   here can reach; without it the rule falls back to the measure and
   nothing moves. The negative margins are percentages of the column, so
   the frame stays centred on it however wide it grows.

   A section divider is a short front page rather than a chapter, and its
   two-column table reads with the text: it keeps the measure. */
.chapter:not(.chapter--section) .section__body > .card:has(.dtable),
.chapter:not(.chapter--section) .section__body > .chart-box:has(.dtable),
.chapter--appendix .section__body > .card:has(.dtable),
.chapter--appendix .section__body > .chart-box:has(.dtable),
/* A cluster box holds a worksheet table, so the box is what steps out and
   the table fills it. Widening the table inside a fixed-width box would
   only have pushed it past the box's own edge. */
.chapter--appendix .section__body > .cluster:has(.dtable) {
  --wide-w: max(100%, min(calc(var(--measure-w) + var(--table-bleed) * 2),
                          calc(var(--pane-w, 100%) - 48px)));
  width: var(--wide-w);
  /* The frame carries the measure as its own ceiling, which is the thing
     being lifted here. */
  max-width: var(--wide-w);
  margin-inline: calc((100% - var(--wide-w)) / 2);
}

.section__body > .card,
.section__body > .figure,
.section__body > .highlights,
.section__body > .file-card,
.section__body > .numbered,
.section__body > .byline,
.section__body > .logo-set,
.section__body > .code-card { margin-block: var(--gap-block-el); }
/* Two of them in a row would stack both margins - flex margins do not
   collapse - and 22 above plus 22 below on top of the flow gap reads as a
   section break between two blocks that belong together. The second drops
   its own. One rule rather than every pair spelled out: `:is()` takes its
   specificity from a single class, so this still outranks the list above. */
.section__body
  > :is(.card, .figure, .highlights, .file-card, .numbered, .byline,
        .logo-set, .code-card)
  + :is(.card, .figure, .highlights, .file-card, .numbered, .byline,
        .logo-set, .code-card) { margin-block-start: 0; }
/* A second foreword opens inside the same section, so its heading has a
   paragraph above it rather than a section boundary. The flow gap alone
   reads as though the heading belonged to that paragraph; this gives it
   the same clearance a section heading gets, the flow gap included. */
.section__body > * + .section-heading {
  margin-block-start: calc(var(--gap-section) - var(--gap-para));
}
.section__body > :first-child { margin-block-start: 0; }
.section__body > :last-child { margin-block-end: 0; }
.article strong { font-weight: var(--w-semibold); }
/* Italics are not used in this design. */
.article em { font-style: normal; }


/* First-pass table styles removed — superseded by .card / .dtable. */

/* --- Image placeholders --- */
.figure { display: flex; flex-direction: column; gap: 12px; margin: 0; }
.figure__placeholder {
  border: 2px dotted var(--c-rule);
  border-radius: 4px;
  aspect-ratio: 768 / 546;
  display: grid;
  place-items: center;
  color: var(--c-rule);
  font-family: var(--f-sans);
  font-size: var(--size-small);
  letter-spacing: var(--t-body-ls);
}
.figure__image {
  display: block;
  /* Every figure fills the measure. An export narrower than the column
     is drawn up to it rather than sitting short of the right margin: an
     empty half-inch beside a picture reads as a mistake where a slightly
     soft edge does not. --fig-max still carries each asset's own pixel
     width for the print build to work from. The width stays definite, so
     a lazy image reserves its box before it loads. */
  width: 100%;
  height: auto;
}
.figure figcaption { max-width: var(--measure-w); }
/* Captions are apparatus, not prose: they sit below the 14px floor on
   purpose, the same exemption the footnote list has. */
.figure figcaption,
.figure--chart figcaption { font-size: var(--t-caption-size); }

/* --- Footnotes ---
   Footnote numbering, not ordinary list numbering: the marker is a
   superscript numeral matching the reference in the running text, and
   the text hangs clear of it. */
.footnotes { display: flex; flex-direction: column; gap: 16px; margin-top: 8px; }
.footnotes__rule {
  border: 0;
  border-top: 1px solid var(--c-rule);
  margin: 0;
  width: 100%;
}
.footnotes__list {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding-left: 0;
  list-style: none;
  counter-reset: footnote;
}
.footnotes__list li {
  counter-increment: footnote;
  /* Hanging indent sized to the marker, not to body type. */
  padding-left: 20px;
  position: relative;
}
.footnotes__list li::before {
  content: counter(footnote);
  position: absolute;
  left: 0;
  /* Matches the in-text marker: a reference glyph at --t-marker-size,
     raised like a superscript. nowrap keeps two-digit numbers whole
     inside the narrow hanging indent. */
  font-size: var(--t-marker-size);
  font-weight: var(--w-semibold);
  /* line-height 1 plus a fixed top offset, so the marker keeps its
     optical position against the first text line if the size changes. */
  line-height: 1;
  top: 4px;
  white-space: nowrap;
  color: var(--c-brand);
}
/* --- Pull quote ---
   A line lifted out of the argument. Not the boxed .highlight--quote:
   this one stays in the flow and just carries weight. */
.article .pull-quote {
  font-weight: var(--w-semibold);
  font-size: var(--t-pull-size);
  line-height: 1.4;
  color: var(--c-pull-quote);
}

/* Footnote text is apparatus, not running prose, so it sits below the
   14px floor. Two classes deep on purpose: mobile.css steps
   `.article li` to 14px, and without the extra weight that rule would
   win here. */
.article .footnotes__list,
.article .footnotes__list li { font-size: var(--t-footnote-size); }

.footnotes a { color: var(--c-brand); text-decoration: underline; }
.footnotes__backref { text-decoration: none; }

/* The marker is a reference glyph, not prose: it sits at --t-marker-size,
   below the 14px floor by design, and never wraps away from the word or
   the punctuation it annotates. */
.footnote-ref {
  font-size: var(--t-marker-size);
  font-weight: var(--w-semibold);
  vertical-align: super;
  line-height: 0;
  white-space: nowrap;
}
.footnote-ref a { color: var(--c-brand); text-decoration: none; }
/* An explanatory note cites no source, so its marker is not a link -
   same numeral, but it carries the note in the tooltip and reaches the
   keyboard through tabindex rather than through an href. */
.footnote-ref__note {
  color: var(--c-brand);
  cursor: help;
  outline-offset: 2px;
}

/* --- Footnote tooltip ---
   The reference links straight to its source now, so the tooltip says
   what that source is before the reader commits to opening it. It lives
   inside the anchor: hovering it keeps it open, and clicking it follows
   the same link as the numeral.

   Same reveal as the top bar's buttons, opening upward - a marker sits
   mid-paragraph, and a tooltip dropping down would cover the line being
   read. */
.footnote-ref { position: relative; }
.footnote-tip {
  position: absolute;
  bottom: calc(100% + var(--bubble-offset));
  left: 50%;
  z-index: 5;
  display: flex;
  flex-direction: column;
  gap: 2px;
  width: max-content;
  max-width: 320px;
  padding: 8px 10px;
  border: 1px solid var(--c-fn-tip-border);
  border-radius: var(--bubble-radius);
  background: var(--c-fn-tip-bg);
  color: var(--c-fn-tip-text);
  box-shadow: var(--shadow-fn-tip);
  font-family: var(--f-sans);
  font-size: var(--t-bubble-size);
  line-height: var(--t-bubble-lh);
  letter-spacing: var(--t-bubble-ls);
  text-align: left;
  white-space: normal;
  /* The marker is superscript with no line box of its own; the tooltip
     must not inherit that. */
  vertical-align: baseline;
  opacity: 0;
  visibility: hidden;
  transform: translate(-50%, 4px);
  transition: opacity 140ms var(--ease), transform 140ms var(--ease),
              visibility 0s linear 140ms;
}
.footnote-ref a:hover .footnote-tip,
.footnote-ref a:focus-visible .footnote-tip,
.footnote-ref__note:hover .footnote-tip,
.footnote-ref__note:focus-visible .footnote-tip {
  opacity: 1;
  visibility: visible;
  transform: translate(-50%, 0);
  transition: opacity 140ms var(--ease), transform 140ms var(--ease),
              visibility 0s linear 0s;
}
/* The tooltip floats 8px clear of the marker, so the cursor would cross
   dead space on its way up and drop the hover before arriving. This pad
   joins the two hit areas; the tooltip sits inside the <a>, so hovering
   it keeps the link hovered and the tooltip open long enough to click. */
.footnote-tip::after {
  content: '';
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  height: var(--bubble-offset);
}
.footnote-tip__title { font-weight: var(--w-medium); }
/* A note runs to a few sentences where a citation is one line, so it is
   given room to be read rather than squeezed into a citation's width. */
.footnote-tip--note { max-width: 360px; }
.footnote-tip--note .footnote-tip__title { font-weight: var(--w-regular); }
/* Underlined because the whole tooltip is part of the link. On the light
   panel the address takes the brand blue rather than a dimmed ink: at 0.9
   opacity on white it read as a link, on pale blue it reads as faded. */
.footnote-tip__url { color: var(--c-brand); text-decoration: underline; }


/* ============================================================
   EDITORIAL COMPONENTS
   ============================================================ */

/* No borders anywhere in this system — separation comes from fills and
   row tints. The only 1px border in the article is on the image. */

/* --- Card shell --- */
.card {
  border: 1px solid var(--c-card-border);
  border-radius: var(--radius);
  background: var(--c-topbar-bg);
  overflow: hidden;          /* clips the header band to the radius */
}
/* Except where the card holds a table. A footnote in a cell hangs its
   tooltip above the row, and a card that clips cuts the tooltip off at
   its own frame - the reference is then unreadable in exactly the place
   a reference is most needed. The corner cells take the radius instead,
   which is all the clipping was ever doing. */
.card:has(> .dtable),
.card:has(> .dtable--fixed) { overflow: visible; }
.card > .dtable thead th:first-child { border-top-left-radius: var(--radius); }
.card > .dtable thead th:last-child  { border-top-right-radius: var(--radius); }
.card__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 24px;
  padding: 14px 16px;
}
.card__title {
  font-family: var(--f-serif);
  font-weight: var(--w-medium);
  font-size: var(--size-body);
  line-height: 1.3;
  letter-spacing: -0.03em;
  color: var(--c-prose);
}
.card__eyebrow {
  font-family: var(--f-sans);
  font-size: var(--size-small);
  letter-spacing: var(--t-body-ls);
  color: var(--c-eyebrow);
  white-space: nowrap;
  flex: none;
}
.card__foot {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 24px;
  padding: 12px 16px;
  background: var(--c-foot-bg);
  font-family: var(--f-sans);
  font-size: var(--t-card-size);
  line-height: 1.5;
  letter-spacing: var(--t-body-ls);
  color: var(--c-prose);
}
.card__foot strong { font-weight: var(--w-semibold); white-space: nowrap; }

/* --- Data table ---
   Everything is left-aligned. Tight padding so short cells don't sit in
   a field of empty space. */
.dtable { width: 100%; border-collapse: collapse; }
.dtable th,
.dtable td { text-align: left; }
.dtable thead th {
  background: var(--c-brand);
  padding: 9px 16px;
  font-family: var(--f-sans);
  font-weight: var(--w-medium);
  font-size: var(--t-table-sm-size);
  letter-spacing: var(--t-body-ls);
  color: var(--c-bubble-text);
}
.dtable tbody td {
  padding: 10px 16px;
  vertical-align: top;
  font-family: var(--f-sans);
  font-size: var(--t-table-sm-size);
  line-height: 1.5;
  letter-spacing: var(--t-body-ls);
  color: var(--c-prose);
}
.dtable tbody tr:nth-child(even) td { background: var(--c-row-alt); }

/* Column sizing lives with the table, not with the cell class.
   A fixed width and `nowrap` on a helper class was fine while every
   labelled cell held two words; once a cell holds a sentence, the width
   squeezes it into a stack of three-word lines while the neighbouring
   column runs half empty. Tables that need a specific balance carry a
   colgroup and `dtable--fixed`; the rest are sized by their content. */
.dtable--fixed { table-layout: fixed; }
.dtable__key    { color: var(--c-brand); white-space: nowrap; }
.dtable__label  { font-weight: var(--w-semibold); }
.dtable__result { color: var(--c-brand); }
/* For the short figures that genuinely must not break. */
.dtable__nowrap { white-space: nowrap; }
.dtable__note {
  display: block;
  margin-top: 2px;
  font-size: var(--t-table-sm-size);
  color: var(--c-eyebrow);
}

/* --- Matrix card ---
   A comparison grid wider than the 744px column. The card scrolls
   sideways rather than dropping or stacking columns, because in a matrix
   the columns are the argument. The axis column wraps instead of forcing
   the table wider still. */
.card--matrix { overflow: hidden; }
/* Only the table scrolls; the head band and the tally foot stay put. */
.card__scroll { overflow-x: auto; }
/* Wide enough that the platform names sit on two lines rather than
   three and the team column holds "Marketing-operated" whole. */
.card--matrix .dtable { min-width: 920px; }
.card--matrix .dtable__label { white-space: normal; }

/* The axis column stays put while the platforms scroll past it, so a
   rating is never read without the row it belongs to. */
.card--matrix .dtable thead th:first-child,
.card--matrix .dtable tbody td.dtable__label { position: sticky; left: 0; z-index: 1; }
.card--matrix .dtable thead th:first-child { background: var(--c-brand); }
.card--matrix .dtable tbody td.dtable__label { background: var(--c-topbar-bg); }
.card--matrix .dtable tbody tr:nth-child(even) td.dtable__label { background: var(--c-row-alt); }

/* --- Numbered points --- */
.numbered { list-style: none; padding: 0; margin: 0; max-width: var(--measure-w); }
.numbered > li {
  display: grid;
  grid-template-columns: 30px 1fr;
  gap: 14px;
  padding: 14px 0;
}
.numbered > li:first-child { padding-top: 0; }
.numbered__badge {
  width: 30px;
  height: 24px;
  border-radius: 6px;          /* rounded rectangle, not a circle */
  background: var(--c-box-bg);
  color: var(--c-brand);
  display: grid;
  place-items: center;
  font-family: var(--f-sans);
  font-weight: var(--w-medium);
  font-size: var(--size-small);
  line-height: 1;
}
/* Block flow, deliberately not flex. A flex container blockifies every
   inline child, which put each bold lead-in and each footnote marker on a
   line of its own. Sibling blocks still get the paragraph rhythm. */
.numbered__body {
  font-family: var(--f-sans);
  font-size: var(--t-body-size);
  line-height: var(--t-body-lh);
  letter-spacing: var(--t-body-ls);
}
.numbered__body > * + * { margin-top: var(--gap-para); }

/* --- Highlight boxes --- */
.highlights {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 12px;
}
.highlight {
  background: var(--c-box-bg);
  border-radius: var(--radius);
  padding: 18px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.highlight__value {
  font-family: var(--f-serif);
  font-weight: var(--w-medium);
  font-size: var(--t-figure-size);
  line-height: 1.15;
  letter-spacing: -0.04em;
  color: var(--c-brand);
}
.highlight__label {
  font-family: var(--f-sans);
  font-size: var(--size-small);
  line-height: 1.45;
  letter-spacing: var(--t-body-ls);
  color: var(--c-label);
}
.highlight--quote { gap: 10px; grid-column: 1 / -1; }
.highlight--quote p {
  font-family: var(--f-serif);
  /* Medium, not regular: at 20px on a tinted panel the regular weight
     reads lighter than the prose around it. */
  font-weight: var(--w-medium);
  font-size: var(--size-lead);
  line-height: 1.4;
  letter-spacing: -0.03em;
  color: var(--c-deep);
  max-width: none;
}

/* --- Reader takeaway ---
   The chapter's conclusion, boxed so it reads as the thing to remember
   rather than one more section. Dark navy, so everything inside it has
   to invert. */
.takeaway {
  padding: 24px;
  border-radius: 8px;
  background: var(--c-takeaway-bg);
  color: var(--c-takeaway-text);
}
.takeaway .section-heading,
.takeaway p,
.takeaway li,
.takeaway strong { color: var(--c-takeaway-text); }
.takeaway a { color: var(--c-takeaway-text); text-decoration: underline; }
/* Its own padding stands in for the section gap. */
.article .takeaway { scroll-margin-top: var(--section-clearance); }

/* --- Company file --- */
.file-card {
  background: var(--c-box-bg);
  border-radius: var(--radius);
  padding: 20px;
  display: flex;
  flex-direction: column;
  gap: 18px;
}
.file-card__name {
  font-family: var(--f-serif);
  font-weight: var(--w-medium);
  font-size: var(--size-heading);
  line-height: 1.2;
  letter-spacing: -0.04em;
  color: var(--c-prose);
}
.file-card__meta {
  font-family: var(--f-sans);
  font-size: var(--size-small);
  letter-spacing: var(--t-body-ls);
  color: var(--c-label);
}
.stat-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.stat-box {
  background: var(--c-topbar-bg);
  border-radius: var(--radius);
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.stat-box__value {
  font-family: var(--f-serif);
  font-weight: var(--w-medium);
  font-size: var(--t-figure-size);
  line-height: 1.1;
  letter-spacing: -0.04em;
  color: var(--c-brand);
}
.stat-box__label {
  font-family: var(--f-sans);
  font-size: var(--size-small);
  line-height: 1.4;
  letter-spacing: var(--t-body-ls);
  color: var(--c-label);
}
.chip {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 6px;
  background: var(--c-chip-bg);
  color: var(--c-brand);
  font-family: var(--f-sans);
  font-size: var(--size-small);
  letter-spacing: var(--t-body-ls);
  vertical-align: middle;
}
.file-card__subhead {
  font-family: var(--f-sans);
  font-weight: var(--w-semibold);
  font-size: var(--t-card-size);
  letter-spacing: var(--t-body-ls);
  color: var(--c-prose);
  display: flex;
  align-items: center;
  gap: 10px;
}

/* The image is the one place a border is kept. */
.figure__image {
  border: 1px solid var(--c-card-border);
  border-radius: var(--radius);
  /* Fills the column; the frame is the column's width, not the asset's. */
  max-width: 100%;
  height: auto;
}
/* An export that runs edge to edge needs air between the artwork and the
   frame, or the border reads as part of the drawing. */
.article .figure__image--pad,
.figure__image--pad { padding: 20px; }

/* --- Previous / next chapter ---
   Plain type and an arrow, no container. */
.section-nav {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 32px;
  padding-top: 8px;
}
/* The label wraps rather than truncating, so a long chapter title is
   always readable in full. The narrower box is what buys the wrap. */
.section-nav__btn {
  display: flex;
  /* A button arrives with the UA's own 13.333px Arial. Both lines inside
     it set their own type, so nothing showed - but the box was being
     measured against a font the book does not use. */
  font: inherit;
  /* Centred against the label block. It used to be flex-start plus a
     hard margin on the arrow, which only lined up at one label size -
     changing the type broke it. This holds at any size or line count. */
  align-items: center;
  gap: 12px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--c-brand);
  text-align: left;
  cursor: pointer;
  flex: 0 1 auto;
  max-width: 42%;
  transition: opacity var(--duration) var(--ease);
}
.section-nav__btn:hover { opacity: 0.7; }
.section-nav__btn:focus-visible { outline: 2px solid var(--c-focus); outline-offset: 4px; border-radius: 2px; }
.section-nav__btn[disabled] { opacity: 0.35; cursor: default; }
.section-nav__btn svg { flex: none; width: 20px; height: 20px; }
.section-nav__meta { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.section-nav__kicker {
  font-family: var(--f-sans);
  font-size: var(--size-small);
  letter-spacing: var(--t-body-ls);
  color: var(--c-label);
}
.section-nav__label {
  font-family: var(--f-serif);
  font-weight: var(--w-medium);
  font-size: var(--size-body);
  line-height: 1.25;
  letter-spacing: -0.04em;
  color: var(--c-brand);
  /* Up to three lines, no ellipsis: nothing is cut off. */
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  white-space: normal;
  text-wrap: pretty;
}
.section-nav__btn--next { margin-left: auto; text-align: right; }
.section-nav__btn--next .section-nav__meta { align-items: flex-end; }

/* --- Code card ---
   For structured-data and configuration snippets. Same shell as .card, so
   a snippet reads as a document exhibit rather than as running text. The
   body scrolls sideways instead of wrapping, because a wrapped JSON line
   stops being readable as code. */
.code-card {
  border: 1px solid var(--c-card-border);
  border-radius: var(--radius);
  background: var(--c-topbar-bg);
  overflow: hidden;
}
.code-card__body {
  margin: 0;
  padding: 16px;
  background: var(--c-code-bg);
  overflow-x: auto;
}
.code-card__body code {
  display: block;
  font-family: var(--f-mono);
  font-size: var(--t-min-size);
  line-height: 1.55;
  letter-spacing: 0;
  color: var(--c-prose);
  white-space: pre;
}
/* Inline code in running prose — element names, paths, file names. */
.article code {
  font-family: var(--f-mono);
  font-size: var(--t-min-size);
  letter-spacing: 0;
  color: var(--c-deep);
}

/* --- Author byline ---
   A name, a role, and a portrait. The portrait frame is a dotted
   placeholder until a headshot exists; drop an img element inside it and the
   placeholder treatment removes itself. */
.byline { display: flex; align-items: center; gap: 16px; }
.byline__portrait {
  width: 88px;
  height: 88px;
  flex: none;
  border-radius: var(--radius);
  background: var(--c-box-bg);
  border: 2px dotted var(--c-rule);
  overflow: hidden;
}
.byline__portrait:has(img) { border: 0; background: none; }
.byline__portrait img { display: block; width: 100%; height: 100%; object-fit: cover; }
.byline__name {
  font-family: var(--f-serif);
  font-weight: var(--w-medium);
  font-size: var(--t-byline-size);
  line-height: var(--t-byline-lh);
  letter-spacing: var(--track-lead);
  color: var(--c-prose);
}
.byline__role {
  margin-top: 4px;
  font-family: var(--f-sans);
  font-size: var(--t-card-size);
  line-height: var(--lh-small);
  letter-spacing: var(--t-body-ls);
  color: var(--c-label);
}

/* No block ends on a word by itself where the browser can avoid it.
   `pretty` costs a little layout time and reflows the last few lines to
   balance them, which is exactly the trade we want in a book. */
.square__label,
.stat-box__label,
.ruled-list > li,
.calendar__body p,
.barviz__title,
.casebox p,
.figure figcaption,
.dtable td { text-wrap: pretty; }

/* --- Chart box ---
   The frame every figure drawn in the document sits in, so a chart, a
   diagram, or a boxed list all carry the same edge. */
.chart-box {
  padding: 24px;
  border: 1px solid var(--c-card-border);
  border-radius: var(--radius);
  max-width: var(--measure-w);
}
/* A table reaches the frame itself: padding around it would double the
   cell padding already inside it. The box used to clip, so that the
   header band took the corner radius - but a footnote in a cell hangs its
   tooltip above the row, and clipping cut the tooltip off at the frame.
   The radius is put on the corner cells instead and the box lets its
   contents out. */
.chart-box--flush {
  padding: 0;
  overflow: visible;
}
.chart-box--flush > .dtable thead th:first-child { border-top-left-radius: var(--radius); }
.chart-box--flush > .dtable thead th:last-child  { border-top-right-radius: var(--radius); }
.chart-box--flush > .perm-legend { margin: 0; padding: 16px 20px; }

/* --- Bar chart ---
   The figure this replaces was a flat export: two horizontal bars, the
   value sitting inside the fill, a right-aligned note above each track.
   Same shape, drawn in the document so the numbers can move.

   The two fills share one scale - 105,000 to the full track - so the
   comparison the figure is making is true to the numbers rather than
   decorative. Widths are set per bar with --pct in the markup. */
.barviz {
  display: flex;
  flex-direction: column;
  gap: 28px;
  width: 100%;
}
.barviz__row {
  display: grid;
  grid-template-columns: 1fr;
  gap: 8px;
}
.barviz__title {
  font-family: var(--f-serif);
  font-weight: var(--w-medium);
  font-size: var(--size-body);
  line-height: 1.3;
  letter-spacing: -0.03em;
  color: var(--c-prose);
}
.barviz__note {
  justify-self: end;
  font-family: var(--f-sans);
  font-size: var(--size-small);
  letter-spacing: var(--t-body-ls);
  color: var(--c-label);
}
.barviz__track {
  height: 46px;
  border-radius: 23px;
  background: var(--c-bar-track);
  overflow: hidden;
}
.barviz__fill {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  height: 100%;
  min-width: 6.5em;                /* the value never outgrows its fill */
  padding-inline: 20px;
  border-radius: 23px;
  width: var(--pct);
}
/* Each bar carries its own figure colour: what was spent is stated in
   the brand blue, what was lost in red. */
.barviz__fill--cost { background: var(--c-bar-cost); }
.barviz__fill--lost { background: var(--c-bar-lost); }
.barviz__fill--cost .barviz__value { color: var(--c-bar-text-cost); }
.barviz__fill--lost .barviz__value { color: var(--c-bar-text-lost); }
.barviz__value {
  font-family: var(--f-sans);
  font-weight: var(--w-semibold);
  font-size: var(--t-card-size);
  letter-spacing: var(--t-body-ls);
  font-variant-numeric: tabular-nums;   /* no reflow while it counts */
  white-space: nowrap;
}

/* The animated state. Kept behind a class set by script so the bars are
   drawn at full width for anything that never runs it - the print
   sheets, a reader with JS off, a saved page. */
.barviz.is-armed .barviz__fill {
  width: 0;
  transition: width 900ms cubic-bezier(0.22, 1, 0.36, 1);
}
/* The min-width that keeps the value inside its fill would leave a stub
   of bar sitting there before the chart runs. It only matters once the
   value is visible. */
.barviz.is-armed:not(.is-shown) .barviz__fill {
  min-width: 0;
  padding-inline: 0;      /* width:0 plus padding is still 40px of bar */
}
.barviz.is-armed.is-shown .barviz__fill { width: var(--pct); }
.barviz.is-armed .barviz__value { opacity: 0; }
.barviz.is-armed.is-shown .barviz__value {
  opacity: 1;
  transition: opacity 200ms var(--ease) 250ms;
}
@media (prefers-reduced-motion: reduce) {
  .barviz.is-armed .barviz__fill { width: var(--pct); transition: none; }
  .barviz.is-armed .barviz__value { opacity: 1; }
}

/* --- Square stat containers ---
   For a list that is really a set of headline numbers. Square so the
   figure carries the block and the words sit under it. */
.squares {
  display: grid;
  /* Five across on the full column, so the set reads as one row of
     figures rather than three and a gap. */
  grid-template-columns: repeat(auto-fit, minmax(128px, 1fr));
  gap: 10px;
  max-width: var(--measure-w);
}
.square {
  /* Square by default. A box whose words will not fit in a square grows
     taller instead of clipping them - aspect-ratio yields to the content's
     own minimum height. */
  aspect-ratio: 1;
  /* Square is the floor, not the ceiling: the row is sized by whichever
     box needs the most height and the rest stretch up to match, so a
     long label never leaves one box taller than its neighbours. */
  min-height: 100%;
  display: flex;
  flex-direction: column;
  /* Figure and words sit on the floor of the square. Centred, a short
     label and a long one never line up with each other. */
  justify-content: flex-end;
  gap: 8px;
  padding: 16px;
  border: 1px solid var(--c-card-border);
  border-radius: var(--radius);
  background: var(--c-topbar-bg);
}
.square__value {
  font-family: var(--f-serif);
  font-weight: var(--w-medium);
  font-size: var(--size-stat);
  line-height: 1.05;
  letter-spacing: -0.04em;
  color: var(--c-brand);
}
/* A statement that is all words rather than a figure - same block, but
   set at a size the sentence can actually hold. */
.square__value--text {
  font-size: var(--size-lead);
  line-height: 1.25;
  letter-spacing: -0.03em;
}
.square__label {
  font-family: var(--f-sans);
  font-size: var(--size-small);
  line-height: 1.4;
  letter-spacing: var(--t-body-ls);
  color: var(--c-label);
}

/* --- Case study box ---
   A whole case study lifted out of the run of the chapter: light blue
   ground, the company's mark at the top, its own sub-headings. */
.casebox {
  padding: 32px;
  border-radius: var(--radius);
  background: var(--c-case-bg);
}
.casebox > * + * { margin-top: var(--gap-para); }
/* Only the mark and the name live here, no loose text, so a flex column
   is safe: nothing inline can be turned into a track of its own. */
.casebox__logo {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  margin-bottom: 24px;
}
.casebox__wordmark {
  font-family: var(--f-serif);
  font-weight: var(--w-semibold);
  font-size: var(--size-heading);
  line-height: 1.2;
  letter-spacing: -0.03em;
  color: var(--c-book-title);
}
.casebox__logo img { display: block; width: 170px; max-width: 45%; height: auto; }
.casebox__heading {
  font-family: var(--f-serif);
  font-weight: var(--w-medium);
  font-size: var(--size-lead);
  line-height: 1.3;
  letter-spacing: -0.03em;
  color: var(--c-prose);
}
.casebox > .casebox__heading { margin-top: var(--gap-section); }
/* Both sub-headings sit the same distance above what they label. The
   ruled list resets its own margin, so this has to outrank that. */
.casebox > .casebox__heading + * { margin-top: 14px; }
.casebox .stat-grid { grid-template-columns: repeat(2, 1fr); }

/* --- Ruled list ---
   Bullets that need a line between them rather than only white space. */
.ruled-list {
  list-style: none;
  padding: 0;
  margin: 0;
}
/* The marker is positioned, not laid out: a flex row would make every
   inline child of the item its own flex track, so a marked-up word
   inside the sentence would pick up the row's gap and open a hole
   before the next comma. The offset matches the row's padding, which is
   14px everywhere except the first row. */
.ruled-list > li {
  position: relative;
  padding: 14px 0 14px 32px;
  border-top: 1px solid var(--c-rule);
  font-family: var(--f-sans);
  font-size: var(--t-body-size);
  line-height: var(--t-body-lh);
  letter-spacing: var(--t-body-ls);
  color: var(--c-prose);
}
.ruled-list > li:first-child { border-top: 0; padding-top: 0; }
.ruled-list > li:last-child { padding-bottom: 0; }
.ruled-list > li::before {
  content: '';
  position: absolute;
  left: 4px;
  top: 14px;
  width: 5px;
  height: 5px;
  margin-top: 0.6em;
  border-radius: 50%;
  background: var(--c-brand);
}
.ruled-list > li:first-child::before { top: 0; }
/* On the case box's blue ground a grey hairline reads as dirt. */
.casebox .ruled-list > li { border-top-color: var(--c-case-rule); }

/* A stat box that is the emphasis rather than a quiet panel. */
.stat-box--solid {
  background: var(--c-stat-solid-bg);
  color: var(--c-stat-solid-fg);
  padding: 18px;
}
.stat-box--solid .stat-box__value,
.stat-box--solid .stat-box__label { color: var(--c-stat-solid-fg); }

/* --- Squares, stacked ---
   Five figures across leaves each label about twenty characters wide,
   which breaks short phrases into three lines. Stacked, the figure sits
   beside its words and each row has the whole column to read across. */
.squares--stacked {
  grid-template-columns: 1fr;
  gap: 16px;
}
.squares--stacked .square {
  aspect-ratio: auto;
  min-height: 0;
  flex-direction: row;
  align-items: baseline;
  gap: 16px;
  padding: 18px 20px;
}
.squares--stacked .square__value {
  /* The figure column has to hold the longest value on one line, so its
     width is set in the figure's own type and follows it up or down.
     5.2em clears 'Up to 94%', the longest of them, with a little room. */
  flex: 0 0 var(--stat-basis, 5.2em);
  font-size: var(--size-stat-sm);
}
/* A statement-shaped value keeps the figure size - one size across the
   set - and takes the whole row instead of a fixed figure column. */
.squares--stacked .square__value--text { flex: 1 1 auto; }
.squares--stacked .square__label { flex: 1 1 auto; }

/* --- Calendar ---
   The three days of the launch laid out as a week strip: a header band
   with the day and its number, the agenda in the cell below it, one
   bordered panel around the set. Static by design - there is nothing to
   choose here, so nothing responds to a pointer. */
.calendar {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  list-style: none;
  margin: 0;
  padding: 0;
  max-width: var(--measure-w);
  border: 1px solid var(--c-card-border);
  border-radius: var(--radius);
  overflow: hidden;                /* clips the header band to the radius */
  background: var(--c-topbar-bg);
}
.calendar__day {
  display: flex;
  flex-direction: column;
}
.calendar__day + .calendar__day { border-left: 1px solid var(--c-card-border); }
.calendar__head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 16px;
  background: var(--c-cal-head-bg);
  border-bottom: 1px solid var(--c-card-border);
}
.calendar__num {
  flex: none;
  /* Sized in its own type so the digit never outgrows the circle. */
  width: 1.7em;
  height: 1.7em;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--c-cal-num-bg);
  color: var(--c-cal-num-fg);
  font-family: var(--f-sans);
  font-weight: var(--w-semibold);
  font-size: var(--size-ui);
  letter-spacing: var(--t-body-ls);
}
.calendar__name {
  font-family: var(--f-serif);
  font-weight: var(--w-semibold);
  font-size: var(--size-body);
  line-height: 1.3;
  letter-spacing: -0.03em;
  color: var(--c-prose);
}
.calendar__body { padding: 16px; }
.calendar__body p { margin: 0; }

/* --- Ruled list, numbered ---
   Numbers rather than dots, set at the size of the text they label so
   the row reads as one line. */
.ruled-list--numbered {
  list-style: none;
  /* --list-offset lets a list continued on the next print sheet resume
     its numbering instead of starting again at one. */
  counter-reset: ruled var(--list-offset, 0);
}
/* The inset scales with the type: a two-digit marker at the sheet's
   larger size runs past a fixed 32px and collides with its own text. */
.ruled-list--numbered > li { counter-increment: ruled; padding-left: 2em; }
.ruled-list--numbered > li::before {
  content: counter(ruled) ".";
  left: 0;
  width: auto;
  height: auto;
  min-width: 1.2em;
  margin-top: 0;
  border-radius: 0;
  background: none;
  color: var(--c-brand);
  font-family: var(--f-sans);
  font-weight: var(--w-semibold);
  font-size: inherit;
  line-height: inherit;
}

/* --- Customer grids ---
   Three by three, one cell per company. The cells hold the name for now
   and are sized for the logo that will replace it. Hovering a cell
   names the site it opens, the same reveal the top-bar buttons use;
   clicking follows it. */
.logo-set__title {
  font-family: var(--f-serif);
  font-weight: var(--w-semibold);
  font-size: var(--size-body);
  line-height: 1.3;
  letter-spacing: -0.03em;
  color: var(--c-prose);
  margin-bottom: 10px;
}
.logo-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 10px;
  list-style: none;
  margin: 0;
  padding: 0;
  max-width: var(--measure-w);
}
.logo-cell {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 128px;
  padding: 24px 20px;
  border: 1px solid var(--c-card-border);
  border-radius: var(--radius);
  background: var(--c-topbar-bg);
  text-align: center;
  text-decoration: none;
  transition: background var(--duration) var(--ease),
              border-color var(--duration) var(--ease);
}
.logo-cell:hover { background: var(--c-btn-hover); border-color: var(--c-brand); }
.logo-cell:focus-visible { outline: 2px solid var(--c-focus); outline-offset: 2px; }
/* Every mark gets the same band of height and is contained inside it, so
   a tall wordmark and a wide one read as the same size on the wall and
   neither is ever scaled past its own artwork. */
/* --mark-h is set per logo in the markup, measured rather than guessed:
   each file's ink was rendered and its bounding box read, and the height
   is the one that gives every mark the same ink area. Holding them all
   to one height instead makes a square badge read half the size of a
   long wordmark, and a file with padding inside its own box read smaller
   again. */
.logo-cell__mark {
  display: block;
  width: 100%;
  /* --mark-scale trims the whole wall at once and leaves the measured
     relationship between the marks untouched. */
  height: calc(var(--mark-h, 34px) * var(--mark-scale, 0.75));
  object-fit: contain;
  /* Each mark gets a raster of its own to be composited from.

     An SVG referenced by <img> is re-rasterised whenever the region it
     sits in is repainted, and it is not cached between repaints: on the
     way past eighteen of them the scroll dropped a frame at nearly every
     logo. Measured over a 2.5s scroll through the wall, 1440x1000:
     15 frames over 20ms before, none after - the same clean 16.7ms as a
     page of plain prose. Promoting the grid alone only got it to 11; the
     raster has to belong to each image.

     `will-change` on eighteen elements is deliberate rather than a
     precaution: the wall is static, so the layers are made once and then
     only ever composited, and each is a few hundred pixels square. */
  will-change: transform;
}
.logo-cell__name {
  font-family: var(--f-sans);
  font-size: var(--t-card-size);
  line-height: 1.35;
  letter-spacing: var(--t-body-ls);
  color: var(--c-prose);
}

/* --- Ruled list, no marker ---
   Where the separator is the whole point and a bullet would just be
   noise; the subject of each row is named in the brand colour instead. */
.ruled-list--plain > li { padding-left: 0; }
.ruled-list--plain > li::before { content: none; }
.ruled-list__name {
  font-weight: var(--w-semibold);
  color: var(--c-brand);
}

/* --- Boxed list ---
   One point per box, spaced apart, for a set that reads as six separate
   findings rather than a run of bullets. */
.boxed-list {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 0;
  padding: 0;
  max-width: var(--measure-w);
}
.boxed-list__name {
  font-weight: var(--w-semibold);
  color: var(--c-brand);
}
.boxed-list > li {
  padding: 14px 18px;
  border: 1px solid var(--c-card-border);
  border-radius: var(--radius);
  background: var(--c-topbar-bg);
  font-family: var(--f-sans);
  font-size: var(--t-body-size);
  line-height: var(--t-body-lh);
  letter-spacing: var(--t-body-ls);
  color: var(--c-prose);
  text-wrap: pretty;
}

/* --- Checklist ---
   Reads as a list of things already cleared, so the marks are drawn, not
   offered: there is no input here and nothing to toggle. */
.checklist {
  list-style: none;
  margin: 0;
  padding: 0;
  max-width: var(--measure-w);
}
.checklist > li {
  position: relative;
  padding: 0 0 0 34px;
  font-family: var(--f-sans);
  font-size: var(--t-body-size);
  line-height: var(--t-body-lh);
  letter-spacing: var(--t-body-ls);
  color: var(--c-prose);
  text-wrap: pretty;
}
.checklist > li + li { margin-top: 12px; }
/* The box */
.checklist > li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.28em;
  width: 18px;
  height: 18px;
  border-radius: 4px;
  background: var(--c-check);
}
/* The tick, drawn with two borders on a rotated box */
.checklist > li::after {
  content: '';
  position: absolute;
  left: 5px;
  top: calc(0.28em + 5px);
  width: 8px;
  height: 4px;
  border-left: 2px solid var(--c-stat-solid-fg);
  border-bottom: 2px solid var(--c-stat-solid-fg);
  transform: rotate(-45deg);
}

/* --- Highlighted phrase ---
   A sentence the reader should not be able to skim past. */
.mark-blue {
  background: var(--c-mark-bg);
  color: inherit;
  font-weight: var(--w-semibold);
  padding: 0.08em 0.18em;
  border-radius: 3px;
  /* A highlight that breaks across lines should carry its ground with
     it rather than leaving the second line bare. */
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}

/* --- Quote box ---
   A pull quote with its attribution, on a blue gradient running left to
   right so it reads as a panel rather than another card. */
.quote-box {
  margin: 0;
  padding: 28px 32px;
  border-radius: var(--radius);
  background: linear-gradient(90deg, var(--c-quote-from), var(--c-quote-to));
  color: var(--c-quote-text);
  max-width: var(--measure-w);
}
.quote-box p {
  margin: 0;
  font-family: var(--f-serif);
  font-weight: var(--w-medium);
  font-size: var(--size-lead);
  line-height: 1.4;
  letter-spacing: -0.02em;
  color: var(--c-quote-text);
  text-wrap: pretty;
}
.quote-box__source {
  margin-top: 16px;
  font-family: var(--f-sans);
  font-size: var(--size-small);
  line-height: 1.5;
  letter-spacing: var(--t-body-ls);
  color: var(--c-quote-text);
  opacity: 0.92;
}
.quote-box__source strong { font-weight: var(--w-semibold); }
/* A brand-blue marker vanishes on the panel's own blue. */
.quote-box .footnote-ref a,
.quote-box .footnote-ref__note { color: var(--c-quote-text); }


/* --- Point ---
   A main point under a heading: the lead on its own line, semibold and
   in the brand colour, with the body underneath in ordinary prose, so it
   is findable when the reader is scanning a section rather than buried
   at the head of a paragraph. No numeral and no marker - the lead is the
   whole signal. */
.article .point__lead,
.point__lead {
  font-weight: var(--w-semibold);
  font-size: var(--size-lead);
  line-height: 1.35;
  letter-spacing: -0.02em;
  color: var(--c-brand);
  margin-bottom: 8px;
}
/* Only the bottom margin goes: `margin: 0` here also killed the top
   margin, and a point with three paragraphs in it - the appendix is full
   of them - had its last one sitting flush against the one above. The
   rule is more specific than `p + p`, so it won that fight silently. */
/* --- Run-in lead ---
   A label that stays on the line it introduces: the appendix's glossary
   terms, the platform teardown labels, the cluster verdicts. It takes the
   lead's colour and weight and nothing else - no size of its own, because
   it is part of the sentence it opens, and it keeps the full stop that
   separates it from that sentence. The blue lead on its own line is the
   other form (.point__lead); this one is for a reference entry, where a
   heading per term would be a page of headings. */
.article strong.inline-lead,
strong.inline-lead {
  font-weight: var(--w-semibold);
  /* The prose ink, not the brand blue: on the line it opens, a coloured
     label reads as a link, and a reference page of them reads as a page of
     links. The weight is the whole signal. Set explicitly rather than
     left to inherit, so the class names the treatment in one place. */
  color: var(--c-prose);
}

.point > p:last-child { margin-bottom: 0; }
/* Spacing between a point's own paragraphs: the section's flex gap does
   not reach inside it. */
.point > p + p { margin-top: var(--gap-para); }
.point > .point__lead + p { margin-top: 0; }
/* A list inside a point is a block like any other and takes the same
   rhythm; without this it sat flush against the paragraph introducing it. */
.point > p:not(.point__lead) + .ruled-list,
.point > p:not(.point__lead) + .boxed-list,
.point > .ruled-list + p,
.point > .boxed-list + p { margin-top: var(--gap-para); }
.point > .ruled-list:last-child,
.point > .boxed-list:last-child { margin-bottom: 0; }
/* The lead keeps its own tighter gap to whatever it introduces, list or
   paragraph: it is a heading for the block under it, not a sibling. */
.point > .point__lead + .ruled-list,
.point > .point__lead + .boxed-list { margin-top: 0; }

/* The same shape inside an ordered list. The list stays ordered so the
   sequence is still carried in the markup, but nothing numeric is
   drawn. */
.point-list {
  list-style: none;
  margin: 0;
  padding: 0;
  max-width: var(--measure-w);
}
.point-list > li + li { margin-top: var(--gap-para); }

/* --- Pie chart ---
   Drawn as one circle per slice: radius 25 with a 50-wide stroke fills
   the whole disc, so a dash length is an arc and the slice can be swept
   on rather than faded in. Each slice starts where the last one ended,
   from twelve o'clock, and arrives in turn. */
.pie-chart {
  display: grid;
  grid-template-columns: 220px 1fr;
  align-items: center;
  gap: 32px;
}
.pie {
  width: 100%;
  max-width: 220px;
  aspect-ratio: 1;
  /* The stroke is drawn from the middle of the path outwards, so the
     disc is complete without a fill. */
  overflow: visible;
}
.pie__seg {
  fill: none;
  stroke-width: 50;
  transform-box: view-box;
  transform-origin: 50% 50%;
  transform: rotate(var(--rot));
  stroke-dasharray: var(--len) 157.08;
}
.pie__seg--1 { stroke: var(--c-slice-1); }
.pie__seg--2 { stroke: var(--c-slice-2); }
.pie__seg--3 { stroke: var(--c-slice-3); }
.pie__seg--4 { stroke: var(--c-slice-4); }
.pie__seg--5 { stroke: var(--c-slice-5); }

/* Armed by script, so a page with no script keeps the whole pie. */
.pie-chart.is-armed .pie__seg {
  stroke-dasharray: 0 157.08;
  transition: stroke-dasharray 620ms cubic-bezier(0.22, 1, 0.36, 1);
  transition-delay: calc(var(--i) * 260ms);
}
.pie-chart.is-armed.is-shown .pie__seg { stroke-dasharray: var(--len) 157.08; }
.pie-chart.is-armed .pie-legend__row {
  opacity: 0;
  transition: opacity 300ms var(--ease);
  transition-delay: calc(var(--i) * 260ms);
}
.pie-chart.is-armed.is-shown .pie-legend__row { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .pie-chart.is-armed .pie__seg {
    stroke-dasharray: var(--len) 157.08;
    transition: none;
  }
  .pie-chart.is-armed .pie-legend__row { opacity: 1; transition: none; }
}

/* --- Pie legend ---
   Number, then a rectangle carrying the slice colour. The rectangle is
   also where a logo will go, so it is sized for one rather than drawn as
   a bullet. */
.pie-legend {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.pie-legend__row {
  display: grid;
  grid-template-columns: 18px 44px 1fr auto;
  align-items: center;
  gap: 12px;
}
.article .pie-legend__num,
.pie-legend__num {
  font-family: var(--f-sans);
  font-weight: var(--w-semibold);
  font-size: var(--size-small);
  letter-spacing: var(--t-body-ls);
  color: var(--c-label);
}
.pie-legend__swatch {
  width: 44px;
  height: 24px;
  border-radius: 4px;
}
.pie-legend__swatch--1 { background: var(--c-slice-1); }
.pie-legend__swatch--2 { background: var(--c-slice-2); }
.pie-legend__swatch--3 { background: var(--c-slice-3); }
.pie-legend__swatch--4 { background: var(--c-slice-4); }
.pie-legend__swatch--5 { background: var(--c-slice-5); }
.article .pie-legend__name,
.pie-legend__name {
  font-family: var(--f-sans);
  font-size: var(--t-card-size);
  line-height: 1.4;
  letter-spacing: var(--t-body-ls);
  color: var(--c-prose);
}
.article .pie-legend__value,
.pie-legend__value {
  font-family: var(--f-sans);
  font-weight: var(--w-semibold);
  font-size: var(--t-card-size);
  letter-spacing: var(--t-body-ls);
  color: var(--c-prose);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* --- Checklist, blank ---
   A list of things to go and do, so the boxes are empty: an outline and
   no tick, and still nothing to click. */
.checklist--blank > li::before {
  background: none;
  border: 1.5px solid var(--c-check);
}
.checklist--blank > li::after { content: none; }
/* Inside the checklist the lead and its body are separate blocks, so the
   box lines up with the lead rather than floating between them. */
.checklist.point-list > li { padding-left: 34px; }
.checklist.point-list > li + li { margin-top: 18px; }

/* --- Permission matrix ---
   Was a flat export; now a real table, so the roles are readable at any
   size and a screen reader can say what each cell means. The state is
   carried by an icon with a text equivalent beside it. */
.dtable--perm thead th {
  vertical-align: top;
  line-height: 1.25;
}
.dtable--perm thead th:not(:first-child) { text-align: center; }
.perm-head__role { display: block; font-weight: var(--w-semibold); }
.perm-head__note {
  display: block;
  margin-top: 2px;
  font-size: var(--t-caption-size);
  font-weight: var(--w-regular);
  opacity: 0.85;
}
.dtable--perm tbody th {
  padding: 12px 16px;
  vertical-align: top;
  font-weight: var(--w-regular);
}
.article .perm-row__name,
.perm-row__name {
  display: block;
  font-family: var(--f-sans);
  font-size: var(--t-card-size);
  line-height: 1.4;
  letter-spacing: var(--t-body-ls);
  color: var(--c-prose);
}
.perm-row__note {
  display: block;
  margin-top: 2px;
  font-family: var(--f-sans);
  font-size: var(--t-caption-size);
  line-height: 1.35;
  color: var(--c-label);
}
.dtable--perm tbody td.perm { text-align: center; vertical-align: middle; }
.perm__wrap { display: inline-flex; align-items: center; justify-content: center; }
.perm__icon { width: 22px; height: 22px; display: block; }
/* Allowed and toggleable are a filled disc with a white tick; the two
   differ only in the fill, which is what the legend explains. */
.perm--yes .perm__icon circle { fill: var(--c-brand); }
.perm--maybe .perm__icon circle { fill: var(--c-deep); }
.perm__mark {
  fill: none;
  stroke: var(--white);
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* Not allowed is a lock, drawn rather than filled. */
.perm--no .perm__icon { width: 20px; height: 20px; }
.perm--no .perm__stroke {
  fill: none;
  stroke: var(--c-perm-lock);
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.perm-legend {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 10px 20px;
  margin: 16px 0 0;
  padding: 0;
}
.article .perm-legend__item,
.perm-legend__item {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--f-sans);
  font-size: var(--t-caption-size);
  letter-spacing: var(--t-body-ls);
  color: var(--c-label);
}
/* The legend states the meaning in words, so the icon's own text
   equivalent would be read twice. */
.perm-legend .u-sr { display: none; }

/* --- Range chart ---
   A row per platform: the cost band drawn as a line with its mid-point
   marked, the band's figures repeated on the right. The two halves are
   separate spans so the band can open outward from the mid-point rather
   than sweeping in from one end. */
.range-chart { display: block; }
.range-chart__head,
.range-row {
  display: grid;
  grid-template-columns: 150px 1fr 132px;
  align-items: center;
  gap: 12px;
}
.range-chart__ticks {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  font-family: var(--f-sans);
  font-size: var(--t-caption-size);
  letter-spacing: var(--t-body-ls);
  color: var(--c-label);
}
/* The ticks label the gridlines, so each sits at the start of its own
   share of the plot rather than centred in it. */
.range-chart__ticks > span { text-align: left; }
.range-chart__unit {
  font-family: var(--f-sans);
  font-size: var(--t-caption-size);
  letter-spacing: var(--t-body-ls);
  color: var(--c-label);
  text-align: right;
}
.range-rows {
  list-style: none;
  margin: 12px 0 0;
  padding: 0;
  border-top: 1px solid var(--c-card-border);
}
.range-row {
  padding: 14px 0;
  border-bottom: 1px solid var(--c-card-border);
}
.range-row.is-marked { background: var(--c-case-bg); }
.range-row__name {
  font-family: var(--f-sans);
  font-size: var(--t-card-size);
  line-height: 1.35;
  letter-spacing: var(--t-body-ls);
  color: var(--c-prose);
}
.range-row__value {
  font-family: var(--f-sans);
  font-size: var(--t-card-size);
  letter-spacing: var(--t-body-ls);
  color: var(--c-prose);
  text-align: right;
  white-space: nowrap;
}
.range-row.is-marked .range-row__name,
.range-row.is-marked .range-row__value { color: var(--c-brand); }

/* The plot. Gridlines match the six ticks above it. */
.range-row__plot {
  position: relative;
  display: block;
  height: 22px;
  /* One gridline per tile: the gradient's period has to be the whole
     tile, not a fraction of it, or it repeats inside every tile. */
  background-image: repeating-linear-gradient(
    to right, var(--c-card-border) 0 1px, transparent 1px 100%);
  background-size: calc(100% / 6) 100%;
  background-repeat: repeat-x;
}
.range-row__left,
.range-row__right {
  position: absolute;
  top: 50%;
  height: 2px;
  margin-top: -1px;
  background: var(--c-deep);
}
.range-row__left  { left: var(--lo); width: calc(var(--mid) - var(--lo)); transform-origin: right center; }
.range-row__right { left: var(--mid); width: calc(var(--hi) - var(--mid)); transform-origin: left center; }
.range-row__cap {
  position: absolute;
  top: 50%;
  width: 2px;
  height: 12px;
  margin-top: -6px;
  background: var(--c-deep);
}
.range-row__cap--lo { left: var(--lo); }
.range-row__cap--hi { left: calc(var(--hi) - 2px); }
.range-row__dot {
  position: absolute;
  top: 50%;
  left: var(--mid);
  width: 11px;
  height: 11px;
  margin: -5.5px 0 0 -5.5px;
  border-radius: 50%;
  border: 2px solid var(--c-deep);
  background: var(--c-topbar-bg);
}
.range-row.is-marked .range-row__left,
.range-row.is-marked .range-row__right,
.range-row.is-marked .range-row__cap { background: var(--c-brand); }
.range-row.is-marked .range-row__dot { border-color: var(--c-brand); }

/* Armed by script; without it every band is drawn in full. */
.range-chart.is-armed .range-row__left,
.range-chart.is-armed .range-row__right {
  transform: scaleX(0);
  transition: transform 560ms cubic-bezier(0.22, 1, 0.36, 1);
  transition-delay: calc(var(--i) * 200ms);
}
.range-chart.is-armed .range-row__cap,
.range-chart.is-armed .range-row__dot {
  opacity: 0;
  transition: opacity 240ms var(--ease);
  transition-delay: calc(var(--i) * 200ms + 220ms);
}
.range-chart.is-armed.is-shown .range-row__left,
.range-chart.is-armed.is-shown .range-row__right { transform: scaleX(1); }
.range-chart.is-armed.is-shown .range-row__cap,
.range-chart.is-armed.is-shown .range-row__dot { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
  .range-chart.is-armed .range-row__left,
  .range-chart.is-armed .range-row__right { transform: scaleX(1); transition: none; }
  .range-chart.is-armed .range-row__cap,
  .range-chart.is-armed .range-row__dot { opacity: 1; transition: none; }
}

.range-legend {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: 8px 24px;
  margin: 16px 0 0;
  padding: 0;
}
.article .range-legend__item,
.range-legend__item {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--f-sans);
  font-size: var(--t-caption-size);
  letter-spacing: var(--t-body-ls);
  color: var(--c-label);
}
.range-legend__line { width: 22px; height: 2px; background: var(--c-deep); }
.range-legend__line--brand { background: var(--c-brand); }
.range-legend__dot {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  border: 2px solid var(--c-deep);
}
.article .range-chart__source,
.range-chart__source {
  margin: 16px 0 0;
  font-family: var(--f-sans);
  font-size: var(--t-caption-size);
  line-height: 1.45;
  letter-spacing: var(--t-body-ls);
  color: var(--c-label);
}

/* --- Chart heading ---
   A chart that carries its own title reads as a standalone figure, the
   way it does in the design source. */
.article .chart-box__title,
.chart-box__title {
  font-family: var(--f-serif);
  font-weight: var(--w-semibold);
  font-size: var(--size-lead);
  line-height: 1.3;
  letter-spacing: -0.03em;
  color: var(--c-prose);
  margin: 0 0 4px;
}
.article .chart-box__note,
.chart-box__note {
  font-family: var(--f-sans);
  font-size: var(--t-card-size);
  line-height: 1.45;
  letter-spacing: var(--t-body-ls);
  color: var(--c-label);
  margin: 0 0 20px;
}

/* The figure number carries the weight; the description stays prose. */
.article .figure figcaption .figcaption__label,
.figure figcaption .figcaption__label { font-weight: var(--w-semibold); }

/* --- Verdict box ---
   A point the source itself calls a box: same lead-and-body shape, with
   a frame so each verdict reads as its own answer. */
.point--boxed {
  padding: 18px 20px;
  border: 1px solid var(--c-card-border);
  border-radius: var(--radius);
  background: var(--c-topbar-bg);
  max-width: var(--measure-w);
}
.point--boxed + .point--boxed { margin-top: 10px; }

/* --- Grade matrix ---
   Single-letter ratings, centred, with the letter itself carrying the
   reading so the row scans without a legend lookup. */
/* Seven platform columns in one measure: the cell's own padding is what
   squeezes a name like "SitecoreAI", so the matrix runs tighter than the
   book's other tables and sets its heads at caption size. */
/* Written against `thead th` and `tbody td` rather than the bare
   elements: `.dtable thead th` carries the same weight and one more
   element, so a shorter selector here loses and the cells kept the
   book's standard 16px - which left a platform name wider than its own
   column and 'Contentful' crowding the table's right edge. */
.dtable--grade thead th,
.dtable--grade tbody td { padding-inline: 4px; }
/* The axis column is the one that holds words rather than a letter, and
   it sits against the frame: it keeps the book's standard inset on that
   side. Taken out of the column's own width, not the ratings' - a fixed
   layout sizes the column from the colgroup and padding eats into the
   text, so the seven rating columns are untouched. */
.dtable--grade thead th:first-child,
.dtable--grade tbody td:first-child { padding-left: 16px; }
.dtable--grade thead th { font-size: var(--t-table-cap-size); }
.dtable--grade tbody td:not(.dtable__label) { text-align: center; }
.dtable--grade thead th:not(:first-child) { text-align: center; }
.article .grade,
.grade { font-weight: var(--w-semibold); }
.grade--w { color: var(--c-brand); }
.grade--t { color: var(--c-prose); }
.grade--l { color: var(--c-grade-weak); }
/* An address column breaks where it must. A 60-character URL has no
   space to break at, and the alternative to breaking mid-token is a cell
   that runs past its own table - the same trade the footnote apparatus
   already makes. Scoped to the column that holds addresses, so ordinary
   prose cells keep their words whole. */
.dtable td.cell--url,
.dtable td.cell--url .cell__value { overflow-wrap: anywhere; }
/* An address is a link, and in the source list it is the only thing in
   its cell - so it is marked as one: the brand blue, semibold, and
   underlined. The book's links are otherwise unmarked in running prose,
   where the words carry the sense; a column of fifteen addresses is a
   list of places to go, and every one of them should look like it. The
   underline is offset clear of the descenders and thinned, because these
   break mid-token and a heavy rule under a wrapped URL reads as two. */
.dtable td.cell--url a {
  color: var(--c-brand);
  font-weight: var(--w-semibold);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}
.dtable__total td { background: var(--c-foot-bg); }
.article .grade-tally,
.grade-tally {
  font-weight: var(--w-semibold);
  text-align: right;
  white-space: nowrap;
}

/* --- Cluster ---
   A block of diagnostic questions on its own tinted ground, with the
   three outcome notes underneath it. The heading names the cluster; no
   marker is added, because the cluster's own number is in its title. */
.cluster {
  padding: 28px;
  border-radius: var(--radius);
  background: var(--c-cluster-bg);
  max-width: var(--measure-w);
}
.cluster + .cluster { margin-top: var(--gap-para); }
/* Inside a box the line breaks greedily rather than prettily. `pretty`
   spends the last few lines of a paragraph evening out the rag, which in a
   box this wide left several lines ending well short of the right edge;
   greedy filling puts every word it can on each line. Widows are still
   handled - the last two words are joined at build time - and nothing
   hyphenates, because a compound inside the limit is held whole. */
.cluster p,
.cluster li { text-wrap: wrap; }
/* The appendix's clusters are the worksheet form of the same six: a lead,
   the question table, the score line, the verdict. The section's flex gap
   stops at the box, so the box spaces its own children. Written against
   what is inside rather than against the appendix's class, so the prose
   clusters in Chapter 10 - a title, a question list, three notes, each
   carrying its own bottom margin - are left exactly as they were. */
.cluster > .point + .card,
.cluster > .card + p,
.cluster > p + p { margin-top: var(--gap-para); }
/* The totals box reads as four verdicts to choose between, not as four
   paragraphs of prose, so they sit further apart than the rhythm inside a
   cluster's own commentary. The score table above them takes the same
   step, which is what stops the first verdict reading as its caption. */
.cluster--totals > .card + p,
.cluster--totals > p + p,
.cluster--totals > .point + .card { margin-top: var(--gap-verdict); }
.cluster > :last-child { margin-bottom: 0; }
.article .cluster__title,
.cluster__title {
  font-family: var(--f-serif);
  font-weight: var(--w-medium);
  font-size: var(--size-lead);
  line-height: 1.3;
  letter-spacing: -0.03em;
  color: var(--c-brand);
  margin: 0 0 16px;
}
.cluster__questions {
  /* Ordered for the outline, unnumbered on the page: the questions are
     a set, not a sequence the reader has to count. */
  list-style: none;
  margin: 0 0 24px;
  padding: 0;
}
.article .cluster__questions > li,
.cluster__questions > li {
  font-family: var(--f-sans);
  font-size: var(--t-body-size);
  line-height: var(--t-body-lh);
  letter-spacing: var(--t-body-ls);
  color: var(--c-prose);
  text-wrap: pretty;
}
.cluster__questions > li + li {
  margin-top: 18px;
  padding-top: 18px;
  border-top: 1px solid var(--c-case-rule);
}

/* The three outcomes, each on its own tint. */
.article .note,
.note {
  margin: 0;
  padding: 12px 16px;
  border-radius: var(--radius-sm);
  font-family: var(--f-sans);
  font-size: var(--t-card-size);
  line-height: 1.5;
  letter-spacing: var(--t-body-ls);
  text-wrap: pretty;
}
.note + .note { margin-top: 8px; }
.note strong { font-weight: var(--w-semibold); }
/* The modifier carries the colour, so it needs to outrank `.article p`
   the same way the base rule does. */
.article .note--green,
.note--green { background: var(--c-note-green-bg); color: var(--c-note-green-fg); }
.article .note--amber,
.note--amber { background: var(--c-note-amber-bg); color: var(--c-note-amber-fg); }
.article .note--red,
.note--red   { background: var(--c-note-red-bg);   color: var(--c-note-red-fg); }

/* --- Verdict ---
   Four outcomes, each on its own solid so the shape of the answer is
   readable before the words are. */
.verdicts {
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-width: var(--measure-w);
}
.verdict {
  padding: 18px 20px;
  border-radius: var(--radius);
  color: var(--c-verdict-fg);
}
.verdict--full { background: var(--c-verdict-full); }
.verdict--fed  { background: var(--c-verdict-fed); }
.verdict--part { background: var(--c-verdict-part); }
.verdict--stay { background: var(--c-verdict-stay); }
.article .verdict__lead,
.verdict__lead {
  font-family: var(--f-serif);
  font-weight: var(--w-semibold);
  font-size: var(--size-lead);
  line-height: 1.3;
  letter-spacing: -0.03em;
  color: var(--c-verdict-fg);
  margin: 0 0 6px;
}
.article .verdict p,
.verdict p { color: var(--c-verdict-fg); margin: 0; }

/* --- Stakeholder panel ---
   One panel per role: what they care about, then the objections they
   raise, ruled apart. White ground and a grey edge, so a run of nine
   panels reads as a list of people rather than a wall of tint. */
.panel {
  padding: 28px;
  border: 1px solid var(--c-card-border);
  border-radius: var(--radius);
  background: var(--c-topbar-bg);
  max-width: var(--measure-w);
}
.panel + .panel { margin-top: var(--gap-para); }
.article .panel__title,
.panel__title {
  font-family: var(--f-serif);
  font-weight: var(--w-medium);
  font-size: var(--size-lead);
  line-height: 1.3;
  letter-spacing: -0.03em;
  color: var(--c-brand);
  margin: 0 0 12px;
}
.article .panel__cares,
.panel__cares { margin: 0 0 8px; }
.article .panel__label,
.panel__label {
  font-weight: var(--w-semibold);
  color: var(--c-prose);
  margin: 20px 0 0;
}
.panel__items {
  list-style: none;
  margin: 12px 0 0;
  padding: 0;
}
.panel__items > li + li {
  margin-top: 18px;
  padding-top: 18px;
  border-top: 1px solid var(--c-rule-soft);
}
.article .panel__q,
.panel__q {
  font-style: italic;
  color: var(--c-prose);
  margin: 0 0 8px;
}
.panel__items > li > p:last-child { margin: 0; }
/* A panel that opens with a line of instruction before its list needs
   the gap the list's own first row deliberately does not carry. */
.panel > p + .ruled-list { margin-top: 18px; }


/* --- Partner badge ---
   The four rungs of the partner ladder, drawn as the badges Webflow
   issues rather than as chips of our own. They are the same artwork at
   four widths - the Enterprise pair carry a second lockup - so they are
   set by height and left to take whatever width their own ratio asks
   for. Capped at the column: the widest is nearly six times as wide as
   it is tall, and on a narrow column the height gives way rather than
   the badge running into the next cell. The height is twice the body
   size at every scale - 40 against the reader's 20, 30 against the
   phone's 16, 48 against a sheet's 24 - so the ladder reads at one
   weight wherever it appears. */
.article .tier-badge,
.tier-badge {
  display: block;
  width: auto;
  height: var(--badge-h, 40px);
  max-width: 100%;
  object-fit: contain;
  object-position: left center;
}


/* ============================================================
   SIDEBAR SHELL
   ============================================================ */
.sidebar {
  /* Fluid width; the reading column on the right stays fixed. */
  flex: 0 0 auto;
  width: var(--sidebar-w);
  min-width: 0;
  height: 100vh;
  background: var(--c-sidebar-bg);
  padding: var(--inset-top) var(--inset-x) 64px;
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
  /* Scrollable, but the bar itself is never drawn. */
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.sidebar::-webkit-scrollbar { width: 0; height: 0; display: none; }

.sidebar__inner {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: var(--gap-block);
}

/* ============================================================
   BRAND LOCKUP
   ============================================================ */
.brand-lockup { display: flex; align-items: center; gap: 16px; }
/* Widths drive the scale; height auto keeps each mark's ratio. Keyed to
   a class per mark, not nth-of-type: the order is a design decision and
   positional selectors quietly swap the two scales when it changes. */
.brand-lockup svg { display: block; height: auto; }
.brand-lockup__mark { display: flex; text-decoration: none; color: inherit; }
.brand-lockup__webflow svg { width: var(--logo-webflow-w); }
.brand-lockup__magier svg  { width: var(--logo-magier-w); }
.brand-lockup__x {
  font-family: 'Open Sans', var(--f-sans);
  font-size: 14px;
  line-height: 1.5;
  color: #000;
}

/* ============================================================
   BOOK TITLE
   ============================================================ */
.book-title-block { display: flex; flex-direction: column; gap: var(--gap-row); }
.book-title {
  font-family: var(--f-serif);
  font-weight: var(--w-semibold);
  font-size: var(--t-book-size);
  line-height: var(--t-book-lh);
  letter-spacing: var(--t-book-ls);
  color: var(--c-book-title);
}
.book-subtitle {
  font-family: var(--f-sans);
  font-weight: var(--w-regular);
  font-size: var(--t-sub-size);
  line-height: var(--t-sub-lh);
  letter-spacing: var(--t-sub-ls);
  color: var(--c-level-3);
  text-wrap: pretty;
}

/* ============================================================
   READING PROGRESS
   ============================================================ */
.progress { display: flex; flex-direction: column; gap: 16px; }
.progress__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 32px;
  font-family: var(--f-sans);
  font-weight: var(--w-medium);
  font-size: var(--t-prog-size);
  line-height: var(--t-prog-lh);
  letter-spacing: var(--t-prog-ls);
}
.progress__label { color: #000; }
.progress__value { color: var(--c-progress-fill); flex: none; }
.progress__track {
  height: 8px;
  border-radius: 100px;
  background: var(--c-progress-track);
  overflow: hidden;
}
.progress__fill {
  height: 100%;
  width: 0%;
  border-radius: 100px;
  background: var(--c-progress-fill);
  transition: width 120ms linear;
}

/* ============================================================
   TABLE OF CONTENTS — nested dropdowns
   ============================================================ */
/* The drag handle belongs to the mobile drawer only. */
.nav-drag-handle { display: none; }

/* Sections sit further apart than the rows inside them, so the four
   top-level groups read as distinct blocks. */
.toc { display: flex; flex-direction: column; gap: var(--gap-section-group); }

/* --- Toggle rows ---
   space-between is what makes the sidebar feel responsive: the label
   sits left, the chevron pins right, and the gap between them grows
   with the column. */
.dd__toggle {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--gap-row);
  min-height: var(--chevron-size);
  padding: 0;
  border: 0;
  background: none;
  text-align: left;
  cursor: pointer;
  color: inherit;
}
.dd__toggle:focus-visible {
  outline: 2px solid var(--c-focus);
  outline-offset: 3px;
  border-radius: 2px;
}

/* Section rows and chapter rows must size identically: the label takes
   all free space, the chevron is a fixed box pinned to the right edge.
   Without the explicit flex basis the label collapses toward min-content
   and the chevron floats in beside it, which is what made the four
   section rows disagree with every chapter row. */
.dd__row {
  display: flex;
  width: 100%;
  align-items: center;
  justify-content: space-between;
  gap: var(--gap-row);
  min-height: var(--chevron-size);
}
.dd__label--section {
  display: block;
  flex: 1 1 auto;
  min-width: 0;
}
/* Chapter labels sit in the same row shape as section labels. */
.dd--chapter > .dd__row { min-height: var(--chevron-size); }
.dd__toggle--chevron {
  flex: 0 0 var(--chevron-size);
  width: var(--chevron-size);
  height: var(--chevron-size);
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  color: inherit;
}
.dd__toggle--chevron:focus-visible {
  outline: 2px solid var(--c-focus);
  outline-offset: 3px;
  border-radius: 2px;
}
.dd__label--section:focus-visible {
  outline: 2px solid var(--c-focus);
  outline-offset: 3px;
  border-radius: 2px;
}
/* The PDF view's front-matter row is a button, not a link - it jumps
   within the document rather than navigating. A button arrives with a UA
   background, border, centred text and its own font, none of which
   belong on a row that has to sit flush with the labels around it. */
button.dd__label--section,
button.dd__label--chapter {
  appearance: none;
  padding: 0;
  border: 0;
  background: none;
  text-align: left;
  /* No font or colour reset here: `button.dd__label--section` outranks
     the plain-class rule that sets them further down, so resetting
     either would win and the row would inherit the bar's sans instead
     of the serif every other row uses. */
}
a.dd__label--section,
a.dd__label--chapter,
button.dd__label--section,
button.dd__label--chapter { cursor: pointer; }
/* --- The colour arriving from the left ------------------------------
   Pointing at a row turns its words blue from the left, over 200ms, and
   that is the whole signal - there is no underline any more. The line
   that used to pass under a row was a second mark saying the same thing,
   and two marks for one state read as two things happening. The
   mechanism is the one in the ICS Media piece - a gradient twice the width of the box, the two colours meeting
   dead on the half, clipped to the glyphs, and the whole thing slid one
   box-width to the left. At rest the right half is showing, which is the
   row's own ink; lit, the left half is, which is the brand blue. Nothing
   fades and no second copy of the text is involved, so there is no
   cross-fade to go muddy in the middle - each letter changes colour as
   the edge passes through it.

   The wipe is on a span of its own inside the text box rather than on the
   text box itself, because a chapter row must light its name and not its
   numeral - a span around the name alone is what buys that. It also kept
   the wipe off the same background as the underline while there still was
   one: two layers of one background, and Safari has never reliably
   honoured a per-layer `background-clip`.

   `--toc-ink` is the rest colour, declared on the row so every tier
   brings its own: navy for a section, black for a chapter name, the
   sidebar's ink for an item. `--toc-lit` is the blue, and the rule reads
   from the same token, so the two halves of the gesture cannot disagree.

   Both fragments of a wrapped label wipe from their own left edge rather
   than the second lagging behind the first, which is what
   `box-decoration-break: clone` buys: each line carries a gradient the
   width of its own words.

   Behind @supports because the fallback for an unsupported clip is
   invisible text, and behind any-hover for the pointer states so a tap
   on a phone does not leave a row half lit. */
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .toc-wipe {
    display: inline;
    box-decoration-break: clone;
    -webkit-box-decoration-break: clone;
    background-image: linear-gradient(to right,
        var(--toc-lit) 50%, var(--toc-ink, currentColor) 50%);
    background-repeat: no-repeat;
    background-size: 200% 100%;
    background-position: 100% 0;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    -webkit-text-fill-color: transparent;
    /* The stroke is what carries the weight - see the note below. It is
       declared here at zero so it has something to interpolate from. */
    -webkit-text-stroke-color: transparent;
    -webkit-text-stroke-width: 0;
    transition: background-position var(--hover-duration) var(--ease),
                -webkit-text-stroke-width var(--hover-duration) var(--ease);
  }

  @media (any-hover: hover) {
    a.dd__label--section:hover .toc-wipe,
    button.dd__label--section:hover .toc-wipe,
    a.dd__label--chapter:hover .toc-wipe,
    button.dd__label--chapter:hover .toc-wipe,
    a.toc-link--item:hover .toc-wipe,
    button.toc-link--item:hover .toc-wipe { background-position: 0 0; }
  }

  a.dd__label--section:focus-visible .toc-wipe,
  a.dd__label--chapter:focus-visible .toc-wipe,
  a.toc-link--item:focus-visible .toc-wipe,
  button.dd__label--section:focus-visible .toc-wipe,
  button.dd__label--chapter:focus-visible .toc-wipe,
  button.toc-link--item:focus-visible .toc-wipe,
  .dd__label--section[aria-current="true"] .toc-wipe,
  .dd__label--chapter[aria-current="true"] .toc-wipe,
  .toc-link[aria-current="true"] .toc-wipe { background-position: 0 0; }

  /* A page printed from the reader gets no background images, so the
     glyphs would come out of the printer blank. */
  @media print {
    .toc-wipe {
      background-image: none;
      color: var(--toc-ink, currentColor);
      -webkit-text-fill-color: currentColor;
    }
  }
}

/* --- The weight the current row settles at --------------------------
   The row the reader is on is heavier as well as blue, and the weight
   arrives over the same 200ms rather than snapping at the end of it.

   It cannot be done with `font-weight`. The faces are static instances -
   400, 500 and 600, with nothing in between - so there is no axis to
   interpolate along, and a variable font's `wght` transition would have
   nothing to move through. Worse, the 600 cut of both families is 6 to 8%
   wider than the 500, which is enough to push a label onto another line:
   measured across the contents, 18 rows at 1440px and 28 at 1280 gain a
   line when set in SemiBold, and an item row goes current on scroll -
   so the column would shuffle under the reader as they read. That is the
   same wall the weight transition hit the first time, and why there was
   no weight change here at all.

   What does work is inking the glyph rather than changing the face:
   `-webkit-text-stroke-width` interpolates smoothly, and because the
   stroke is transparent and the letters are painted by the wipe's
   gradient clipped to them, thickening the stroke thickens the mask - so
   the glyph grows and the gradient fills the larger shape. The reader
   sees the weight come up as the colour comes across. It costs no width
   at all: a stroke is drawn on the outline and takes part in no
   measurement, so not one row re-wraps and the column never moves.
   Per tier, because the distance to cover is not the same: a section
   label starts at 500 in the serif, a chapter name at 500 in the sans,
   an item at 400.

   Hovering does not touch the weight - only the row you are on is
   heavier, or every row the cursor passed would twitch. */
.dd__label--section { --toc-bold: 0.35px; }
.dd__label--chapter { --toc-bold: 0.4px; }
.toc-link--item     { --toc-bold: 0.5px; }

.dd__label--section[aria-current="true"] .toc-wipe,
.dd__label--chapter[aria-current="true"] .toc-wipe,
.toc-link[aria-current="true"] .toc-wipe {
  -webkit-text-stroke-width: var(--toc-bold, 0.4px);
}

.dd__label--chapter:focus-visible {
  outline: 2px solid var(--c-focus);
  outline-offset: 3px;
  border-radius: 2px;
}

.dd__label--section {
  font-family: var(--f-serif);
  font-weight: var(--w-medium);
  font-size: var(--t-l1-size);
  line-height: var(--t-l1-lh);
  letter-spacing: var(--t-l1-ls);
  color: var(--c-level-1);
  --toc-ink: var(--c-level-1);
}

.dd__label--chapter {
  display: flex;
  flex: 1 1 auto;
  align-items: baseline;
  gap: var(--chapter-num-gap);
  min-width: 0;
  font-family: var(--f-sans);
  font-weight: var(--w-medium);
  font-size: var(--t-l2-size);
  line-height: var(--t-l2-lh);
  letter-spacing: var(--t-l2-ls);
}
.chapter-num  {
  color: var(--c-chapter-num);
  flex: none;
  min-width: var(--chapter-num-w);
}
.chapter-name { color: var(--c-chapter-name); }
/* Only the name is lit and only the name carries the rule: the numeral
   is already the brand blue and stays exactly as it is. */
.dd__label--chapter { --toc-ink: var(--c-chapter-name); }
/* An appendix row names itself in the numeral's blue and carries its title
   in the prose ink on the same line: "Appendix A: What Webflow Enterprise
   Offers…". Inline, so a title long enough to wrap runs under the whole
   label rather than into a column of its own. */
.toc-num { color: var(--c-chapter-num); }

/* --- Chevron ---
   Points down when closed, flips up when the panel opens. */
.chevron {
  flex: 0 0 var(--chevron-size);
  width: var(--chevron-size);
  height: var(--chevron-size);
  color: var(--c-level-1);
  transition: transform var(--dd-duration) var(--ease);
}
.dd[data-open="true"] > .dd__toggle .chevron,
.dd[data-open="true"] > .dd__row .chevron { transform: rotate(180deg); }

/* --- Panel ---
   grid-template-rows 0fr -> 1fr animates to the content's natural
   height without hard-coding one, and the overflow:hidden child is the
   mask the rows are revealed through. */
.dd__panel {
  display: grid;
  grid-template-rows: 0fr;
  /* Clipping alone leaves the contents in the accessibility tree and in
     the tab order, so a collapsed chapter could still be reached with
     the keyboard. visibility removes them properly; the delay holds it
     visible for the length of the collapse animation. */
  visibility: hidden;
  transition: grid-template-rows var(--dd-duration) var(--ease),
              visibility 0s linear var(--dd-duration);
}
.dd[data-open="true"] > .dd__panel {
  grid-template-rows: 1fr;
  visibility: visible;
  transition: grid-template-rows var(--dd-duration) var(--ease),
              visibility 0s linear 0s;
}
.dd__inner { overflow: hidden; min-height: 0; }

.dd__inner > * { padding-top: var(--gap-row); }
.dd__inner .toc-list,
.dd__inner .dd { transition: none; }
/* The panel is revealed purely by its mask. Rows carry no transform of
   their own, so no text slides or nudges while a panel animates — the
   only motion in the sidebar is the mask edge and the chevron. */

/* Item lists sit 12px in from their chapter row, which is what makes the
   section / chapter / item tiers read as distinct without relying on
   type size alone. Bullets are markers on the li, so the marker lands in
   that indent and wrapped lines hang flush with the first line. */
.toc-list {
  display: flex;
  flex-direction: column;
  gap: var(--gap-row);
  padding-left: var(--indent-item);
}
/* Items belonging to a chapter align with the chapter's title rather
   than its number, so the text below "The €140K Landing Page" starts on
   the same vertical as the title itself. The number box is a fixed
   width, so this stays exact for every chapter from 01 to 13. */
.dd--chapter .toc-list {
  padding-left: calc(var(--chapter-num-w) + var(--chapter-num-gap));
}
/* No markers — the tiers are separated by indentation alone. */
.toc-item { list-style: none; }
.dd--chapter { display: block; }
.dd__inner > .dd + .dd { padding-top: var(--gap-row); }

/* --- Item links --- */
.toc-link {
  cursor: pointer;
  text-wrap: pretty;
}
/* A block, not a grid. The grid existed to stack the old SemiBold ghost
   on top of the label; with the ghost gone its only remaining effect was
   to blockify the text box, and an inline box is what lets the passing
   rule follow each line of a label that wraps.

   Not for a chapter row, though. In the print picker a chapter row is a
   .toc-link and a .dd__label--chapter at once - which is what makes its
   hover and current states the reader's - and the two rules set `display`
   at the same weight, so whichever came last won. This one came last, and
   the numeral's gutter went with it: "01The €140K Landing Page". Written
   as an exception so the row keeps the flex the numeral column needs. */
.toc-link:not(.dd__label--chapter) { display: block; }
/* The row used to slide five pixels towards the reader on hover. It is
   the colour wipe now and nothing moves: a label that shifts under the
   cursor takes the eye off the word it is trying to read, and in a column
   of thirty rows the movement was the loudest thing in the sidebar. */

.toc-link--item {
  font-family: var(--f-sans);
  font-size: var(--t-l3-size);
  line-height: var(--t-l3-lh);
  letter-spacing: var(--t-l3-ls);
  color: var(--c-level-3);
  font-weight: var(--w-regular);
  --toc-ink: var(--c-level-3);
}
/* Hover does not change weight here either - the colour is the whole
   signal. What the current row does take is set further up, and it is
   inked rather than a change of face, because the faces are static
   instances and cannot be interpolated. */
.toc-link:focus-visible {
  outline: 2px solid var(--c-focus);
  outline-offset: 3px;
  border-radius: 2px;
}
/* Sub-headers previously carried an extra offset. All items now share
   the same indentation, so the modifier is intentionally inert. */
.toc-item--nested { padding-left: 0; }


/* ============================================================
   PDF VIEW
   ------------------------------------------------------------
   Fixed 1191 x 1685 pages, stacked and scrolled on screen, one
   sheet per page in print. Geometry is lifted from the Figma
   frame (node 658:524 title, 658:748 content); nothing here is
   eyeballed.

   Loads on top of the reader's own brand/tokens/article/
   components CSS, so every component keeps its behaviour and
   only the page furniture and the type scale change.
   ============================================================ */
:root {
  /* --- Sheet ------------------------------------------------ */
  --pg-w: 1191px;
  --pg-h: 1685px;

  /* --- Content frame (Figma "Frame 20": x127 y50.255 w936) -- */
  --pg-inset-x:   127px;
  --pg-inset-top: 50.255px;
  --pg-frame-w:   936px;

  /* Eyebrow row, then the body frame 110px below it. */
  --pg-eyebrow-h: 11px;
  --pg-body-top:  110px;

  /* The article spans the whole frame, so the text lines up with the
     running head above it and the rule below it. It used to sit 84px
     inside at 768px wide, which left both edges hanging. */
  --pg-article-x: 0px;
  --pg-article-w: var(--pg-frame-w);

  /* The three numbers that define the page: the article box is
     1255px tall, then 156px of air, then the 63.489px footer.
     1255 + 156 = 1411 = the footer's offset in Figma. */
  --pg-article-h:  1255px;
  /* A figure plus its caption has to leave room for the caption and a
     little air, so the picture itself is capped short of the box. */
  --pg-figure-max-h: 1150px;
  /* A figure given the page reaches into the air below the article box.
     The footer sits at 1255 + 156 = 1411, so this stops well clear. */
  --pg-article-full-h: 1390px;
  --pg-footer-gap: 156px;
  --pg-footer-h:   63.489px;

  /* Separator to logos. */
  --pg-rule-gap: 26px;

  /* --- Vertical rhythm (Figma 658:748) ---------------------------
     Read straight off the frame, as cap-to-baseline gaps:
       chapter-intro  y0   h115   -> section at y161  = 46
       section title  h17        -> body at y46       = 29
       section 1 ends y893       -> section 2 at y939 = 46
     The paragraph value is the one Figma does not state as a gap:
     its body is a single text layer using blank lines, so one
     paragraph break is one extra 32px line advance. Measured from
     the baseline of the last line to the cap of the next, that is
     32 + 32 - capHeight = 50px, which is what reproduces the
     frame's 686px body height exactly. */
  --pg-gap-section: 46px;
  --pg-case-pad:   32px;
  --pg-gap-heading: 29px;
  --pg-gap-para:    50px;
  --pg-gap-block:   50px;
  /* Two framed blocks in a row need more air than two paragraphs, or
     the pair reads as one container with a line through it - but less
     than the 72px that had them reading as separate spreads. */
  --pg-gap-frame:   56px;
}

/* ============================================================
   TYPE SCALE OVERRIDES
   A sheet is read at arm's length, so print sets its own scale
   rather than inheriting the reader's - straight from the Figma
   frame. Every --size-* primitive the reader declares is pinned
   here, so a change to the reader's scale cannot move the sheets.
   ============================================================ */
:root {
  --size-display: 44px;  --lh-display: 1.1;  --track-display: -0.05em;
  --size-body:    24px;  --lh-body:    1.6;  --track-body:    -0.03em;
  --size-heading: 28px;  --lh-heading: 1.3;  --track-heading: -0.04em;
  /* Small on a sheet is only a shade under the running text: the scoring
     notes, the table cells and the labels are content, and at the
     reader's proportion they read as small print at this size. */
  --size-small:   22px;
  --size-ui:      20px;
  /* The reader's caption steps up with everything else. */
  --size-caption: 18px;
  /* Component type - the highlights, the quote box, the blue headers on
     a panel or a cluster - is built from these, so it steps up with the
     running text instead of staying at the reader's size and reading as
     small print on a sheet. The lead sits between body and heading: on
     paper the scale is compressed at the top, so it takes 26 rather than
     the reader's four-point step. */
  /* Footnote apparatus is deliberately under the running text. The
     in-text reference keeps its superscript size against a 24px body;
     the list at the foot of the chapter is set small on purpose. */
  --size-marker:   12px;
  --size-footnote: 14px;
  --size-title:   36px;
  --size-lead:    26px;
  /* The figures were carrying the whole scale's step-up and reading as
     display type next to their own labels. */
  --size-stat:    32px;
  --size-stat-sm: 30px;
  /* Code on a sheet, and the table cells. Held at the values the sheets
     were signed off at: the reader's scale moved four points and the
     sheets are set from the frame, not from it. */
  --size-min:       14px;
  --size-table:     24px;
  --size-table-sm:  22px;
  --size-table-cap: 18px;
  --size-chrome:    24px;
  --size-chrome-sm: 22px;
  --size-chrome-lg: 36px;
  /* Prose runs the full article column here; there is no wider
     element to measure against on a fixed sheet. */
  --measure-w: var(--pg-frame-w);
}
/* Figma sets the standfirst tighter than running text. */
.chapter-standfirst { line-height: 1.3; letter-spacing: -0.04em; }

/* --- The trim model ----------------------------------------------
   Every text layer in the Figma frame is set to trim-both with a
   cap/alphabetic edge: its box starts at the cap height and ends at
   the baseline, with no half-leading. That is why the frame's gaps
   read as 29px and 46px - they are cap-to-baseline distances, not
   box-to-box distances.

   Without trimming, a browser adds half the leading above and below
   every block, so those same numbers render 13-16px looser than the
   design. Trimming here means the Figma gap values can be used
   verbatim below, and they land exactly.

   Chrome 133+/Safari 18.2+. Older browsers keep half-leading and
   read slightly looser - the @supports block further down pulls the
   margins in to compensate. */
.sheet .chapter-heading,
.sheet .chapter-standfirst,
.sheet .section-heading,
.sheet .flow > p,
.sheet .sheet__chapter,
.sheet .sheet__title,
.sheet .sheet__page {
  text-box: trim-both cap alphabetic;
}

/* ============================================================
   SHELL
   ============================================================ */
/* Both, not just html: base.css paints the body for the reader, and
   leaving that in place put a second grey behind the sheets. */
html,
body { background: var(--c-viewer-surface); }
body {
  margin: 0;
  padding: 40px 0 80px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 40px;
}

.page {
  position: relative;
  flex: none;
  width: var(--pg-w);
  height: var(--pg-h);
  overflow: hidden;
  background: var(--white);
  /* Screen only - the shadow is what separates one sheet from
     the next while scrolling. Removed for print below. */
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.14);
}

/* --- Preview scale ------------------------------------------
   On screen the sheet shows at 888 x 1256.32, which is
   888/1191 of full size. Deliberately a transform and not zoom:
   a transform paints smaller without touching layout, so the
   sheet still measures 1191 x 1685 everywhere it matters - the
   paginator's decisions and the printed PDF are untouched. zoom
   would re-run layout, re-break the lines, and the preview would
   stop agreeing with the file.

   The negative margins take back the space the untransformed box
   still occupies, so the sheets stay centred and evenly spaced. */
@media screen {
  :root { --preview-scale: 0.7455919; }   /* 888 / 1191 */
  .page {
    transform: scale(var(--preview-scale));
    transform-origin: top left;
    margin-right:  calc(var(--pg-w) * (var(--preview-scale) - 1));
    margin-bottom: calc(var(--pg-h) * (var(--preview-scale) - 1));
  }
  /* Clearance so the last sheet is not trapped under the bar. */
  body { padding-bottom: 260px; }
}

/* ============================================================
   TITLE PAGE
   The approved Figma export, dropped in whole at 2x (2382 x 3370
   for a 1191 x 1685 sheet) so it stays crisp in print. No markup
   to drift out of sync with the design.
   ============================================================ */
.cover-img {
  display: block;
  width: var(--pg-w);
  height: var(--pg-h);
}
/* Browsers drop large background colours when printing unless told
   otherwise, which would print the cover white. */
.page--title,
.cover-img {
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
}

/* ============================================================
   CONTENT PAGE  (Figma 658:748)
   ============================================================ */
.sheet {
  position: absolute;
  left: var(--pg-inset-x);
  top: var(--pg-inset-top);
  width: var(--pg-frame-w);
}

/* --- Eyebrow ------------------------------------------------ */
.sheet__eyebrow {
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: var(--pg-eyebrow-h);
  white-space: nowrap;
}
.sheet__chapter {
  font-family: var(--f-serif);
  font-weight: var(--w-medium);
  font-size: 16px;
  line-height: 1.6;
  letter-spacing: -0.04em;
  color: var(--blue-500);
}
.sheet__title {
  font-family: var(--f-sans);
  font-weight: var(--w-regular);
  font-size: 16px;
  line-height: 1.6;
  letter-spacing: -0.03em;
  color: #929292;
}

/* --- Body frame --------------------------------------------- */
.sheet__body {
  position: absolute;
  left: 0;
  top: var(--pg-body-top);
  width: var(--pg-frame-w);
}

/* The article column. Fixed height, clipped: the paginator's job
   is to make sure nothing ever needs the clip, and the clip is
   there so a mistake shows up as a missing block rather than as
   text silently sliding under the footer. */
/* The box spans the whole frame and holds the text column in with
   padding, rather than being 768 wide and offset. Same measure, but a
   table too wide for the column can now bleed out into the margins
   instead of being cut off by the clip - the clip sits at the frame
   edge, where it belongs on a sheet that cannot scroll. */
.sheet__article {
  position: relative;
  width: var(--pg-frame-w);
  padding-inline: var(--pg-article-x);
  height: var(--pg-article-h);
  overflow: hidden;
}

/* A card whose table will not fit the measure. The reader lets these
   scroll sideways (see .card--matrix); a sheet has nowhere to scroll,
   so it takes the margins instead. Tagged by pdf.js, which measures. */
.sheet .flow > .card.is-wide {
  width: var(--pg-frame-w);
  margin-inline: calc(var(--pg-article-x) * -1);
}
/* Nothing scrolls on paper: the table shows in full or not at all. */
.sheet .card--matrix,
.sheet .card__scroll { overflow: visible; }
.sheet .card--matrix .dtable { min-width: 0; }
/* Sticky columns are a scrolling affordance and have no meaning here. */
.sheet .card--matrix .dtable thead th:first-child,
.sheet .card--matrix .dtable tbody td.dtable__label { position: static; }
/* Last resort, applied by pdf.js when even the full frame is too narrow:
   scale the card down rather than clip a column off the page. */
.sheet .flow > .card.is-shrunk {
  transform: scale(var(--card-fit, 1));
  transform-origin: top left;
}
/* Trimming has to apply to the whole heading, not to its two
   spans: trimmed separately they each lose their leading and the
   number sits on top of the title. article.css makes the spans
   blocks; inline plus a hard newline gives the h1 real line boxes,
   so the lines keep their 44px advance and the trim applies once,
   across the pair - which is how Figma's single text layer with
   two lines measures 72px tall. */
.sheet .chapter-heading__num,
.sheet .chapter-heading__title { display: inline; }
.sheet .chapter-heading__num::after { content: '\A'; white-space: pre; }

/* --- Flow spacing ------------------------------------------
   The paginator flattens sections, so vertical rhythm cannot
   come from a flex gap on a wrapper any more. Every gap is a
   margin-top on the following block, which also means the
   first block on a page always sits flush to the top. */
/* The container keeps the .article class so every descendant
   selector in article.css/components.css still applies, but the
   reader's own flex column and 46px section gap must go: on the
   sheet the rhythm comes from the margins below, and leaving the
   gap in place added 46px on top of every one of them. */
.flow {
  display: block;
  gap: 0;
  width: 100%;
  max-width: none;
  margin: 0;
}
.flow > * { margin: 0; }
.flow > * + * { margin-top: var(--pg-gap-para); }
.flow > .card,
.flow > .figure,
.flow > .highlights,
.flow > .stat-grid,
.flow > .file-card,
.flow > .numbered,
.flow > .byline,
.flow > .code-card { margin-top: var(--pg-gap-block); }
/* Between sections, and from a section heading to its body -
   both Figma values, and both must beat the block rule above. */
.flow > .section-heading { margin-top: var(--pg-gap-section); }
.flow > .chapter-intro + *,
.flow > .flow-footnotes { margin-top: var(--pg-gap-section); }
.flow > .section-heading + * { margin-top: var(--pg-gap-heading); }
.flow > :first-child { margin-top: 0; }
.sheet .chapter-intro { gap: var(--pg-gap-heading); }

/* --- Footer ------------------------------------------------- */
.sheet__foot {
  position: absolute;
  left: 0;
  /* Offset inside .sheet__body, which already carries
     --pg-body-top - adding it again double-counts it. */
  top: calc(var(--pg-article-h) + var(--pg-footer-gap));
  width: var(--pg-frame-w);
  height: var(--pg-footer-h);
}
.sheet__rule {
  border: 0;
  border-top: 1px solid var(--grey-300);
  margin: 0;
  width: 100%;
}
.sheet__footrow {
  position: absolute;
  top: var(--pg-rule-gap);
  left: 0;
  width: 100%;
  height: 37.489px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.sheet__lockup { display: flex; align-items: center; gap: 17.512px; }
.sheet__lockup svg { display: block; }
.sheet__lockup .fl-webflow { width: 121.995px; height: 20.561px; }
.sheet__lockup .fl-magier  { width: 120.224px; height: 37.489px; }
.sheet__lockup__x {
  font-family: var(--f-sans);
  font-size: 13.707px;
  line-height: 1.5;
  color: var(--black);
}
.sheet__page {
  font-family: var(--f-sans);
  font-weight: var(--w-regular);
  font-size: 16px;
  line-height: 1.6;
  letter-spacing: -0.03em;
  color: var(--grey-900);
  white-space: nowrap;
}
.sheet__page b { font-weight: var(--w-semibold); color: var(--blue-500); }

/* ============================================================
   ARTICLE TWEAKS FOR A FIXED SHEET
   ============================================================ */
.sheet .article section { gap: 29px; }          /* Figma title-to-body */
.sheet .chapter-intro { gap: 29px; }
.sheet .footnotes { margin-top: 0; }
/* Footnote numbering can resume mid-list when the notes span two
   sheets; the paginator sets --fn-offset on the continuation. */
.sheet .footnotes__list { counter-reset: footnote var(--fn-offset, 0); }
/* Nothing on a print sheet is interactive, so links read as text
   colour and only the URL keeps the brand colour. */
.sheet .article a { color: var(--c-brand); }
/* The reader suppresses italics by design. On the sheet the Figma
   frame sets the same emphasis in semibold, so it carries weight
   here instead of disappearing. */
.sheet .article em { font-style: normal; font-weight: var(--w-semibold); }

/* Browsers without text-box trimming add half-leading above and
   below each block. Pull the gaps in by roughly that much so the
   rhythm still reads as designed rather than 30% looser. */
@supports not (text-box: trim-both cap alphabetic) {
  :root {
    --pg-gap-section: 30px;
    --pg-gap-heading: 13px;
    --pg-gap-para:    32px;
    --pg-gap-block:   32px;
  }
}

/* ============================================================
   BOTTOM BAR  (Figma 672:546)
   Screen furniture: a fade band across the foot of the viewport,
   with the lockup pill and the download control floating on it.
   None of it prints.
   ============================================================ */
.viewer-fade {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  height: 189px;                 /* Figma: the band is 189 tall */
  z-index: 9;
  pointer-events: none;
  /* Same multi-stop ramp the reader uses on its reading pane: a
     two-stop fade leaves a visible edge where it terminates. */
  background: linear-gradient(
    to top,
    rgb(var(--c-viewer-fade-rgb) / 1)    0%,
    rgb(var(--c-viewer-fade-rgb) / 0.92) 22%,
    rgb(var(--c-viewer-fade-rgb) / 0.72) 42%,
    rgb(var(--c-viewer-fade-rgb) / 0.42) 62%,
    rgb(var(--c-viewer-fade-rgb) / 0.15) 82%,
    rgb(var(--c-viewer-fade-rgb) / 0)   100%
  );
}

.viewer-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 41.54px;               /* Figma: 1117 - 1075.46 */
  z-index: 10;
  display: flex;
  justify-content: center;
  padding-inline: 24px;
  pointer-events: none;          /* only the pill is clickable */
}
.viewer-bar__pill {
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: 32px;                     /* Figma 676:1151 */
  /* The pill is ~930px at the design's sizes. Left to its intrinsic
     width it runs off both edges of a window narrower than that and
     the ends get cut. It shrinks instead: the lockup and the controls
     hold their size, the picker gives way, and the chapter name
     ellipsises. */
  max-width: 100%;
  min-width: 0;
  padding: 12px 24px;
  border-radius: 108px;
  background: var(--c-viewer-bar-bg);
  border: 1px solid var(--c-viewer-bar-line);
}
.viewer-bar__lockup,
.viewer-bar__actions { flex: none; }
.viewer-picker { flex: 0 1 auto; min-width: 0; }

/* --- Lockup ------------------------------------------------- */
.viewer-bar__lockup {
  display: flex;
  align-items: center;
  gap: 18.434px;
}
.viewer-bar__lockup svg { display: block; }
.viewer-bar__lockup a { display: flex; text-decoration: none; color: inherit; }
.viewer-bar .vb-webflow { width: 128.416px; height: 21.643px; }
.viewer-bar .vb-magier  { width: 126.552px; height: 39.462px; }
.viewer-bar__x {
  font-family: var(--f-sans);
  font-size: 14.429px;
  line-height: 1.5;
  color: var(--black);
}

/* --- Actions cluster ---------------------------------------- */
.viewer-bar__actions {
  display: flex;
  align-items: center;
  gap: 24px;                     /* Figma: 36px boxes, 24px apart */
}
/* The reader's buttons are 32px; on the bar they are 36. */
.viewer-bar__actions .action-button {
  width: 36px;
  height: 36px;
  color: var(--c-icon);
  text-decoration: none;
}
/* The tooltip hangs below the button in the reader. Here the bar is at
   the foot of the window, so it has to sit above instead - the same
   reveal, mirrored. The size comes from --t-tip-size, which the reader
   uses too: the bar is window furniture at a fixed size, not part of the
   sheet's type scale, so it does not follow --size-small up to the
   sheet's 22px - and the two views name their icons at one size. */
.viewer-bar__actions .action-button__bubble {
  top: auto;
  bottom: calc(100% + var(--bubble-offset));
  transform: translate(-50%, 4px);
}
.viewer-bar__actions .action-button:hover .action-button__bubble,
.viewer-bar__actions .action-button:focus-visible .action-button__bubble,
.viewer-bar__actions .action-button.is-tipped .action-button__bubble {
  transform: translate(-50%, 0);
}

/* --- Chapter picker ----------------------------------------
   The trigger reads the sheet you are on; the panel above it is the
   reader's sidebar dropdown, opening upward. */
.viewer-picker { position: relative; }
.viewer-picker__trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* Fixed width, from Figma. A chapter name longer than the one the
     design was drawn with would otherwise resize the whole bar as you
     scroll, so the name truncates instead. */
  width: 299px;
  max-width: 100%;
  min-width: 0;
  padding: 6px 12px;
  border: 1px solid var(--c-viewer-card-line);
  border-radius: 100px;
  background: var(--c-viewer-card-bg);
  cursor: pointer;
  font-family: var(--f-sans);
  font-weight: var(--w-medium);
  font-size: 14px;
  line-height: 1.3;
  letter-spacing: -0.05em;
  white-space: nowrap;
}
.viewer-picker__label {
  display: flex;
  align-items: center;
  gap: 14px;                     /* Figma 676:1166 */
  min-width: 0;
}
.viewer-picker__trigger .chapter-num  { color: var(--c-chapter-num); flex: none; }
.viewer-picker__trigger .chapter-name {
  color: var(--c-chapter-name);
  overflow: hidden;
  text-overflow: ellipsis;
  /* Without this the name refuses to shrink below its content and
     pushes the chevron out of the pill. */
  min-width: 0;
}
.viewer-picker__trigger:focus-visible {
  outline: 2px solid var(--c-focus);
  outline-offset: 2px;
}
/* Same rule the sidebar uses, keyed to this trigger. */
.viewer-picker[data-open="true"] .viewer-picker__trigger .chevron {
  transform: rotate(180deg);
}

/* The panel is bottom-anchored, so the grid-rows animation it inherits
   from .dd__panel grows it upward and the card is revealed from its
   own bottom edge - the button reveal, run the other way. */
/* The panel does NOT use the sidebar's mask. It fades and settles into
   place instead - the SmoothUI basic-dropdown behaviour, mirrored for a
   menu that opens upward: their panel comes from y:-10 with origin-top,
   so ours comes from y:+10 with origin-bottom.

   Their transition is a spring (duration .25, bounce .1); the closest
   honest CSS equivalent is a short cubic-bezier with a little overshoot.
   Closing is quicker than opening, as it is there. */
.viewer-picker__panel {
  --vp-card: 453px;
  position: absolute;
  left: 50%;
  /* Measured from the trigger pill, which sits inside the bar's own
     padding - so the clearance to the bar's outer edge is ~10px. */
  bottom: calc(100% + 24px);
  width: var(--vp-card);
  max-width: calc(100vw - 48px);
  margin-left: calc(var(--vp-card) / -2);

  /* Override the inherited mask outright. */
  display: block;
  grid-template-rows: none;
  transform-origin: bottom center;
  opacity: 0;
  transform: translateY(10px) scaleY(0.8);
  visibility: hidden;
  transition: opacity 150ms var(--ease),
              transform 150ms var(--ease),
              visibility 0s linear 150ms;
}
.viewer-picker[data-open="true"] > .viewer-picker__panel {
  opacity: 1;
  transform: translateY(0) scaleY(1);
  visibility: visible;
  transition: opacity 250ms cubic-bezier(0.22, 1.15, 0.36, 1),
              transform 250ms cubic-bezier(0.22, 1.15, 0.36, 1),
              visibility 0s linear 0s;
}
/* No mask, so the inner is just a pass-through and the card's shadow is
   free to spill. */
.viewer-picker__panel > .dd__inner {
  overflow: visible;
  padding: 0;
}
.viewer-picker__panel > .dd__inner > .viewer-toc { padding-top: 20px; }

/* Rows arrive with the panel, staggered, from x:-10 - their motion.li.
   --row-i is set per row by the builder so the delay is data, not a
   pile of nth-child rules. */
/* Two classes deep on purpose: sidebar.css has `.dd__inner .dd
   { transition: none }` to keep rows still while a panel masks, and
   these groups are .dd elements inside a .dd__inner. At equal
   specificity the later file wins, so this has to match it. */
.viewer-toc .viewer-toc__group {
  opacity: 0;
  transform: translateX(-10px);
  transition: opacity 200ms var(--ease), transform 200ms var(--ease);
}
.viewer-picker[data-open="true"] .viewer-toc .viewer-toc__group {
  opacity: 1;
  transform: translateX(0);
  transition-delay: calc(var(--row-i, 0) * 40ms + 60ms);
}
@media (prefers-reduced-motion: reduce) {
  .viewer-picker__panel,
  .viewer-picker[data-open="true"] > .viewer-picker__panel,
  .viewer-toc .viewer-toc__group,
  .viewer-picker[data-open="true"] .viewer-toc .viewer-toc__group {
    transform: none;
    transition-duration: 1ms;
    transition-delay: 0s;
  }
}

.viewer-toc {
  display: flex;
  flex-direction: column;
  gap: 24px;
  padding: 20px;
  border: 1px solid var(--c-viewer-card-line);
  border-radius: 24px;
  background: var(--c-viewer-card-bg);
}
.viewer-toc__group > .dd__row { gap: 12px; }
/* The sheet's type scale puts --size-body well above the reader's, and
   the sidebar's section label reads from it. In the picker it stays at
   16px. */
.viewer-toc .dd__label--section { font-size: 16px; line-height: 1.3; }
.viewer-toc .toc-list { gap: 20px; }
.viewer-toc .dd__inner > * { padding-top: 20px; }
/* A chapter whose pages are not in this document stays a label. */
.viewer-toc .toc-link.is-absent {
  cursor: default;
  opacity: 0.45;
}
/* The row keeps the sidebar's grid so the ghost underneath still does
   its job; only the type and the button reset are ours. */
.viewer-toc .toc-link {
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  font-family: var(--f-sans);
  font-weight: var(--w-medium);
  font-size: 14px;
  line-height: 1.3;
  letter-spacing: -0.05em;
  text-align: left;
}
.viewer-toc button.toc-link { cursor: pointer; }
/* The picker's rows carry the reader's classes, so the colour arriving
   from the left and the weight the current row settles at both come from
   sidebar.css unchanged - there is no second copy here
   to drift out of step. What the picker keeps of its own is the type
   scale above and the two lines below, because a sheet's --size-body sits
   well above the reader's and the picker reads at 14px whatever the page
   is set at. .viewer-toc__row is gone with the hand-kept copy: the row is
   .dd__label--chapter now, which is where its flex and its numeral
   gutter come from. */
.viewer-toc .chapter-num  { color: var(--c-chapter-num); flex: none; min-width: var(--chapter-num-w); }
.viewer-toc .chapter-name { color: var(--c-chapter-name); }
/* The rest colour the wipe reads from has to be restated wherever the
   colour above is, or the gradient's unlit half would fall back to the
   row's own transparent text. */
.viewer-toc .dd__label--chapter { --toc-ink: var(--c-chapter-name); }
/* An item row reads at the reader's weight too, not the picker's medium -
   the rest states have to match as much as the lit ones do. */
.viewer-toc .toc-link--item     { --toc-ink: var(--c-level-3); font-weight: var(--w-regular); }
.viewer-toc .dd__label--section { --toc-ink: var(--c-level-1); }

/* --- Download control --------------------------------------- */
.viewer-bar__download {
  display: flex;
  align-items: center;
  padding: 6px 12px;
  border-radius: 100px;
  background: var(--c-viewer-btn-bg);
  box-shadow: var(--shadow-bubble);
  text-decoration: none;
  transition: background var(--duration) var(--ease);
}
.viewer-bar__label {
  font-family: var(--f-sans);
  font-weight: var(--w-semibold);
  font-size: 15px;
  line-height: 1.3;
  letter-spacing: -0.04em;
  color: var(--c-viewer-btn-text);
  text-box: trim-both cap alphabetic;
  white-space: nowrap;
}
.viewer-bar__icon {
  display: grid;
  place-items: center;
  padding: 5.363px;
  border-radius: 5.899px;
}
.viewer-bar__icon svg {
  display: block;
  width: 13.274px;
  height: 13.274px;
  stroke: var(--c-viewer-btn-text);
}

/* --- Hover -------------------------------------------------
   Figma 672:786 could not be read (Dev Mode refused the node),
   so this follows the reader's own button convention: the fill
   deepens and the arrow nudges the way it points. */
.viewer-bar__download:hover { background: var(--c-viewer-btn-hover); }
.viewer-bar__icon svg { transition: transform var(--duration) var(--ease); }
.viewer-bar__download:hover .viewer-bar__icon svg { transform: translateY(1px); }
.viewer-bar__download:focus-visible {
  outline: 2px solid var(--c-viewer-btn-bg);
  outline-offset: 3px;
}

/* ============================================================
   PRINT
   One .page per sheet, at exactly the Figma page size.
   ============================================================ */
@page {
  /* The Figma export is 1191 x 1685 POINTS (A2). The sheet is built in
     CSS px, and 1191px is only 893pt, so printing the px size would
     hand back a correctly proportioned but three-quarter-size page.
     Asking for the point size and zooming the sheet by 96/72 lands on
     the same page as the design. zoom, not transform: zoom feeds back
     into layout, so the fixed sheet boxes still break where they did
     on screen. */
  size: 1191pt 1685pt;
  margin: 0;
}
@media print {
  html,
  body { background: none; }
  body { margin: 0; padding: 0; gap: 0; display: block; }
  .page {
    box-shadow: none;
    margin: 0;
    zoom: 1.3333333;          /* 96/72 - px sheet onto a pt page */
    break-after: page;
    page-break-after: always;
  }
  .page:last-child { break-after: auto; page-break-after: auto; }
  .viewer-fade,
  .viewer-bar,
  .viewer-picker__panel { display: none; }
}

/* --- Case box on paper ---
   The reader shows the case study as one tinted panel. It is several
   sheets long in print, so the panel is assembled from its parts: each
   carries the fill, consecutive parts sit flush, and the padding that
   would be the box's inset is applied at whichever part meets the top or
   bottom of the sheet. */
.flow > .casebox-part {
  background: var(--c-case-bg);
  padding-inline: var(--pg-case-pad);
}
.flow > .casebox-part + .casebox-part {
  margin-top: 0;
  padding-top: var(--pg-gap-para);
}
.flow > .casebox-part.is-case-top { padding-top: var(--pg-case-pad); }
.flow > .casebox-part.is-case-bottom { padding-bottom: var(--pg-case-pad); }
/* The panel is rounded wherever it meets the edge of a sheet, not only
   at the two ends of the case study. A box carried over the page break
   should read as a finished box on both pages rather than one with its
   corners sawn off. */
.flow > .casebox-part[data-case-first],
.flow > .casebox-part.is-case-top {
  border-top-left-radius: var(--radius);
  border-top-right-radius: var(--radius);
}
.flow > .casebox-part[data-case-last],
.flow > .casebox-part.is-case-bottom {
  border-bottom-left-radius: var(--radius);
  border-bottom-right-radius: var(--radius);
}
/* Its sub-headings keep the clearance a heading gets, minus the gap the
   part rule already added. */
.flow > .casebox-part.casebox__heading { margin-top: 0; padding-top: var(--pg-gap-section); }
.flow > .casebox-part.casebox__heading.is-case-top { padding-top: var(--pg-case-pad); }

/* --- Bar chart on paper ---
   Static: pdf.js never arms it, so the fills are already at their final
   width. This only sizes it for the sheet. */
.sheet .barviz { gap: 24px; }
.sheet .barviz__track { height: 40px; border-radius: 20px; }
.sheet .barviz__fill { border-radius: 20px; }

/* The reader scopes this to .casebox, which the sheets unwrap, so the
   print stat grid needs telling too. */
.flow > .casebox-part .stat-grid,
.flow > .casebox-part.stat-grid,
.flow > .stat-grid { grid-template-columns: repeat(2, 1fr); }

/* --- Square figures on paper --- */
.sheet .squares { max-width: 100%; }

/* --- Calendar on paper ---
   Tighter cells than the reader's, so three days of agenda fit the
   sheet's measure without the strip growing taller than it needs. */
.sheet .calendar__head { padding: 10px 14px; }
.sheet .calendar__body { padding: 14px; }

/* --- Chart box on paper --- */
.sheet .barviz { padding: 20px; }

/* --- Charts and boxed lists on paper ---
   Static: pdf.js never arms them, so the bars are full and the pie is
   whole. This only fits them to the sheet's measure. */
.sheet .chart-box { padding: 20px; max-width: 100%; }
/* A table fills its frame edge to edge - the frame is the table's own
   border, and padding inside it only narrows the columns. */
.sheet .chart-box--flush { padding: 0; }
.sheet .pie-chart { grid-template-columns: 200px 1fr; gap: 24px; }
.sheet .pie { max-width: 200px; }

/* --- Range chart on paper ---
   Static: pdf.js never arms it, so every band is drawn. Narrower name
   and value columns so the plot keeps its width on the sheet. */
.sheet .range-chart__head,
.sheet .range-row { grid-template-columns: 132px 1fr 118px; }


/* --- Containers carried across sheets ---
   .panel and .cluster are rebuilt page by page by the paginator, so
   each sheet holds a complete container with all four corners drawn.
   Nothing to add for the frame itself - the reader's own rule paints
   it - but a container that opens a page has no run-in above it, and
   one that ends a page should not push its last rule to the edge. */
.flow > .panel:first-child,
.flow > .cluster:first-child { margin-top: 0; }

/* --- Figures on a sheet ---
   A tall export (the partner rubric is two and a half times as tall as
   it is wide) has to fit the page box or the paginator can only warn
   about it. Bounded by height, it keeps its ratio and its resolution:
   the cap only ever scales an image down. */
.sheet .figure__image {
  /* --fig-print-max is worked out per figure at build time: the widest
     the picture may be drawn and still clear both the column and the
     page box, at its own ratio. Asking the browser to clamp width and
     height at once instead would squash a tall export, since the two
     caps are applied independently. */
  width: min(100%, var(--fig-print-max, var(--fig-max, 100%)));
  max-width: 100%;
  max-height: none;
  height: auto;
}


/* An element with no size of its own inherits from the document, which
   is still the reader's body size - so a bare container on a sheet was
   setting its text eight points below the running text around it. */
.sheet .article { font-size: var(--t-body-size); }


/* At the sheet's type a long tier name is wider than its column, and a
   fixed table lets it hang over the edge - which the width fit reads as
   an oversized table and scales the whole thing down. Let it wrap. */
/* A sheet is set at print size, so the badge is drawn larger with it. */
.sheet .tier-badge { --badge-h: 48px; }
/* The badge column is sized from the widest badge, not from the words.
   A sheet drops the reader's colgroup and lets the browser balance the
   columns against their content, which gave this one 209px - enough for
   the two single lockups and not for the two Enterprise ones, which are
   5.8 times as wide as they are tall. They capped on width and their
   height gave way, so two rungs of the ladder drew smaller than the
   other two. 312px is the widest badge at its full height plus the
   cell's own 16px either side, with a few pixels to spare. */
.sheet .dtable--tier th:first-child,
.sheet .dtable--tier td:first-child { width: 316px; }


/* Paper has no hover, and the definition list at the foot of the chapter
   carries every note the tooltip would have shown. Left in, the bubble
   is still laid out: inside a fixed table cell it hangs past the edge,
   which the width fit reads as an oversized table and scales the whole
   thing down. */
.sheet .footnote-tip { display: none; }


/* --- In Numbers, and every stat set on a sheet ---
   Two across rather than three: at the sheet's type a third column
   leaves each figure a measure too narrow for its sentence, and a set
   of four reads as a square rather than a row of three and an orphan. */
.sheet .squares { grid-template-columns: repeat(2, 1fr); }
.sheet .squares--stacked { grid-template-columns: 1fr; }

/* --- Code samples carried across sheets --- */
.sheet .code-line { display: block; }

/* --- Reader takeaway ---
   Rebuilt as its own panel by the paginator, so it needs the block
   spacing a container gets rather than the run-in a paragraph gets. */
.flow > .takeaway { margin-top: var(--pg-gap-section); padding: var(--pg-case-pad); }
.flow > .takeaway .section-heading { margin-top: 0; }
/* Inside the panel the blocks are its own children, so the flow's
   spacing rules do not reach them. */
.flow > .takeaway > * + * { margin-top: var(--pg-gap-heading); }

/* --- Air around the notes at the foot of a chapter ---
   The rule divides two kinds of reading, so it keeps room on both sides,
   but the notes themselves sit close together underneath it. */
.sheet .footnotes { gap: 20px; }
.sheet .footnotes__rule { margin-top: 0; }
/* One column, set small and tight: the notes are apparatus, and at this
   size a whole chapter's worth still sits under the text it belongs to. */
.sheet .footnotes__list {
  display: block;
  columns: auto;
}
/* The hanging indent is set in em so it holds against the marker rather
   than against a fixed number: at 16px a two-digit note ran its numeral
   into its own first word. */
.sheet .footnotes__list > li { padding-left: 1.6em; }
.sheet .footnotes__list > li + li { margin-top: 6px; }
/* The numeral is set with its note rather than with the running text. */
.sheet .footnotes__list li::before { font-size: var(--size-footnote); }

/* --- Between framed blocks ---
   Two containers in a row need more than the paragraph gap, or they
   read as one block with a hairline through it. */
.flow > .panel + .panel,
.flow > .cluster + .cluster,
.flow > .point--boxed + .point--boxed,
.flow > .card + .card,
.flow > .chart-box + .chart-box,
.flow > .boxed-list + .boxed-list { margin-top: var(--pg-gap-frame); }
.flow > .takeaway + *,
.flow > * + .takeaway { margin-top: var(--pg-gap-frame); }


/* Column widths come from the content on a sheet, not from the reader's
   percentages: the same split that balances a 744px measure starves the
   narrow columns at 936px. */
.sheet .dtable,
.sheet .dtable--fixed { table-layout: auto; }
/* A framed table fills its frame - the frame is the table's own border,
   and padding inside it only takes width away from the columns. */
.sheet .chart-box:has(> .dtable),
.sheet .chart-box:has(> table) { padding: 0; }


/* The figure column on a sheet, wide enough that the longest value -
   'Up to 94%' - stays on one line at the sheet's type. */
.sheet .squares--stacked { --stat-basis: 220px; }

/* --- The rule at the foot of a chapter ---
   One measure of air above it and the same below, so the notes read as
   a separate register rather than as the paragraph's last line. */
.flow > .flow-footnotes { margin-top: 24px; }
.sheet .footnotes { gap: 24px; }
.sheet .footnotes__rule { margin-top: 0; }
/* A run of notes long enough to need a second sheet opens that sheet at
   the top margin, like any other block that starts a page. */
.flow > .flow-footnotes:first-child { margin-top: 0; }


/* A page whose only content is a full-page figure lets the article box
   grow, so the picture can take the whole column instead of stopping
   short of the right margin to stay inside the standard height. */
.sheet__article:has(.figure--full) { height: var(--pg-article-full-h); }
.sheet .figure__image--full { max-height: none; }


/* The cells are wider on a sheet, so the marks get a taller band. */
/* The sheet's cells are a quarter wider, so every mark grows by the same
   factor and the set keeps its balance. */
.sheet .logo-cell { min-height: 162px; padding: 32px 26px; }
/* The sheet's cells are a quarter wider, so the same trim applies on top
   of that: 0.75 x 1.26. */
.sheet .logo-cell__mark { --mark-scale: 0.945; }
