/* ============================================================================
   Anduin design system — the official palette, not an approximation.

   Every token here is a value a designer can name, which is the whole point of
   the rename: `--gray8` is a word two people can say and mean the same thing,
   where `--g8` was a number that happened to be close to one.

   CONTRAST, measured against gray0 (white). These are the rules, not advice:

     primary3  2.42:1  brand only — never text, and never white text on it
     primary4  4.57:1  link text, primary button fill (white text passes AA)
     primary5  6.98:1  link hover/active, the Publish action
     gray6     3.20:1  icons and placeholders ONLY
     gray7     5.13:1  secondary text — this is the floor for any text
     gray8     9.59:1  body text        gray9  13.9:1  headings
     success4  3.47:1  fills and dots only     success5  5.47:1  success text
     warning4  2.42:1  fills only              warning5  5.28:1  warning text
     danger4   4.49:1  fills, large text only  danger5   5.87:1  error text
     gray9 on primary3  6.08:1  ← the way to use primary3 as a button fill

   So primary3 is the brand, not the ink: the logo mark, the active tab
   underline, the focus ring, a selected-nav accent. Every piece of blue *text*
   is primary4 or primary5.

   purple is reserved for AI surfaces. It marks exactly one thing here — an
   article Claude drafted that no human has published yet — and it keeps its
   meaning only because it is not used for anything else.

   Ramp positions 2 and 3 of success/warning/danger are not published in the
   spec we were given, so they are absent rather than guessed.
   ============================================================================ */
:root{
    --gray0:#FFFFFF; --gray1:#F9FAFB; --gray2:#EDEFF2; --gray3:#E7E9ED;
    --gray4:#CCD2DB; --gray5:#98A1B2; --gray6:#8790A4; --gray7:#646E82;
    --gray8:#323C4D; --gray9:#1D2939;

    --primary1:#DCF2FA; --primary2:#AADAF9; --primary3:#48AFF0;
    --primary4:#1075DC; --primary5:#0158AE;

    --success1:#CEF2D5; --success4:#239E47; --success5:#1F7931;
    --warning1:#FEEFCB; --warning4:#DE990F; --warning5:#8C6500;
    --danger1:#F8E8E8;  --danger4:#E32D2D;  --danger5:#CB0808;
    --purple50:#F3F3FF; --purple600:#6B44E7; --purple700:#592ED3;

    /* Elevation and press states are gray9 at low alpha — a tint of the ink,
       so nothing here introduces a colour the palette does not contain. */
    --shadow-card:0 2px 10px rgba(29,41,57,.06);
    --shadow-pop:0 6px 22px rgba(29,41,57,.20);
    --shadow-glow:0 4px 14px rgba(72,175,240,.35);
    --scrim:rgba(29,41,57,.12);
    --ring:0 0 0 3px var(--primary1);

    --r:8px; --hair:1px solid var(--gray3);

    /* CHROME HEIGHTS — declared once here, and read by every sticky offset and
       every scroll-margin below. The comment block further down used to end
       "change either height and this number moves with it"; now it actually
       does. The narrow-screen media query re-declares --topbar-h when search
       moves to its own row, and the tab row, the sidebar, the rail and every
       heading anchor follow with no second set of numbers.

       index.html needs the same figure for its scroll-spy and measures the two
       elements instead of reading --chrome-h: the computed value of a custom
       property is its token sequence with var() substituted, so that would hand
       it the string "calc(56px + 45px)" rather than a number. */
    --topbar-h:56px; --tabs-h:45px;
    --chrome-h:calc(var(--topbar-h) + var(--tabs-h));
    --anchor-h:calc(var(--chrome-h) + 16px);   /* chrome + 16px of air */

    /* MOTION — one duration per intent, not one duration for everything.
       Hover/press feedback is fast enough to feel attached to the cursor;
       a disclosure or a colour wash gets the slower step. Both sit inside the
       150-300ms band that reads as responsive rather than instant or laggy. */
    --t-fast:120ms; --t:180ms;
    --ease:cubic-bezier(.2,.6,.3,1);
    /* The stylesheet asks for weights 520, 540, 550, 560, 620 and 640, and only
       a variable face can render them. Until now the font was named here but
       nothing ever loaded it, so every one of those weights fell back to the
       system font's nearest static weight and the type scale was approximate
       everywhere. The <head> of each page now fetches Inter's 400..700 axis.

       'Inter var' leads the stack for a self-hosted install — that is the
       family name the upstream variable woff2 registers itself under. Google
       Fonts serves the same variable file under the plain name, so on a normal
       page it is the second entry that resolves and the first costs one failed
       family lookup. Both are before the system stack on purpose. */
    --sans:'Inter var','Inter',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;
    --mono:ui-monospace,SFMono-Regular,'SF Mono',Menlo,Consolas,monospace;
  }
/* ============================================================================
   TYPE AND SPACE — decided once here so later screens inherit them instead of
   re-inventing them. Sizes are px because the whole app is px; the point of the
   table is that the *set* is closed, not that it is unitless.

   TYPE                    size / line-height / tracking
     h1                    35 / 1.2  / -.022em      article and hero title
     h2                    22 / 1.35 / -.014em
     h3                    17.5 / 1.4
     lede                  18 / 1.55                 gray7
     body                  17 / 1.65                 gray8  ← the measure suits this
     ui                    14 / 1.5                  nav, tabs, buttons, cells
     ui-sm                 13 / 1.5                  counts, hints, captions
     label                 11 / 1.4  / +.07em        uppercase, CMS field labels
     code                  14 / 1.6                  mono

   The reference (Mintlify) runs a 529px measure at 18/28. We were at 720px and
   15px — a wider measure AND smaller type, which is the least readable pair of
   the two. 656px at 17px is ~72 characters: the wide end of the comfortable
   band, kept there rather than at the reference's ~62 because our articles
   carry tables and fenced code that wrap badly in a narrow column.

   MEASURE   content 656   sidebar 256   right rail 264

   SPACE     4  8  12  16  24  32  48  64
             A padding, gap or margin comes off this list. Nothing in between.

   STICKY    --topbar-h (56) + --tabs-h (45) = --chrome-h. Headings need
             scroll-margin-top: --anchor-h (--chrome-h + 16 of air) or a
             fragment jump parks the heading underneath the chrome. Nothing
             below restates those numbers: the sidebar, the rail, the tab row
             and every heading read the tokens, and index.html's scroll-spy
             reads --chrome-h off the computed style. Narrow screens move
             search to its own row and re-declare --topbar-h; everything
             else follows without a second set of breakpoints.
   ============================================================================ */
  *{box-sizing:border-box}
  /* `[hidden]{display:none}` is only a USER-AGENT rule, so any author `display`
     outranks it and a hidden element keeps its box. Global and `!important`
     because an author rule that overrides `hidden` is always a bug. */
  [hidden]{display:none !important}
  html,body{margin:0;padding:0}
  body{font-family:var(--sans);color:var(--gray8);background:var(--gray0);
       -webkit-font-smoothing:antialiased;font-size:17px;line-height:1.65}
  a{color:var(--primary4);text-decoration:none}
  a:hover{color:var(--primary5);text-decoration:underline}
  button{font-family:inherit;font-size:inherit;cursor:pointer}

  /* ---------- chrome ----------
     Three columns, not a flex row with spacers: the search box has to sit in
     the optical centre of the window, and it cannot do that if its position
     depends on how long the brand and the utility cluster happen to be. */
  .topbar{height:var(--topbar-h);border-bottom:var(--hair);display:grid;
          grid-template-columns:1fr minmax(0,440px) 1fr;align-items:center;gap:24px;
          padding:0 24px;position:sticky;top:0;background:var(--gray0);z-index:50}
  .brand{display:flex;align-items:center;gap:12px;text-decoration:none;min-width:0}
  .brand:hover{text-decoration:none}
  /* The real mark, at the palette values it already ships with — its fill IS
     primary3, which is why primary3 is the key colour of the whole interface.
     No dark surface exists in this pass, so the file is referenced rather than
     inlined for currentColor; the -white variants are unused on purpose. */
  .logo{height:21px;width:auto;display:block;flex:0 0 auto}
  .logomark{height:21px;width:auto;display:none;flex:0 0 auto}
  .bsep{width:1px;height:18px;background:var(--gray3);flex:0 0 auto}
  .bname{font-size:14px;font-weight:550;color:var(--gray8);letter-spacing:-.005em;
         white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .topright{display:flex;align-items:center;gap:14px;justify-self:end;min-width:0}
  /* One dark pill on the far right, and only one — the moment there are two,
     neither reads as the primary way out of this page. */
  .cta{display:inline-flex;align-items:center;height:32px;padding:0 14px;
       border-radius:16px;background:var(--gray9);color:var(--gray0);font-size:13px;
       font-weight:550;white-space:nowrap}
  .cta:hover{background:var(--gray8);color:var(--gray0);text-decoration:none}
  .cta:focus-visible{outline:2px solid var(--primary3);outline-offset:2px}
  /* Not used by the top bar any more — that is a grid — but .ahead in the CMS
     is still a flex row that pushes its action buttons to the right edge. */
  .spacer{flex:1}
  .seg{display:flex;border:var(--hair);border-radius:var(--r);overflow:hidden}
  /* The CMS's section switcher sits in the centre track, where the portal puts
     search. Declared here rather than as an inline style on the element: an
     inline justify-self outranks the narrow-screen rule that gives this nav its
     own full-width row. */
  .segnav{justify-self:center}
  .seg button{border:0;background:var(--gray0);padding:6px 13px;font-size:13px;color:var(--gray7);font-weight:500}
  .seg button+button{border-left:var(--hair)}
  .seg button.on{background:var(--gray9);color:var(--gray0)}

  .wrap{display:flex;min-height:calc(100vh - var(--topbar-h))}

  /* Search lives in the top bar and nowhere else. It used to be rendered into
     the sidebar by render(), which destroyed and rebuilt the input on every
     keystroke-triggered re-render — hence the focus/selection restoring dance
     that used to follow it. In the top bar it is a persistent element outside
     #app, so it simply keeps focus, and Home gets a search box for free
     instead of needing one built into its hero. */
  .search{position:relative;min-width:0}
  .search input[type=text]{width:100%;height:34px;padding:0 14px 0 34px;border:var(--hair);
                border-radius:17px;font-family:inherit;font-size:14px;
                color:var(--gray9);background:var(--gray1)}
  .search input[type=text]::placeholder{color:var(--gray6)}
  .search input[type=text]:focus{outline:none;border-color:var(--primary3);
                background:var(--gray0);box-shadow:var(--ring)}
  .search .si{position:absolute;left:12px;top:50%;transform:translateY(-50%);
              width:14px;height:14px;color:var(--gray6);pointer-events:none}

  /* ---------- portal sidebar ---------- */
  /* height, not max-height: the border-right is the column divider, and a
     sticky element only as tall as its content leaves that rule stopping in
     mid-page. Sized to the viewport it stays continuous at any scroll offset. */
  .side{width:256px;flex:0 0 256px;border-right:var(--hair);padding:24px 0 64px;
        position:sticky;top:var(--chrome-h);align-self:flex-start;
        height:calc(100vh - var(--chrome-h));overflow-y:auto}
  .cat{margin-bottom:8px;padding:0 12px}
  .cat h4{margin:0 8px 6px;font-size:11px;font-weight:600;letter-spacing:.07em;
          text-transform:uppercase;color:var(--gray7)}
  /* A filled pill, not a left rule: the reference marks the active article by
     filling its row, which reads at a glance in a list of twelve. */
  .cat a{display:block;padding:6px 10px;font-size:14px;color:var(--gray7);
         border-radius:6px;line-height:1.45;margin-bottom:1px;
         transition:background var(--t-fast) var(--ease),color var(--t-fast) var(--ease)}
  .cat a:hover{color:var(--gray9);background:var(--gray2);text-decoration:none}
  .cat a.on{color:var(--primary5);font-weight:550;background:var(--primary1)}
  .cat a:focus-visible{outline:2px solid var(--primary3);outline-offset:-2px}
  /* An inline SVG padlock rather than the 🔒 emoji: the emoji renders in its own
     colours, so it ignored the palette entirely and could not be toned down to
     an icon weight. At gray6 this is non-text content at 3.20:1, which is the
     applicable threshold for an icon. */
  .lock{display:inline-flex;align-items:center;color:var(--gray6);margin-left:6px;
        vertical-align:-1px}
  .lock svg{width:10px;height:11px;display:block}
  /* Sub-categories: one level, so a single indent step is the whole story. */
  /* Top-level categories as a tab row: switching tabs prunes the tree, so the
     sidebar only ever shows one category's worth of articles. */
  /* --tabs-h. With --topbar-h above it this is --chrome-h, which is what every
     sticky offset and every heading's scroll-margin is measured from. */
  .tabs{display:flex;gap:4px;padding:0 24px;height:var(--tabs-h);align-items:stretch;
        border-bottom:var(--hair);overflow-x:auto;overflow-y:hidden;
        position:sticky;top:var(--topbar-h);background:var(--gray0);z-index:40}
  .tab{border:0;background:none;padding:0 12px;font-size:14px;color:var(--gray7);
       font-weight:500;white-space:nowrap;border-bottom:2px solid transparent;
       margin-bottom:-1px;text-decoration:none;display:inline-flex;align-items:center;
       gap:6px;cursor:pointer}
  .tab:hover{color:var(--gray9);text-decoration:none}
  .tab.on{color:var(--gray9);border-bottom-color:var(--primary3);font-weight:600}
  /* Counts are numerals read against each other down a row or a column, so
     they get the tabular figures Inter ships — proportional digits make a 1
     narrower than a 5 and the row jitters as tabs re-render. */
  .tab .tabn{font-size:12px;color:var(--gray7);font-weight:500;
             font-variant-numeric:tabular-nums}
  .tab:focus-visible{outline:2px solid var(--primary3);outline-offset:-2px}

  /* Sub-category group headers, which is what the reference's sidebar is made
     of end to end: an uppercase label per group, its articles beneath. */
  .subcat{margin-top:14px}
  .subcat > summary{margin:0 10px 4px;font-size:11px;font-weight:600;color:var(--gray7);
             letter-spacing:.07em;text-transform:uppercase;
             display:flex;align-items:center;gap:6px;cursor:pointer;list-style:none;
             padding:4px 0}
  .subcat > summary::-webkit-details-marker{display:none}
  /* A caret that rotates, so the state is visible without reading the label. */
  .subcat > summary::before{content:'';width:0;height:0;border:4px solid transparent;
             border-left-color:var(--gray6);transform-origin:2px 50%;
             transition:transform var(--t-fast) var(--ease)}
  .subcat[open] > summary::before{transform:rotate(90deg)}
  .subcat > summary:hover{color:var(--gray9)}
  .subcat > summary:focus-visible{outline:2px solid var(--primary3);outline-offset:2px;border-radius:3px}
  .subcat > summary .subn{margin-left:auto;font-size:11px;color:var(--gray7);
             font-weight:500;letter-spacing:0;font-variant-numeric:tabular-nums}
  .subcat a{padding-left:10px}
  .subs{display:flex;flex-wrap:wrap;gap:5px;margin:0 0 9px}
  .subs .sub{font-size:11px;padding:2px 7px;border-radius:20px;background:var(--gray2);
             color:var(--gray8);font-weight:500}
  .crumbsep{color:var(--gray4);margin:0 2px}

  /* ---------- portal main ---------- */
  .main{flex:1;min-width:0;display:flex;justify-content:center;padding:48px 48px 96px}
  .col{max-width:656px;width:100%}
  /* Home has no sidebar, so its grid gets the width back instead of sitting in
     a 656px column with 256px of empty space beside it. */
  .col.wide{max-width:1100px}
  .wrap.nosidebar .main{padding-top:40px}

  /* ---------- right rail: on this page ----------
     264px, two levels, h3 indented 16px. The active section is gray9 and the
     rest gray7 — the reference marks the current heading dark rather than
     coloured, which keeps the one blue accent on the tab row where it means
     "you are in this category". */
  .toc{width:264px;flex:0 0 264px;padding:56px 0 0 32px;position:sticky;
       top:var(--chrome-h);align-self:flex-start;
       max-height:calc(100vh - var(--chrome-h));overflow-y:auto}
  /* The rail carries no divider, so it stays content-height — a full-height
     sticky box here would just trap scroll events over empty space. */
  .toc h5{margin:0 0 12px;font-size:11px;font-weight:600;letter-spacing:.07em;
          text-transform:uppercase;color:var(--gray7)}
  .toc a{display:block;font-size:13px;color:var(--gray7);padding:5px 0;line-height:1.45;
         border-left:2px solid transparent;padding-left:12px;margin-left:-12px}
  .toc a:hover{color:var(--gray9);text-decoration:none}
  .toc a.h3{padding-left:28px;margin-left:-12px}
  .toc a.on{color:var(--gray9);font-weight:550;border-left-color:var(--primary3)}
  .toc a:focus-visible{outline:2px solid var(--primary3);outline-offset:2px;border-radius:3px}

  .crumb{font-size:13px;color:var(--gray7);margin-bottom:16px}
  .badges{display:flex;gap:8px;align-items:center;margin-bottom:16px;flex-wrap:wrap}
  .pill{font-size:11.5px;padding:3px 9px;border-radius:20px;font-weight:550;
        letter-spacing:.01em;white-space:nowrap;display:inline-block}
  /* ACCESS PILLS, inverted E3. The amber now lands on "Public", which is the
     statement that deserves a second look before publishing — this is visible
     to the whole internet — rather than on "Customers only", which is the
     ordinary case and reads wrong as a warning.

     .neutral and .warn are the same two treatments named for what they look
     like, because .pub and .gate were being borrowed by badges that are not
     about access at all: an article count wearing .pub, an unplaced-image
     warning wearing .gate. Inverting the access pair without splitting those
     off would have turned the count orange and the warning grey.

     Contrast is measured and one pair is narrow: warning5 on warning1 is
     4.63:1 against a 4.5 threshold at 11.5px. Do not darken warning1 or
     lighten warning5 without re-measuring. gray8 on gray2 is 9.65:1. */
  .pill.pub,.pill.warn{background:var(--warning1);color:var(--warning5)}
  .pill.gate,.pill.neutral{background:var(--gray2);color:var(--gray8)}
  .pill.tag{background:var(--primary1);color:var(--primary5)}

  article h1{font-size:35px;line-height:1.2;letter-spacing:-.022em;color:var(--gray9);
             margin:0 0 16px;font-weight:640}
  .lede{font-size:18px;color:var(--gray7);margin:0 0 32px;line-height:1.55}
  article h2{font-size:22px;line-height:1.35;letter-spacing:-.014em;color:var(--gray9);
             margin:48px 0 16px;font-weight:620}
  article h3{font-size:17.5px;line-height:1.4;color:var(--gray9);margin:32px 0 12px;
             font-weight:600}
  /* D3 made headings real fragment targets. The topbar (56px) and the category
     tab row (45px) are both sticky, so a heading scrolled to y=0 sits
     underneath them. scroll-margin-top fixes it for the browser's native jump
     and for our own scrollIntoView alike — no JS offset maths.
     E1 changed both sticky heights and re-measured the number by hand; E2
     replaced it with --anchor-h so it cannot drift again. The sidebar and the
     rail offset from --chrome-h, and this is that plus 16px of air. */
  article h2,article h3,article h4{scroll-margin-top:var(--anchor-h)}
  article p{margin:0 0 20px}
  article ul,article ol{margin:0 0 20px;padding-left:24px}
  article li{margin-bottom:8px}
  article code{font-family:var(--mono);font-size:14px;background:var(--gray2);
               padding:2px 6px;border-radius:4px;color:var(--gray9)}
  article pre{background:var(--gray9);color:var(--gray2);padding:16px;border-radius:var(--r);
              overflow-x:auto;margin:0 0 24px;font-size:14px;line-height:1.6}
  article pre code{background:none;color:inherit;padding:0;font-size:inherit}
  /* An informational callout: primary1 fill with a primary3 rule. gray8 on
     primary1 is 9.59:1, so the body copy inside is safe. */
  article blockquote{margin:0 0 24px;padding:16px;background:var(--primary1);
                     border-left:3px solid var(--primary3);border-radius:0 var(--r) var(--r) 0;color:var(--gray8)}
  article blockquote p:last-child{margin:0}
  article hr{border:0;border-top:var(--hair);margin:48px 0}
  /* A help center is full of things with no spaces in them — slugs, header
     names, S3 keys, an `x-demo-audience` — and a 656px column is narrower than
     some of them. `anywhere` breaks those without touching ordinary prose the
     way `break-all` would. Applied to the block elements rather than to
     `article` itself so it inherits into inline code and links too. */
  article p,article li,article td,article th,article h1,article h2,article h3,
  .lede,.card p,a.idx > span,.results .hit p{overflow-wrap:anywhere}
  /* Prose tables are not CMS tables. The bare element rules further down style
     the admin grid — uppercase 11px headers, full-bleed width — and an article
     table inherited all of it, including the part where a five-column table is
     wider than the measure and pushes the whole page sideways. md.js wraps
     each one in .tablewrap so the overflow is the wrapper's, not the body's. */
  .tablewrap{overflow-x:auto;margin:0 0 24px;border:var(--hair);
             border-radius:var(--r);-webkit-overflow-scrolling:touch}
  .tablewrap table{font-size:15px;min-width:100%;width:max-content}
  .tablewrap th{padding:10px 14px;background:var(--gray1);font-size:11px;
                white-space:nowrap;border-bottom:var(--hair)}
  .tablewrap td{padding:10px 14px;line-height:1.55;vertical-align:top}
  .tablewrap tr:last-child td{border-bottom:0}
  .tablewrap tr:hover td{background:transparent}   /* row hover is a CMS affordance */
  article img{max-width:100%;height:auto;border:var(--hair);border-radius:var(--r);display:block;margin:0 0 8px}
  .figcap{font-size:13px;color:var(--gray7);margin:0 0 24px}

  .loom{margin:0 0 8px;border:var(--hair);border-radius:var(--r);overflow:hidden;
        background:var(--gray1);position:relative;padding-top:56.25%}
  .loom iframe{position:absolute;inset:0;width:100%;height:100%;border:0}
  .loomph{position:absolute;inset:0;display:grid;place-items:center;text-align:center;gap:8px}
  .loomph .play{width:52px;height:52px;border-radius:50%;background:var(--primary4);
                color:var(--gray0);display:grid;place-items:center;margin:0 auto 10px;
                box-shadow:var(--shadow-glow)}
  .loomph .play svg{margin-left:3px}
  .loomph small{color:var(--gray7);font-size:12px;display:block}
  .loomph b{color:var(--gray8);font-size:13.5px;font-weight:550}

  /* The article footer, where C3's vote lives. "Was this page helpful?" rather
     than "Was this helpful?" — the reference asks about the page, and so should
     we, because the answer is used per article. */
  .helpful{margin-top:64px;padding-top:24px;border-top:var(--hair);display:flex;
           align-items:center;gap:12px;font-size:14px;color:var(--gray7)}
  .helpful button{border:var(--hair);background:var(--gray0);border-radius:8px;
                  padding:7px 16px;font-size:14px;color:var(--gray8);font-weight:500}
  .helpful button{transition:border-color var(--t-fast) var(--ease),
                  color var(--t-fast) var(--ease),background var(--t-fast) var(--ease)}
  .helpful button:hover{border-color:var(--primary3);color:var(--primary5);background:var(--primary1)}
  .helpful button:focus-visible{outline:2px solid var(--primary3);outline-offset:2px}
  .helpful .thanks{color:var(--gray7)}
  .whynot{width:100%}
  /* The global `label` rule below is uppercase — that is the CMS field-label
     convention, and it leaks into every label in the app. This is prose. */
  .whynot label{display:block;font-size:14px;color:var(--gray8);margin-bottom:8px;
    text-transform:none;letter-spacing:normal;font-weight:500}
  .whynot textarea{width:100%;font-family:inherit;font-size:14px;padding:10px 12px;
    border:var(--hair);border-radius:8px;color:var(--gray9);resize:vertical}
  .whynot textarea:focus{outline:none;border-color:var(--primary3);box-shadow:var(--ring)}
  .whynot > div{display:flex;gap:8px;margin-top:8px}
  .whynot button{border:0;background:var(--primary4);color:var(--gray0);border-radius:8px;
    padding:7px 16px;font-size:14px;font-weight:500}
  .whynot button:hover{background:var(--primary5)}
  .whynot button.ghostlink{background:none;color:var(--gray7);border-color:transparent}
  .whynot button.ghostlink:hover{color:var(--gray9)}

  /* ---------- landing ---------- */
  .hero{padding:8px 0 40px}
  .hero h1{font-size:35px;line-height:1.2;letter-spacing:-.024em;margin:0 0 12px;
           color:var(--gray9);font-weight:640}
  .hero p{font-size:18px;color:var(--gray7);margin:0;line-height:1.55}
  .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(268px,1fr));gap:16px}
  .card{border:var(--hair);border-radius:10px;padding:20px;background:var(--gray0);
        transition:border-color var(--t) var(--ease),box-shadow var(--t) var(--ease),
                   transform var(--t) var(--ease)}
  .card:hover{border-color:var(--gray4);box-shadow:var(--shadow-card)}
  .card h3{margin:0 0 6px;font-size:16px;color:var(--gray9);font-weight:600}
  .card p{margin:0 0 12px;font-size:14px;color:var(--gray7);line-height:1.5}
  .card a{display:block;font-size:14px;padding:3px 0;color:var(--gray7)}
  .card a:hover{color:var(--primary5);text-decoration:none}
  /* The whole card is the link now — it opens the category overview. */
  a.card{display:block;text-decoration:none;color:inherit}
  /* 1px of lift, which is the smallest movement that still reads as "this is
     liftable" — and the reason `transform` is in the transition rather than a
     margin or a top: a composited property does not re-layout the grid. */
  a.card:hover{text-decoration:none;border-color:var(--primary3);
               box-shadow:var(--shadow-card);transform:translateY(-1px)}
  a.card:focus-visible{outline:2px solid var(--primary3);outline-offset:2px}
  .card h3 .cardn{font-size:12px;color:var(--gray7);font-weight:500;margin-left:2px;
                  font-variant-numeric:tabular-nums}
  .cardgo{display:block;margin-top:12px;font-size:13px;color:var(--primary4);font-weight:550}
  a.card:hover .cardgo{color:var(--primary5)}

  /* ---------- card icon ----------
     A category card was a heading, a blurb and a link — three lines of text in
     a box, five boxes, all identical. Nothing anchored the eye, so finding
     "Data rooms" meant reading all five. An icon is the cheapest fix: it is
     recognised pre-attentively, so the grid becomes scannable rather than
     readable.

     Outline glyphs on a 24 grid at 1.6 stroke, drawn to match the padlock and
     the search glyph already in the interface rather than pulled from a second
     icon family. Every one is aria-hidden: the category name is right beside
     it, so announcing the icon too would just repeat it.

     primary5 on primary1, not primary3 on white. primary3 is 2.42:1 and the
     floor for a non-text glyph is 3:1 — it is the brand, not the ink, exactly
     as the palette note at the top of this file says. */
  .cardicon{width:36px;height:36px;border-radius:9px;background:var(--primary1);
            color:var(--primary5);display:grid;place-items:center;margin-bottom:14px;
            transition:background var(--t) var(--ease),color var(--t) var(--ease)}
  .cardicon svg{width:20px;height:20px;display:block}
  /* On hover the tint deepens one step. primary2 is still a fill, and primary5
     on it stays above the 3:1 the glyph needs. */
  a.card:hover .cardicon{background:var(--primary2)}

  /* An icon beside a heading or a nav group: same glyph set as the cards, sized
     down and set in gray7 rather than the cards' primary5-on-primary1 tile —
     next to 22px or 35px type a filled tile competes with the words. */
  .withicon{display:flex;align-items:center;gap:10px}
  .h1icon{width:26px;height:26px;flex:0 0 26px;color:var(--gray7)}
  .h2icon{width:20px;height:20px;flex:0 0 20px;color:var(--gray7)}
  .subicon{width:14px;height:14px;flex:0 0 14px;color:var(--gray6)}

  /* ---------- support band ----------
     The escalation path, which a help center without one does not have: the
     reader who did not find their answer in the grid above had nowhere to go
     but the back button. It closes the Home page, which is also why the page
     no longer ends in 300px of blank white below the last card.

     gray1 on gray0 with a hairline — a panel, not a banner. Nothing here
     competes with the one dark pill in the top bar. */
  .support{margin-top:48px;padding:32px;border:var(--hair);border-radius:12px;
           background:var(--gray1)}
  .support h2{margin:0 0 8px;font-size:22px;line-height:1.35;letter-spacing:-.014em;
              color:var(--gray9);font-weight:620}
  .support p{margin:0;font-size:16px;color:var(--gray7);line-height:1.6;max-width:60ch}
  .support p+p{margin-top:12px}
  .support b{color:var(--gray8);font-weight:560}
  .supportacts{display:flex;gap:12px;flex-wrap:wrap;margin-top:20px}
  /* A link styled as the primary button, so it needs the inline-block and the
     no-underline that a <button> gets for free. */
  a.btn.lg,button.btn.lg{display:inline-flex;align-items:center;min-height:40px;
    padding:0 18px;font-size:14px;text-decoration:none}
  a.btn.lg:hover{text-decoration:none;color:var(--gray0)}
  a.btn.lg.ghost:hover{color:var(--gray8)}
  @media(max-width:560px){.support{padding:24px 20px}
    .supportacts{flex-direction:column;align-items:stretch}
    a.btn.lg,button.btn.lg{justify-content:center}}

  /* The skip link's target. Same anchor offset as a heading: focus() scrolls
     the element into view, and without this the top of the content would sit
     under the sticky chrome exactly as an un-offset heading would. */
  .main{scroll-margin-top:var(--anchor-h)}

  /* ---------- people ----------
     The role key sits above the table because "author" and "editor" do not
     explain themselves, and a legend is cheaper than a tooltip on every row. */
  .rolekey{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));
           gap:10px;margin-bottom:20px}
  .rolecard{border:var(--hair);border-radius:8px;padding:12px 14px;background:var(--gray1)}
  .rolecard b{display:block;font-size:13px;color:var(--gray9);font-weight:600;margin-bottom:3px}
  .rolecard span{font-size:12.5px;color:var(--gray7);line-height:1.5}
  .pident{color:var(--gray9);font-weight:560}
  .ptok,.pago{font-size:13px;color:var(--gray7);font-variant-numeric:tabular-nums}
  /* A disabled person stays legible — this is a record, not a deletion — but
     reads as inactive. Opacity would drop the text under 4.5:1, so the row is
     tinted and the ink stays put. */
  tr.personoff td{background:var(--gray1)}
  tr.personoff .pident{color:var(--gray7);text-decoration:line-through}
  td select[data-prole]{width:auto;font-family:inherit;font-size:13px;padding:5px 8px;
    border:var(--hair);border-radius:7px;color:var(--gray8);background:var(--gray0)}
  td select[data-prole]:focus{outline:none;border-color:var(--primary3);box-shadow:var(--ring)}

  /* ---------- activity log ----------
     A dense table read by scanning down one column at a time, so the timestamp
     is tabular and monospace-ish and the action pills are the same width class.
     Reuses .pill rather than inventing badges: the palette already says what
     success, warning and danger mean, and an audit log is exactly where those
     meanings should be consistent with the rest of the CMS. */
  .actat{font-family:var(--mono);font-size:12.5px;color:var(--gray7);white-space:nowrap;
         font-variant-numeric:tabular-nums}
  .acttype{display:block;font-size:11px;letter-spacing:.06em;text-transform:uppercase;
           color:var(--gray7);font-weight:600}
  .actlabel{font-weight:540;color:var(--gray9)}
  .actdetail{font-size:13px;color:var(--gray7);line-height:1.5}
  .actmore{margin-top:16px;display:flex;justify-content:center}
  .afdate{display:inline-flex;align-items:center;gap:6px;font-size:12px;
          text-transform:none;letter-spacing:normal;font-weight:500;color:var(--gray7);
          margin:0}
  .afdate input{width:auto;font-family:inherit;font-size:13px;padding:5px 8px;
          border:var(--hair);border-radius:7px;color:var(--gray8);background:var(--gray0)}
  .afdate input:focus{outline:none;border-color:var(--primary3);box-shadow:var(--ring)}
  /* success/danger variants of .pill. Named for the tone rather than for the
     action, because the same tone serves publish and create.

     okpill is gray9 on success1, not success5 on success1. Measured:
     success5 on success1 is 4.504433:1 — it passes 4.5 by four thousandths,
     which is not a margin, it is a rounding error waiting to become a failure
     the first time either token is nudged. gray9 on success1 is 12.10:1, the
     fill still carries the meaning, and it is the same pattern E1 settled on
     for primary3 ("the brand, not the ink"). danger and warning keep their
     ramp text at 4.95 and 4.63, which have room. */
  .pill.okpill{background:var(--success1);color:var(--gray9)}
  .pill.badpill{background:var(--danger1);color:var(--danger5)}

  /* ---------- modal ----------
     A native <dialog>, which is the whole reason this is short: showModal()
     gives the top layer, the ::backdrop, focus containment and Esc-to-cancel
     without a line of JS for any of them. What replaced prompt()/confirm() is
     therefore mostly markup.

     Sized to the content with a ceiling, and scrollable inside rather than
     growing past the viewport — B5's icon grid is the case that needs it.

     Contrast note: the single destructive action is filled with danger5
     (white on it is 5.87:1), not danger4 — white on danger4 is 4.49:1, which
     the palette table marks as fills-and-large-text-only and this is 14px. */
  .modal{border:0;padding:0;background:transparent;max-width:min(520px,calc(100vw - 32px));
         width:100%;max-height:calc(100vh - 64px)}
  .modal::backdrop{background:rgba(29,41,57,.44)}
  .modalbox{background:var(--gray0);border:var(--hair);border-radius:12px;
            box-shadow:var(--shadow-pop);display:flex;flex-direction:column;
            max-height:calc(100vh - 64px);overflow:hidden}
  .modalhead{padding:24px 24px 0}
  .modalhead h2{margin:0;font-size:18px;line-height:1.35;letter-spacing:-.01em;
                color:var(--gray9);font-weight:620}
  .modalhead p{margin:8px 0 0;font-size:14px;line-height:1.55;color:var(--gray7)}
  /* Chrome makes an overflowing container keyboard-focusable, which is how a
     keyboard user scrolls the icon grid to reach its last row — so it is kept,
     not suppressed, and given a visible ring. Without one it is a tab stop with
     no indication of what has focus. */
  .modalbody{padding:20px 24px 4px;overflow-y:auto;display:flex;flex-direction:column;gap:16px}
  .modalbody:focus-visible{outline:2px solid var(--primary3);outline-offset:-2px;
                           border-radius:4px}
  .modalbody:empty{display:none}
  /* Right-aligned, cancel first in the DOM so Tab reaches it before the
     action that cannot be undone. */
  .modalfoot{padding:20px 24px 24px;display:flex;gap:10px;justify-content:flex-end;
             flex-wrap:wrap}
  .modalfield{display:flex;flex-direction:column}
  /* The field hint sits under the label and above the control; the error
     replaces nothing and appears under the control. Both opt out of the
     uppercase treatment the bare `label` rule applies. */
  .modalfield .hint{margin-top:0;margin-bottom:6px}
  .modalfield .err{margin-top:6px;font-size:13px;line-height:1.45;color:var(--danger5);
                   font-weight:500}
  .modalfield input[aria-invalid="true"],.modalfield textarea[aria-invalid="true"],
  .modalfield select[aria-invalid="true"]{border-color:var(--danger4)}
  .modalfield input[aria-invalid="true"]:focus,.modalfield textarea[aria-invalid="true"]:focus,
  .modalfield select[aria-invalid="true"]:focus{box-shadow:0 0 0 3px var(--danger1)}
  .modalfield textarea{min-height:0;font-family:inherit;font-size:14px;line-height:1.55}
  /* A locked field states a value the form will submit but the editor may not
     change — B5's pre-filled parent. Not a disabled input: a disabled control
     is skipped by the keyboard and reads as broken rather than as settled. */
  .modallocked{padding:9px 11px;border:var(--hair);border-radius:7px;
               background:var(--gray1);color:var(--gray8);font-size:14px}
  .modallocked b{color:var(--gray9);font-weight:560}
  .btn.destructive{background:var(--danger5);color:var(--gray0)}
  .btn.destructive:hover{background:var(--danger5);box-shadow:inset 0 0 0 999px var(--scrim)}
  @media(max-width:560px){
    .modalfoot{flex-direction:column-reverse;align-items:stretch}
    .modalfoot .btn{justify-content:center;text-align:center}
  }

  /* ---------- icon picker ----------
     Real radio inputs, so arrow keys move between tiles and the group announces
     itself as a group — a grid of divs with click handlers gives up both. The
     input is visually hidden rather than display:none, which would take it out
     of the tab order and out of the accessibility tree with it. */
  .iconpick{display:grid;grid-template-columns:repeat(auto-fill,minmax(76px,1fr));gap:8px}
  .iconopt{position:relative;text-transform:none;letter-spacing:normal;
           font-weight:400;margin:0}
  .iconopt input{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none}
  .iconglyph{display:flex;flex-direction:column;align-items:center;gap:6px;
             padding:10px 6px;border:var(--hair);border-radius:8px;cursor:pointer;
             background:var(--gray0);
             transition:border-color var(--t-fast) var(--ease),
                        background var(--t-fast) var(--ease)}
  .iconglyph svg{width:22px;height:22px;color:var(--gray7)}
  .iconglyph span{font-size:11px;line-height:1.3;text-align:center;color:var(--gray7)}
  .iconopt:hover .iconglyph{border-color:var(--gray4);background:var(--gray1)}
  .iconopt input:checked + .iconglyph{border-color:var(--primary3);
             background:var(--primary1);box-shadow:inset 0 0 0 1px var(--primary3)}
  .iconopt input:checked + .iconglyph svg{color:var(--primary5)}
  .iconopt input:checked + .iconglyph span{color:var(--primary5);font-weight:550}
  .iconopt input:focus-visible + .iconglyph{outline:2px solid var(--primary3);
             outline-offset:2px}
  /* The icon shown in the CMS category table, so the column reads as a set. */
  .caticon{display:inline-flex;vertical-align:-3px;margin-right:8px;color:var(--gray7)}
  .caticon svg{width:16px;height:16px}
  @media(pointer:coarse){.iconglyph{padding:14px 6px}}

  /* ---------- skip link ----------
     The chrome above the content is a brand, a search box, a select, a CTA and
     up to six category tabs — a dozen tab stops before the first word of the
     article, on every single page. Visible only on focus, which is the one
     time it is useful. */
  .skip{position:absolute;left:16px;top:8px;z-index:100;padding:10px 16px;
        border-radius:8px;background:var(--gray9);color:var(--gray0);font-size:14px;
        font-weight:550;transform:translateY(-200%)}
  .skip:focus{transform:translateY(0);color:var(--gray0);text-decoration:none;
              outline:2px solid var(--primary3);outline-offset:2px}

  /* Category overview index: title plus lede, so the page is worth reading
     rather than a bare list of links. */
  .idxlist{display:flex;flex-direction:column;margin:0 0 32px}
  a.idx{display:block;padding:14px 0;border-bottom:var(--hair);text-decoration:none}
  a.idx:first-child{border-top:var(--hair)}
  a.idx:hover{text-decoration:none}
  a.idx:focus-visible{outline:2px solid var(--primary3);outline-offset:2px;border-radius:4px}
  a.idx b{display:block;font-size:16px;font-weight:560;color:var(--gray9);line-height:1.4}
  a.idx:hover b{color:var(--primary5)}
  /* Direct child only: the lock badge is a span *inside* the <b>, and a bare
     `a.idx span` rule turned it into a block on its own line. */
  a.idx > span{display:block;font-size:14px;color:var(--gray7);margin-top:3px;line-height:1.5}
  .crumb a{color:var(--gray7)}
  .crumb a:hover{color:var(--primary5)}

  .results .hit{border-bottom:var(--hair);padding:18px 0}
  .results .hit h3{margin:0 0 5px;font-size:16px;color:var(--gray9);font-weight:600}
  .results .hit p{margin:0;font-size:14px;color:var(--gray7);line-height:1.5}
  .results mark{background:var(--warning1);color:var(--gray9);border-radius:2px}
  /* Shared by the portal and the CMS. This rule is the PORTAL's — left exactly
     as it was, because the reader-facing "Nothing matched" search state must not
     change. The CMS box lives in `.admin .empty` further down, so an edit to the
     box cannot reach the portal and an edit here is a deliberate portal change. */
  .empty{padding:40px 0;color:var(--gray7);font-size:15px;line-height:1.6}
  .empty p{margin:0 0 16px;max-width:56ch}
  .empty b{color:var(--gray8);font-weight:560}
  /* The sections, as a row of chips rather than a bulleted list: it is a set of
     peers to pick from, not a sequence to read. */
  .emptycats{display:flex;flex-wrap:wrap;gap:8px}
  .emptycat{display:inline-flex;align-items:center;min-height:36px;padding:0 14px;
            border:var(--hair);border-radius:18px;font-size:14px;font-weight:500;
            color:var(--gray8);background:var(--gray0);
            transition:border-color var(--t-fast) var(--ease),
                       background var(--t-fast) var(--ease),color var(--t-fast) var(--ease)}
  .emptycat:hover{border-color:var(--primary3);background:var(--primary1);
                  color:var(--primary5);text-decoration:none}
  .emptycat:focus-visible{outline:2px solid var(--primary3);outline-offset:2px}

  /* ---------- admin ---------- */
  .admin{flex:1;padding:32px 40px 80px;min-width:0;font-size:15px}
  /* Empty states in the CMS read as a container — E7's other half. Scoped to
     `.admin` rather than given its own class so a NEW empty state in the CMS
     gets the box automatically; a separate class would be one someone forgets.

     gray8 on gray1 is 10.64:1. The trap here, and it is a real one: keeping the
     inherited gray7 and just adding a fill gives 4.91 on gray1 and **4.45 on
     gray2, which fails** — the identical pair Correction 10 records E1 hitting
     on the public pill and the sub-category chips. The colour is therefore
     restated explicitly rather than inherited. */
  .admin .empty{background:var(--gray1);border:var(--hair);border-radius:var(--r);
                padding:20px 22px;color:var(--gray8);font-size:14.5px}
  .admin .empty b{color:var(--gray9)}
  .admin .empty p:last-child{margin-bottom:0}
  .ahead{display:flex;align-items:center;gap:14px;margin-bottom:24px}
  .ahead h2{margin:0;font-size:22px;color:var(--gray9);letter-spacing:-.015em;font-weight:620}
  /* A genuine subsection: h3 because it sits under the h2 above it, sized here
     rather than with an inline style on the tag. E6 found this as an <h2> shrunk
     to 16px, which is a heading level chosen for its appearance. */
  .ahead.subhead h3{margin:0;font-size:16px;color:var(--gray9);letter-spacing:-.01em;
                    font-weight:620}
  .btn{border:0;background:var(--primary4);color:var(--gray0);padding:8px 15px;border-radius:7px;
       font-size:13.5px;font-weight:520;
       transition:background var(--t-fast) var(--ease),border-color var(--t-fast) var(--ease),
                  color var(--t-fast) var(--ease)}
  .btn:hover{background:var(--primary5)}
  .btn.ghost{background:var(--gray0);color:var(--gray8);border:var(--hair)}
  .btn.ghost:hover{background:var(--gray1);border-color:var(--gray4)}
  /* Publish is the one action that ends in something being visible to the world,
     so it gets the strongest blue in the ramp. There is nothing darker than
     primary5, so the hover is a gray9 scrim over it rather than another token. */
  .btn.publish{background:var(--primary5)}
  .btn.publish:hover{box-shadow:inset 0 0 0 999px var(--scrim)}
  /* Destructive actions are danger, never a ghost button that looks like Cancel. */
  .btn.danger{background:var(--gray0);color:var(--danger5);border:1px solid var(--gray3)}
  .btn.danger:hover{background:var(--danger1);border-color:var(--danger4);color:var(--danger5)}
  .btn.sm{padding:5px 11px;font-size:13px}
  .btn:focus-visible,.cta:focus-visible{outline:2px solid var(--primary3);outline-offset:2px}

  /* Every CMS table is wrapped in one of these. The articles grid carries seven
     columns — title, category, access, tags, status, updated, actions — and at
     `width:100%` inside a fixed-padding pane it cannot shrink below the sum of
     their minimums, so below about 1060px it pushed the whole document sideways
     and took the top bar's centred nav with it. Now the overflow is the
     wrapper's: the table scrolls, the page does not.

     min-width:100% rather than width:100% on the table, so it still fills the
     pane when it fits and only grows past it when it has to. */
  .adminwrap{overflow-x:auto;-webkit-overflow-scrolling:touch;max-width:100%}
  .adminwrap table{min-width:100%;width:auto}
  table{width:100%;border-collapse:collapse;font-size:14px}
  th{text-align:left;font-size:11px;letter-spacing:.07em;text-transform:uppercase;
     color:var(--gray7);font-weight:600;padding:0 12px 9px;border-bottom:var(--hair)}
  td{padding:12px;border-bottom:var(--hair);color:var(--gray8);vertical-align:middle}
  /* Dates and counts down a CMS column, same reason as the tab counts. */
  td,.stat b{font-variant-numeric:tabular-nums}
  tr:hover td{background:var(--gray1)}
  td .ttl{color:var(--gray9);font-weight:540}
  /* Status is a colour with a meaning: success for live, warning for a draft.
     Never the two shades of gray this used to be. */
  .dot{display:inline-block;width:7px;height:7px;border-radius:50%;margin-right:7px}
  /* purple is the AI reservation, and it marks exactly one thing: an article
     Claude wrote that no human has published yet. Used for anything else it
     stops meaning that, which is the only reason it is worth having. */
  .pill.ai{background:var(--purple50);color:var(--purple700);font-weight:600}
  .dot.live{background:var(--success4)}.dot.draft{background:var(--warning4)}

  .editor{display:grid;grid-template-columns:1fr 1fr;gap:26px;align-items:start}
  .fields{display:flex;flex-direction:column;gap:15px}
  /* CMS field labels. NOTE: this is a bare element selector, so it uppercases
     every <label> in the app — including .toggle and .whynot, both of which
     reset it above. Anything new that is prose in a <label> must do the same. */
  label{display:block;font-size:11px;font-weight:600;letter-spacing:.07em;
        text-transform:uppercase;color:var(--gray7);margin-bottom:6px}
  input[type=text],select,textarea{width:100%;padding:8px 11px;border:var(--hair);
        border-radius:7px;font-family:inherit;font-size:14px;color:var(--gray9);background:var(--gray0)}
  input[type=text]:focus,select:focus,textarea:focus{outline:none;border-color:var(--primary3);
        box-shadow:var(--ring)}
  textarea{font-family:var(--mono);font-size:13px;line-height:1.65;resize:vertical;min-height:400px}
  .row2{display:grid;grid-template-columns:1fr 1fr;gap:12px}
  .toolbar{display:flex;gap:6px;margin-bottom:7px;flex-wrap:wrap}
  .hint{font-size:13px;color:var(--gray7);margin-top:5px;line-height:1.45;
        text-transform:none;letter-spacing:normal;font-weight:400}
  /* .toggle is itself a <label>, so it inherited the uppercase field-label
     treatment and rendered its whole explanatory sentence in caps. It is a
     control with prose attached, not a field label. */
  .toggle{display:flex;align-items:flex-start;gap:10px;padding:12px;border:var(--hair);
          border-radius:8px;background:var(--gray1);text-transform:none;
          letter-spacing:normal;font-weight:400;font-size:14px}
  .toggle input{width:16px;height:16px;accent-color:var(--primary4);margin:2px 0 0}
  .toggle div{flex:1}
  .toggle b{display:block;font-size:14px;color:var(--gray9);font-weight:550;margin-bottom:2px}
  .toggle small{font-size:13px;color:var(--gray7);line-height:1.5;display:block}
  .prevbox{border:var(--hair);border-radius:10px;overflow:hidden;position:sticky;top:76px}
  /* The preview bar now carries the Preview-as control, so it is a row rather
     than a caption. The label keeps the uppercase treatment; the buttons do not. */
  .prevbar{padding:8px 10px 8px 14px;border-bottom:var(--hair);background:var(--gray1);
           display:flex;align-items:center;gap:10px;flex-wrap:wrap}
  .prevbar > span{font-size:11px;letter-spacing:.07em;text-transform:uppercase;
           color:var(--gray7);font-weight:600}
  .prevbar .seg{margin-left:auto;background:var(--gray0)}
  .seg.sm button{padding:4px 10px;font-size:12.5px}
  /* The verdict panel above a previewed article. `ok` and `bad` are statements
     about visibility, not about correctness — a gated article is often exactly
     what the editor intended, so `bad` is warning, never danger. */
  .pvnote{padding:10px 12px;border-radius:8px;background:var(--gray1);
          border:var(--hair);font-size:13.5px;line-height:1.55;color:var(--gray8);
          margin-bottom:16px}
  .pvnote b{color:var(--gray9);font-weight:600}
  .pvnote.ok{background:var(--success1);border-color:var(--success4)}
  .pvnote.ok b{color:var(--success5)}
  .pvnote.bad{background:var(--warning1);border-color:var(--warning4)}
  .pvnote.bad b{color:var(--warning5)}
  .prevbody{padding:26px 28px;max-height:74vh;overflow-y:auto}
  .prevbody h1{font-size:25px}
  /* Section explainers. Plain grey text, no fill, no border — E7.

     The reasoning, because it is the kind of thing that gets "improved" back:
     an explainer is read once and is noise forever after, while an empty state
     is a status the reader needs to notice every time it appears. These two had
     it backwards — the permanent thing shouted in a blue panel and the momentary
     thing whispered in grey. They have swapped.

     gray7 on white is 5.13:1. The bold lead-in stays gray9 and is what keeps a
     dozen of these scannable rather than a dozen grey blurs. */
  .note{font-size:13px;color:var(--gray7);margin-bottom:22px;line-height:1.55;
        max-width:88ch}
  .note b{color:var(--gray9)}
  @media(max-width:1240px){.toc{display:none}.editor{grid-template-columns:1fr}
    .prevbox{position:static}}
  /* Below this the three-column top bar cannot hold a centred 440px search box
     and a brand and a CTA, so the grid collapses to brand / search / CTA with
     the search taking what is left. */
  @media(max-width:1000px){.topbar{grid-template-columns:auto minmax(0,1fr) auto;gap:16px}
    .bsep,.bname{display:none}}
  @media(max-width:860px){.side{display:none}.main{padding:28px 22px 70px}
    .grid{grid-template-columns:1fr}.logo{display:none}.logomark{display:block}}

  /* 404 has a brand and nothing else — no search, no tabs, no CTA — so its top
     bar opts out of the three-column grid rather than carrying two empty
     tracks. A class, not an inline style: an inline grid-template-columns
     outranks the narrow-screen rule below and stranded the layout at 375px. */
  .topbar.bare{grid-template-columns:auto 1fr}

  /* ---------- narrow: search gets its own row ----------
     One row could not hold four things. Measured at 375px, the collapsed
     `auto minmax(0,1fr) auto` grid gave the search input 50px of width and
     then let the un-shrinkable "Viewing as" select overlap it by 31px — the
     box was both unusable and covered. Squeezing it further was not an
     option: search *is* the navigation on a phone, where the category sidebar
     is display:none.

     So the top bar becomes two rows — brand and utilities above, search full
     width beneath — and re-declares --topbar-h to match. Because every sticky
     offset reads the token, the tab row, the rail and every heading anchor
     move down by themselves; there is no second set of magic numbers here. */
  @media(max-width:760px){
    :root{--topbar-h:104px}
    .topbar{grid-template-columns:auto minmax(0,1fr);grid-template-rows:56px 48px;
      column-gap:12px;row-gap:0;padding:0 16px}
    /* 404's bar has no search to rehouse, so it keeps one 56px row and opts
       out of the taller --topbar-h. It has no tab row either, so nothing
       downstream is offsetting from it. */
    .topbar.bare{grid-template-columns:auto 1fr;grid-template-rows:56px;height:56px}
    .brand{grid-area:1/1/2/2}
    .topright{grid-area:1/2/2/3;gap:10px}
    /* Full bleed across both columns, and the row's own 48px centres it.
       Scoped to `.topbar .search`, not to `.search`: 404 renders a standalone
       search form in the page body, and the unscoped rule shifted its icon
       5px off centre to compensate for a padding-bottom that form has not got. */
    .topbar .search{grid-area:2/1/3/3;align-self:center;padding-bottom:10px}
    .topbar .search input[type=text]{height:38px}
    .topbar .search .si{top:calc(50% - 5px)}
    .tabs{padding:0 16px}
    /* The CMS top bar has a segmented nav in the centre track where the portal
       has search, and four buttons do not fit beside a brand and a CTA. Same
       treatment as search: it drops to its own row, and scrolls inside that row
       if four buttons still do not fit. */
    .topbar.cms .segnav{grid-area:2/1/3/3;justify-self:stretch;align-self:center;
                        overflow-x:auto;margin-bottom:10px}
    .admin{padding:24px 16px 64px}
  }

  /* ---------- touch ----------
     Pointer, not width: a 1024px tablet is touch and a 700px window on a
     laptop is not. The sidebar is hidden at this size, so what is left to
     grow is the tab row, the rail, the vote buttons and the CMS row actions —
     all of which sat between 30 and 36px. 44px is the iOS figure and the one
     that clears WCAG's target-size rule with room to spare. */
  @media(pointer:coarse){
    .tab{padding:0 14px}
    .cat a,.toc a{min-height:44px;display:flex;align-items:center}
    .helpful button,.whynot button,.btn{min-height:44px}
    .btn.sm{min-height:38px}
    .cta{height:38px}
    /* 8px between adjacent targets, so a thumb cannot hit two at once. */
    .helpful{gap:12px;flex-wrap:wrap}
    .attacts,.toolbar{gap:8px}
  }

  /* ---------- motion preferences ----------
     Every transition in this file is feedback — a border warming on hover, a
     caret turning, a card lifting. None of it carries information the layout
     does not already state, so under `reduce` it is all safe to drop outright
     rather than shortened.

     Deliberately NOT `html{scroll-behavior:smooth}`, which is the usual advice
     and is wrong for this app. index.html owns scrolling on purpose — it sets
     scrollRestoration to manual and calls scrollTo(0,0) on every navigation —
     and a global smooth behaviour catches those too, so following a sidebar
     link would glide the old page up to the top instead of the new page simply
     starting at it. The one place smoothness would be welcome, a click in the
     right rail, is not worth animating page transitions to get. The reduce
     override stays anyway, so an author who adds a smooth scroll later inherits
     the opt-out rather than having to remember it. */
  @media(prefers-reduced-motion:reduce){
    html{scroll-behavior:auto}
    *,*::before,*::after{animation-duration:.01ms !important;
      animation-iteration-count:1 !important;transition-duration:.01ms !important;
      scroll-behavior:auto !important}
    a.card:hover{transform:none}
  }
