/* ==========================================================================
   HOUSE STYLE — site.css
   The LAN site's furniture: top bar, sidebar, search, landing page.

   ⚠ THIS FILE IS NEVER LOADED BY THE PDF, AND THAT IS THE POINT.

   `_pdf()` in build.py inlines exactly base.css + arabic.css + print.css, in
   that order. Everything in here — the whole navigation shell — is therefore
   structurally incapable of reaching the printed manual. Chrome CSS put into
   base.css would be parsed by WeasyPrint on every build and would sit one
   `body` class away from changing a page that is already correct.

   The same rule as base.css applies throughout: logical properties for
   anything that mirrors (margin, padding, border, inset, text-align),
   physical only for size. The sidebar lands on the right in Arabic because
   the grid follows `dir`, not because anything here says "right".
   ========================================================================== */

:root {
  --topbar-h:     3.5rem;
  --sidebar-w:    18rem;

  /* ⚠ THE CONTENT COLUMN IS A FIXED WIDTH, DELIBERATELY, AND IT IS LOAD-BEARING.

     A screenshot's legibility is arithmetic: the image column comes out of
     this number, and `docs shots` uses it to decide which shots are readable
     inline and which have to be re-cropped. A content column that grows with
     the window would make that a range instead of a number, and "this shot is
     unreadable" would depend on the reader's monitor.

     CHANGING THIS INVALIDATES THE SHOT AUDIT. `INLINE_COLUMN_CSS_PX` in
     factory/legibility.py is derived from it and carries the same warning —
     change one, recompute the other, and re-run `docs shots`. */
  --content-max:  74rem;
  --shell-inner:  calc(var(--sidebar-w) + var(--content-max));
}

/* --------------------------------------------------------------------------
   THE SHELL

   `body` itself is the grid — there is no wrapper element. That is not
   golfing: the PDF builder slices the unit template on the literal string
   `<main class="page">` and reassembles the pieces, so every wrapper opened
   before `<main>` and closed after it would have to survive that slice. With
   no wrapper there is nothing to survive.
   -------------------------------------------------------------------------- */

/* FOUR COLUMNS, AND THE OUTER TWO HOLD NOTHING.

   The reading area is a fixed width and has to sit in the middle of the
   window. Capping the whole grid instead — `max-width` on the body — centres
   the reading area but takes the top bar with it, and a bar that stops short
   of both edges makes the page read as a box floating in an empty room. That
   is the "unfinished" the right-hand void was.

   So the rails are grid columns that absorb whatever the window has spare.
   The bar spans all four and reaches both edges; the sidebar and the content
   sit in the middle two and are centred by construction. No wrapper element,
   which matters because anything opened before `<main class="page">` and
   closed after it has to survive the PDF builder's slice. */
body.has-chrome {
  display: grid;
  grid-template-columns:
    minmax(0, 1fr)                        /* rail */
    var(--sidebar-w)
    minmax(0, var(--content-max))
    minmax(0, 1fr);                       /* rail */
  /* Three rows: bar, content, footer. The content row takes `1fr`, so on a
     page with little on it the footer is pushed to the bottom of the window
     instead of stopping halfway down with nothing under it. */
  grid-template-rows: auto 1fr auto;
  min-height: 100vh;
}

/* A page with no sidebar must not reserve the sidebar's column — the notice
   the Arabic edition serves was sitting 18rem right of centre because the
   track was still there, empty. Both variables, because `--shell-inner` is
   resolved where it is declared and does not see an override made here. */
body.is-bare { --sidebar-w: 0rem; --shell-inner: var(--content-max); }

body.has-chrome > .topbar  { grid-column: 1 / -1; }
body.has-chrome > .sidebar { grid-column: 2; }

/* The content fills its track exactly, so there is no leftover slack between
   the sidebar and the text — the gap the reader sees is this padding and
   nothing else. base.css's 62rem cap is for the PDF and is overridden here.  */
body.has-chrome > .page {
  grid-column: 3;
  max-width: none;
  margin-inline: 0;
  padding-inline: 3rem;
}

/* The page's bottom edge. Carries the one thing a reader might want that the
   site cannot give them — the printable copy — so it earns its space rather
   than merely filling it. */
body.has-chrome > .pagefoot {
  grid-column: 3;
  grid-row: 3;
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem 1.5rem;
  align-items: baseline;
  justify-content: space-between;
  margin-inline: 3rem;
  padding-block: 1.25rem 2.5rem;
  border-block-start: 1px solid var(--rule);
  color: var(--ink-faint);
  font-size: 0.85rem;
}
.pagefoot a { color: var(--ink-soft); text-decoration: none; }
.pagefoot a:hover { color: var(--accent); text-decoration: underline; }
.pagefoot__top::before { content: "↑ "; }

/* Absolutely positioned, so neither becomes a grid item and neither opens a
   row of its own. */
.skip, .skip-target { position: absolute; }

/* ⚠ HIDDEN BY ITS OWN HEIGHT, NOT BY A MEASUREMENT OF IT.

   This was `inset-block-start: -3rem`, which is 48px, chosen because the
   English link is 48px tall. The Arabic link is 56px tall — `html[lang="ar"]`
   sets a larger font-size and a 1.85 line-height — so it was never fully
   parked, and five pixels of accent blue sat at the top of EVERY page of the
   Arabic edition, at every width, from the day the second language shipped.

   `translateY(-100%)` is that measurement taken by the browser instead of by
   us, and it cannot go stale: it means "your own height, whatever that is
   today". The extra 1rem clears the 0.5rem it would otherwise sit at. Nothing
   here needs revisiting if the type changes again. */
.skip {
  inset-inline-start: 0.5rem;
  inset-block-start: 0.5rem;
  transform: translateY(calc(-100% - 1rem));
  z-index: 60;
  background: var(--accent);
  color: #fff;
  padding: 0.6rem 1rem;
  border-radius: var(--radius);
  text-decoration: none;
  transition: transform 0.12s;
}
.skip:focus { transform: none; }

/* --------------------------------------------------------------------------
   TOP BAR
   -------------------------------------------------------------------------- */

.topbar {
  position: sticky;
  inset-block-start: 0;
  z-index: 40;
  display: flex;
  align-items: center;
  gap: 1rem;
  height: var(--topbar-h);
  /* Full-bleed background and border, but the CONTENTS line up with the
     sidebar and the text below. The padding is the rail width, recomputed
     from the same two variables the grid uses, so the bar cannot drift out of
     alignment with the columns it sits above. */
  padding-inline: max(1.25rem, calc((100% - var(--shell-inner)) / 2));
  background: color-mix(in srgb, var(--ground) 88%, transparent);
  backdrop-filter: blur(10px);
  border-block-end: 1px solid var(--rule);
}

.topbar__home {
  font-weight: 650;
  color: var(--ink);
  text-decoration: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}


/* The sidebar disclosure button. Hidden on desktop, where the sidebar is
   simply present. */
.topbar__toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 2.2rem;
  height: 2.2rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--ground);
  color: var(--ink);
  cursor: pointer;
}

/* --------------------------------------------------------------------------
   SEARCH
   -------------------------------------------------------------------------- */

/* The bar is now title | search | language | theme. `margin-inline-start:
   auto` on the search pushes everything after it to the far end, which is
   what `.topbar__views` used to do before it moved to the sidebar. */
.search { position: relative; flex: 0 1 22rem; margin-inline-start: auto; }

.search__field {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  background: var(--ground-sunk);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding-inline: 0.6rem;
}
.search__field:focus-within { border-color: var(--accent); background: var(--ground); }

.search__icon { flex: none; color: var(--ink-faint); display: flex; }

.search input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: none;
  color: inherit;
  font: inherit;
  font-size: 0.92rem;
  padding-block: 0.5rem;
  outline: none;
}
/* Safari draws its own clear button on type=search and it fights the layout. */
.search input::-webkit-search-decoration,
.search input::-webkit-search-cancel-button { -webkit-appearance: none; }

.search__key {
  flex: none;
  font-size: 0.7rem;
  color: var(--ink-faint);
  border: 1px solid var(--rule);
  border-radius: 4px;
  padding: 0.05em 0.35em;
  background: var(--ground);
}
.search__field:focus-within .search__key { display: none; }

.search__results {
  position: absolute;
  inset-inline: 0;
  inset-block-start: calc(100% + 0.4rem);
  max-height: min(28rem, 70vh);
  overflow-y: auto;
  scrollbar-width: thin;
  background: var(--ground);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  box-shadow: 0 8px 28px rgb(0 0 0 / 0.16);
  z-index: 50;
  padding: 0.3rem;
}
.search__results[hidden] { display: none; }

.search__hit {
  display: block;
  padding: 0.55rem 0.7rem;
  border-radius: 4px;
  text-decoration: none;
  color: inherit;
}
.search__hit:hover,
.search__hit.is-active { background: var(--accent-soft); }

.search__unit { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--ink-faint); }
.search__head { font-weight: 650; font-size: 0.95rem; margin-block: 0.1rem; }
.search__snip { font-size: 0.85rem; color: var(--ink-soft); }
.search__snip mark, .search__head mark {
  background: color-mix(in srgb, var(--accent) 22%, transparent);
  color: inherit;
  border-radius: 2px;
  padding: 0 0.1em;
}

.search__empty { padding: 1rem 0.7rem; color: var(--ink-faint); font-size: 0.9rem; }
.search__count {
  padding: 0.35rem 0.7rem 0.5rem;
  font-size: 0.75rem;
  color: var(--ink-faint);
  border-block-end: 1px solid var(--rule);
  margin-block-end: 0.3rem;
}

/* A match the reader was sent to, marked in the page itself. */
mark.hit {
  background: color-mix(in srgb, var(--accent) 25%, transparent);
  color: inherit;
  border-radius: 2px;
}

@keyframes flash {
  from { background: var(--accent-soft); }
  to   { background: transparent; }
}
.step.is-target, .step__prose.is-target { animation: flash 1.8s ease-out; border-radius: var(--radius); }

/* --------------------------------------------------------------------------
   LANGUAGE SWITCHER AND THEME BUTTON
   -------------------------------------------------------------------------- */

.langs { display: flex; align-items: center; gap: 0.15rem; flex: none; }

.langs a {
  padding: 0.3rem 0.55rem;
  border-radius: 4px;
  font-size: 0.85rem;
  color: var(--ink-faint);
  text-decoration: none;
  white-space: nowrap;
}
.langs a:hover { background: var(--ground-sunk); color: var(--ink); }
.langs a[aria-current] { color: var(--ink); font-weight: 650; }

/* An edition that does not exist yet is still listed, and still reachable —
   it leads to a page that says so. Dimming it sets the expectation before the
   click rather than after it. */
.langs__unwritten { opacity: 0.55; text-decoration: underline dotted; text-underline-offset: 3px; }

.theme {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.1rem;
  height: 2.1rem;
  padding: 0;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--ground);
  color: var(--ink-soft);
  cursor: pointer;
}
.theme:hover { color: var(--ink); border-color: var(--accent); }

/* One icon at a time. Unstamped — before viewer.js has run, or with no
   JavaScript at all — the page IS following the system, so `auto` is what an
   unstamped button should show. */
.theme svg { display: none; }
.theme .theme__auto,
.theme[data-state="auto"] .theme__auto { display: block; }
.theme[data-state="auto"] .theme__light,
.theme[data-state="auto"] .theme__dark { display: none; }
.theme[data-state="light"] .theme__auto { display: none; }
.theme[data-state="light"] .theme__light { display: block; }
.theme[data-state="dark"] .theme__auto { display: none; }
.theme[data-state="dark"] .theme__dark { display: block; }

/* Nothing to cycle with scripting off. */
.no-js .theme { display: none; }

/* --------------------------------------------------------------------------
   OPENING A SCREENSHOT FULL SIZE

   The inline picture is about a third of the size the application was
   photographed at. This is where the reader gets the rest.
   -------------------------------------------------------------------------- */

.shot--zoomable { cursor: zoom-in; }
.shot--zoomable:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* A quiet affordance. The picture is the subject; this only has to be enough
   that a reader knows there is more of it. */
.shot--zoomable::after {
  content: "";
  position: absolute;
  inset-block-start: 0.5rem;
  inset-inline-end: 0.5rem;
  width: 1.6rem;
  height: 1.6rem;
  border-radius: 4px;
  background: rgb(0 0 0 / 0.55) no-repeat center/0.9rem 0.9rem
    url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='white' stroke-width='1.6' stroke-linecap='round'%3E%3Ccircle cx='7' cy='7' r='4.6'/%3E%3Cpath d='M10.4 10.4L14 14M7 5v4M5 7h4'/%3E%3C/svg%3E");
  opacity: 0;
  transition: opacity 0.12s;
  pointer-events: none;
}
.shot--zoomable:hover::after,
.shot--zoomable:focus-visible::after { opacity: 1; }

.lb-open { overflow: hidden; }        /* the page behind must not scroll too */

.lb {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  flex-direction: column;
  background: rgb(9 11 14 / 0.94);
  color: #f2f4f7;
}

.lb__bar {
  flex: none;
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.6rem 0.9rem;
  border-block-end: 1px solid rgb(255 255 255 / 0.14);
  font-size: 0.9rem;
}
.lb__title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.lb__close {
  flex: none;
  width: 2.1rem;
  height: 2.1rem;
  border: 1px solid rgb(255 255 255 / 0.24);
  border-radius: var(--radius);
  background: none;
  color: inherit;
  font-size: 0.95rem;
  cursor: pointer;
}
.lb__close:hover { background: rgb(255 255 255 / 0.12); }

.lb__scroll {
  flex: 1;
  overflow: auto;
  padding: 1rem;
  display: flex;
  justify-content: center;
  align-items: flex-start;
  cursor: zoom-out;
}

/* ⚠ THE STAGE IS THE IMAGE'S BOX AND NOTHING ELSE.

   Pins and rings inside it are positioned as percentages of this element, so
   any difference between its box and the picture's box is a proportional
   error in every marker. Only the image is in the flow here — the caption and
   the close button are outside — and `display: block` on the image removes
   the inline baseline gap that would otherwise add a few pixels of height
   below it and push every pin up.

   Same rule, and the same reason, as the note in print.css. */
.lb__stage {
  position: relative;
  display: block;
  flex: none;
  line-height: 0;
}
.lb__stage img { display: block; width: 100%; height: auto; }

/* The markers keep their measured percentages. They are drawn slightly larger
   here only because the picture is: a 1.55rem pin over a 486px image and the
   same pin over a 1440px one are not the same weight on the page. */
.lb__stage .shot__pin { width: 1.75rem; height: 1.75rem; font-size: 0.9rem; line-height: 1.75rem; }
.lb__stage .shot__ring { border-width: 3px; }

@media (max-width: 900px) {
  .langs a { padding: 0.3rem 0.4rem; font-size: 0.8rem; }
  .lb__scroll { padding: 0.4rem; }
}

/* --------------------------------------------------------------------------
   SIDEBAR
   -------------------------------------------------------------------------- */

/* THE RULE RUNS THE FULL HEIGHT OF THE PAGE; THE NAV INSIDE IT STICKS.

   These are two different jobs and they were being asked of one element. A
   sticky box has to be `align-self: start` — it needs somewhere to travel
   within its grid area — but that also shrinks it to its own content, so the
   border stopped wherever the list of chapters happened to end, usually a
   third of the way down. It read as a divider that had given up.

   So the OUTER element stretches to the grid row and carries the rule, and an
   INNER element does the sticking. Nothing else changes. */
.sidebar {
  grid-row: 2 / -1;   /* the rule runs past the footer, to the very bottom */
  border-inline-end: 1px solid var(--rule);
  font-size: 0.9rem;
  min-width: 0;
}

.sidebar__inner {
  position: sticky;
  inset-block-start: var(--topbar-h);
  max-height: calc(100vh - var(--topbar-h));
  overflow-y: auto;
  overscroll-behavior: contain;   /* scrolling the nav must not scroll the page */
  scrollbar-width: thin;          /* a side panel, not the page: keep it light */
  padding-block: 1.75rem 3rem;
  padding-inline: 0 1rem;
}

.sidebar__label {
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--ink-faint);
  margin-block: 0 0.5rem;
}

.sidebar__group { margin-block-end: 1.6rem; }

.sidebar__list { list-style: none; margin: 0; padding: 0; }

.sidebar__list li {
  margin: 0;
  /* The spine the current-page marker sits on. */
  border-inline-start: 2px solid var(--rule);
}

.sidebar__link {
  display: flex;
  gap: 0.5rem;
  align-items: baseline;
  padding: 0.32rem 0.65rem;
  color: var(--ink-soft);
  text-decoration: none;
  border-radius: 0 4px 4px 0;
  line-height: 1.35;
  /* A label that wraps turns a list of six into a wall. The short forms come
     from `nav:` in each unit's frontmatter; this is the belt to that braces —
     if a project ships a long one it is clipped rather than allowed to
     reflow the whole column. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sidebar__link:hover { background: var(--ground-sunk); color: var(--ink); }

.sidebar__list li:has(> [aria-current]) { border-inline-start-color: var(--accent); }
.sidebar__link[aria-current] {
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: 650;
}

.sidebar__n {
  flex: none;
  font-size: 0.75rem;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
  min-width: 1.1em;
}
.sidebar__link[aria-current] .sidebar__n { color: inherit; }

/* THE TWO WAYS IN — the first thing in the sidebar, set apart from the
   chapters by a rule, because they are a different kind of thing: a way of
   ordering the manual rather than a place in it. */
.views {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  margin-block-end: 1.5rem;
  padding-block-end: 1.25rem;
  border-block-end: 1px solid var(--rule);
}

.views__item {
  display: block;
  padding: 0.45rem 0.7rem;
  border-radius: var(--radius);
  color: var(--ink-soft);
  text-decoration: none;
  font-weight: 600;
}
.views__item:hover { background: var(--ground-sunk); color: var(--ink); }
.views__item[aria-current] { background: var(--accent-soft); color: var(--accent); }

/* --------------------------------------------------------------------------
   BREADCRUMB — real links now, so it needs to look like them
   -------------------------------------------------------------------------- */

.crumb a { color: var(--ink-faint); text-decoration: none; }
.crumb a:hover { color: var(--accent); text-decoration: underline; }
.crumb__sep { color: var(--ink-faint); opacity: 0.6; margin-inline: 0.3em; }
/* The separator is a glyph that points along the reading direction, so it has
   to be mirrored by hand — it is content, not layout. */
[dir="rtl"] .crumb__sep { transform: scaleX(-1); display: inline-block; }

/* --------------------------------------------------------------------------
   PREVIOUS / NEXT — the pathnav block that unit.html.j2 already had
   -------------------------------------------------------------------------- */

.pathnav { gap: 0.75rem; }
.pathnav .card { flex: 1 1 0; min-width: min(100%, 14rem); }
.pathnav__next { text-align: end; }
.pathnav .card__sub { display: flex; align-items: center; gap: 0.3rem; }
.pathnav__next .card__sub { justify-content: flex-end; }
.pathnav__prev .card__sub::before { content: "←"; }
.pathnav__next .card__sub::after  { content: "→"; }
[dir="rtl"] .pathnav__prev .card__sub::before { content: "→"; }
[dir="rtl"] .pathnav__next .card__sub::after  { content: "←"; }

/* --------------------------------------------------------------------------
   LANDING PAGE
   -------------------------------------------------------------------------- */

.hero { margin-block-end: 3rem; }
.hero__kicker {
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--accent);
  font-weight: 700;
  margin-block-end: 0.7rem;
}
.hero__title { font-size: clamp(2.1rem, 5vw, 3rem); margin-block: 0 0.8rem; letter-spacing: -0.015em; }
.hero__lede { font-size: 1.15rem; color: var(--ink-soft); max-width: 40rem; margin-block-end: 2rem; }

.facts { display: flex; flex-wrap: wrap; gap: 2.5rem; padding-block: 1.25rem; border-block: 1px solid var(--rule); }
.fact__n { display: block; font-size: 1.9rem; font-weight: 700; line-height: 1.1; font-variant-numeric: tabular-nums; }
.fact__k { font-size: 0.82rem; color: var(--ink-faint); }

.ways { display: grid; grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr)); gap: 1.1rem; margin-block: 2.5rem; }

.way {
  display: flex;
  flex-direction: column;
  padding: 1.5rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--ground);
  text-decoration: none;
  color: inherit;
}
.way:hover { border-color: var(--accent); background: var(--accent-soft); }
.way__title { font-size: 1.25rem; font-weight: 650; margin-block-end: 0.4rem; }
.way__blurb { color: var(--ink-soft); font-size: 0.95rem; margin-block-end: 1rem; }

/* What is actually inside this way in — the path names, or three real
   questions. The landing page stopped listing all six chapters, so this is
   what tells a reader which of the two doors is theirs. */
.way__list {
  list-style: none;
  margin: 0 0 1.2rem;
  padding: 0;
  flex: 1;
  font-size: 0.9rem;
  color: var(--ink-soft);
}
.way__list li {
  padding-block: 0.25rem;
  padding-inline-start: 0.9rem;
  position: relative;
  margin: 0;
}
.way__list li::before {
  content: "";
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: 0.85em;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--ink-faint);
}

.way__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
  padding-block-start: 0.9rem;
  border-block-start: 1px solid var(--rule);
}
.way__go { color: var(--accent); font-weight: 650; font-size: 0.9rem; }
.way__go::after { content: " →"; }
[dir="rtl"] .way__go::after { content: " ←"; }
.way__meta { font-size: 0.8rem; color: var(--ink-faint); }

/* --------------------------------------------------------------------------
   THE TWO VIEWS

   One template renders both, and these are the two presentations it switches
   between. A training path is an ORDER — it is numbered and read downwards.
   The reference index is a LOOKUP — it is a list of questions, scanned. They
   are the same units either way, which is the whole point of the two files.
   -------------------------------------------------------------------------- */

/* Training: an ordered walk. */
.walk { list-style: none; margin: 1.5rem 0 0; padding: 0; counter-reset: walk; }

.walk__item { position: relative; counter-increment: walk; }

.walk__link {
  display: grid;
  grid-template-columns: 2.4rem minmax(0, 1fr) auto;
  gap: 0.2rem 1rem;
  align-items: baseline;
  padding: 1rem 0.8rem;
  border-block-end: 1px solid var(--rule);
  text-decoration: none;
  color: inherit;
}
.walk__item:first-child .walk__link { border-block-start: 1px solid var(--rule); }
.walk__link:hover { background: var(--ground-sunk); }

.walk__n {
  grid-row: span 2;
  width: 1.9rem;
  height: 1.9rem;
  border-radius: 50%;
  border: 1px solid var(--rule);
  background: var(--ground-sunk);
  color: var(--ink-soft);
  font-size: 0.85rem;
  font-weight: 700;
  line-height: 1.85rem;
  text-align: center;
}
.walk__link:hover .walk__n { border-color: var(--accent); color: var(--accent); }
.walk__title { font-weight: 650; font-size: 1.05rem; }
.walk__note { grid-column: 2; color: var(--ink-soft); font-size: 0.9rem; }
.walk__meta { grid-row: span 2; color: var(--ink-faint); font-size: 0.8rem; white-space: nowrap; }

/* Reference: a scannable list of questions.

   ⚠ COLUMNS, NOT GRID, AND THAT IS THE POINT.

   A grid with `auto-fill` lays its items out ACROSS and then down, so
   fourteen questions in three tracks read 1-2-3 on the first line and 4-5-6
   on the second. The eye wants to go down a list of questions, and it was
   being sent sideways.

   Multi-column flows top to bottom and then to the next column, which is the
   order the reader is already using. `break-inside: avoid` is what stops a
   card being split across the column boundary, and the vertical rhythm comes
   from each card's own margin rather than a grid `gap` — a gap in one axis
   and a margin in the other is where the uneven spacing came from. */
.asks {
  columns: 21rem;
  column-gap: 1rem;
  margin-block: 1.25rem 2.5rem;
}

.ask {
  display: block;
  break-inside: avoid;
  margin-block-end: 0.7rem;
  padding: 0.85rem 1rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--ground);
  text-decoration: none;
  color: inherit;
}
.ask:hover { border-color: var(--accent); background: var(--accent-soft); }
.ask:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ask__q { font-weight: 600; }
.ask__a { font-size: 0.82rem; color: var(--ink-faint); margin-block-start: 0.15rem; }

.group-head { margin-block: 2.5rem 0.4rem; padding-block-end: 0.5rem; border-block-end: 1px solid var(--rule); }
.group-head h2 { margin: 0; font-size: 1.2rem; }
.group-head p { margin: 0.4rem 0 0; color: var(--ink-soft); font-size: 0.95rem; max-width: var(--measure); }

/* --------------------------------------------------------------------------
   COLLAPSIBLE GROUPS — the lookup view only

   The reference page is scanned, not read. Closed, its four or five headings
   fit on one screen; open, sixteen questions push the last heading out of
   sight and the reader is reading a wall again.

   ⚠ IT HAS TO LOOK PRESSED-ABLE. The first version of this styled almost
   nothing and left the browser's own disclosure triangle showing above the
   heading with the count on a line of its own. It worked and it read as
   debris — a reader does not click something that looks like it fell out of
   the page. So each group is now a bordered row with its own hover, the
   count sits quietly on the row, and the chevron is at the end where a
   reader looks for one.

   ⚠ EVERY COLOUR IS A TOKEN, so both themes come out right from one set of
   rules. There is no dark-mode block below and there should never need to be
   — base.css redefines the tokens and this inherits the answer.
   -------------------------------------------------------------------------- */

/* The row itself. `overflow: hidden` keeps the summary's hover background
   inside the rounded corners; without it the tint bleeds past the radius at
   the top two corners and the row looks chipped. */
.group {
  margin-block: 0 0.5rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--ground);
  overflow: hidden;
}

/* ⚠ THE MARKER IS REMOVED THREE WAYS, AND ALL THREE ARE NEEDED.
   `list-style` is what Firefox honours, `::-webkit-details-marker` is
   Safari's, and Chrome drops it once `display` stops being `list-item`.
   Any one alone leaves a stray triangle in one of the three. */
.group-head--toggle {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 0;
  padding: 0.85rem 1.05rem;
  border: 0;
  cursor: pointer;
  list-style: none;
  user-select: none;
  transition: background 0.1s ease;
}
.group-head--toggle::-webkit-details-marker { display: none; }
.group-head--toggle::marker { content: ""; }

.group-head--toggle h2 {
  flex: 1 1 auto;
  margin: 0;
  font-size: 1.05rem;
}

/* Quiet, and on the row rather than under it. It is there to tell a reader
   whether opening the group is worth the click, which is a job it can only do
   from beside the heading. */
.group-head__n {
  flex: none;
  font-size: 0.8rem;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}

/* The chevron, drawn from two borders so it inherits the text colour and
   needs no asset. Down when closed, up when open — a vertical pair rather
   than the right/down pair, because vertical needs no mirroring in Arabic
   and one less RTL rule is one less thing to get wrong. */
.group-head--toggle::after {
  content: "";
  flex: none;
  width: 0.42rem;
  height: 0.42rem;
  border-inline-end: 2px solid currentColor;
  border-block-end: 2px solid currentColor;
  transform: rotate(45deg);
  translate: 0 -0.12rem;
  opacity: 0.5;
  transition: transform 0.12s ease, opacity 0.1s ease;
}
.group[open] > .group-head--toggle::after {
  transform: rotate(225deg);
  translate: 0 0.12rem;
}

/* Hover on the ROW, not on the text. A colour change on a heading is easy to
   miss and says "link"; a tinted row says "press me". */
.group-head--toggle:hover { background: var(--ground-sunk); }
.group-head--toggle:hover h2 { color: var(--accent); }
.group-head--toggle:hover::after { opacity: 1; }

.group-head--toggle:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* The rule under the heading appears only when the group is open, so a closed
   list reads as separate rows rather than one ruled block. */
.group[open] > .group-head--toggle { border-block-end: 1px solid var(--rule); }

.group-head__blurb {
  margin: 0;
  padding: 0.8rem 1.05rem 0;
  color: var(--ink-soft);
  font-size: 0.95rem;
  max-width: var(--measure);
}

/* `.asks` carries its own margin for the uncollapsed page; inside a group the
   row's padding does that job instead. */
.group > .asks {
  margin: 0;
  padding: 0.9rem 1.05rem 0.35rem;
}

/* ⚠ PRINT. The site's PDF is built from unit pages only, so a closed group
   cannot hide anything from it — but a reader may still hit ctrl-P here, and
   four headings is not a page worth printing.

   The mechanism that actually opens them is viewer.js's `beforeprint`
   handler, because setting `open` is the only way that works in every engine.
   The rule below is the second line of defence, for scripting turned off: it
   reveals the content in engines that hide it with `display`, and quietly
   does nothing in those that do not. Deliberately not the only defence. */
@media print {
  .group { border-color: var(--rule); break-inside: avoid; }
  .group-head--toggle { cursor: auto; }
  .group-head--toggle::after { display: none; }
  details.group:not([open]) > *:not(summary) { display: block; }
}

/* --------------------------------------------------------------------------
   NOTICE — a language that has no content yet says so
   -------------------------------------------------------------------------- */

.notice {
  max-width: 38rem;
  margin-block: 3rem;
  padding: 1.75rem;
  border: 1px solid var(--rule);
  border-inline-start: 4px solid var(--warn);
  border-radius: 0 var(--radius) var(--radius) 0;
  background: var(--warn-soft);
}
.notice h1 { font-size: 1.5rem; margin-block: 0 0.6rem; }
.notice p { color: var(--ink-soft); }
.notice p:last-child { margin-block-end: 0; }
.notice__actions { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-block-start: 1.5rem; }
.notice__actions a {
  padding: 0.5rem 0.9rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--ground);
  text-decoration: none;
  font-size: 0.92rem;
}

.site-note {
  margin-block-start: 3rem;
  padding-block-start: 1.5rem;
  border-block-start: 1px solid var(--rule);
  color: var(--ink-faint);
  font-size: 0.85rem;
}

/* --------------------------------------------------------------------------
   NARROW SCREENS

   The sidebar becomes a disclosure. Below 900px base.css has already stacked
   each step's prose above its screenshot, so the reading order is unchanged —
   only the navigation moves.
   -------------------------------------------------------------------------- */

@media (max-width: 900px) {
  body.has-chrome { display: block; }

  .topbar { padding-inline: 0.9rem; gap: 0.6rem; }
  .topbar__toggle { display: flex; }

  /* ⚠ `min-width: 0` IS THE WHOLE FIX FOR THE TOPBAR OVERFLOWING A PHONE.
     At 375px the bar was 513px wide and the page scrolled sideways, in both
     editions, on every page. Nothing in it was too wide — a scan for elements
     wider than the viewport returned NOTHING, which is what made it puzzling.

     A flex item's `min-width` defaults to `auto`, meaning "never shrink below
     your own min-content". `.search input` already carries `min-width: 0`, so
     the input could shrink — but `.search`, the flex item that HOLDS it,
     could not, and floored the row at its content's intrinsic width. The
     language links and the theme button, both `flex: none`, were then simply
     pushed off the right-hand edge.

     So the fix is on the container, not the input. Setting it here rather
     than on line 186 keeps the desktop bar's `flex: 0 1 22rem` basis intact. */
  .search { flex: 1 1 auto; min-width: 0; }
  .search__key { display: none; }

  .sidebar {
    display: none;
    border-inline-end: 0;
    border-block-end: 1px solid var(--rule);
  }
  .sidebar.is-open { display: block; }
  .sidebar__inner {
    position: static;
    max-height: none;
    overflow: visible;
    padding: 1.25rem 0.9rem;
  }

  body.has-chrome > .page { padding-inline: 1.25rem; }
  body.has-chrome > .pagefoot { margin-inline: 1.25rem; }
}

/* --------------------------------------------------------------------------
   PHONES — the search box gets its own row.

   `min-width: 0` above stops the bar overflowing, but stopping an overflow is
   not the same as the result being usable: at 375px everything fitted by
   squeezing the search field to 68 CSS pixels, which is not a search field.
   It is a magnifying glass with a rumour of an input beside it.

   Five things want that row — disclosure, title, search, two editions, and
   the light/dark switch — and on a phone the honest answer is that they do
   not fit on one. So the bar wraps and search takes the second row whole.
   `order: 5` puts it there without moving it in the DOM, so the tab order
   still runs left to right, top to bottom.

   THE HEIGHT IS A VARIABLE FOR EXACTLY THIS REASON. The sidebar's sticky
   offset is `inset-block-start: var(--topbar-h)` and its height is computed
   from the same token, so redeclaring it here moves the drawer down with the
   bar. Hard-coding 3.5rem anywhere would have made this a layout rewrite.
   -------------------------------------------------------------------------- */

@media (max-width: 560px) {
  /* Two rows of content measure ~81px: a 36px control row, a 7px gap and a
     38px field. 6.25rem leaves a few pixels of air above and below once
     `align-content: center` has done its work.

     ⚠ NO `padding-block` HERE. `height` is fixed, so padding does not make
     the bar taller — it eats into the same box and pushes the content out of
     it. The first row clipped against the top of the screen for exactly that
     reason. Change the token, never pad the bar. */
  :root { --topbar-h: 6.25rem; }

  .topbar {
    flex-wrap: wrap;
    align-content: center;
    row-gap: 0.45rem;
  }

  /* ⚠ THE TITLE IS DROPPED, NOT SHRUNK, AND ONLY BECAUSE IT IS SAID AGAIN
     FORTY PIXELS LOWER.

     "SNB Production Workflow" is 297px of unbreakable text. Left in, it takes
     a whole row to itself and the bar wraps to THREE, which is when the top
     row starts clipping against the edge of the screen. Truncating it instead
     was the first thing tried and it produced `SNB …`, which identifies
     nothing and still costs 164px.

     It can go because nothing is lost. Every page states its own title
     immediately below the bar: unit and view pages open with
     `<p class="crumb">`, whose FIRST LINK is the words "SNB Production
     Workflow" pointing at `index.html` — the same text and the same
     destination as the control being hidden. The home page has no crumb and
     needs none: its `<h1>` is that same sentence.

     So check the crumb before changing this. If a page type ever ships
     without one AND without an `<h1>` naming the manual, this rule strands
     its reader with no way back to the front page. */
  .topbar__home { display: none; }

  /* With the title gone the row would pack left. The disclosure keeps its
     corner and everything else is pushed to the far side, which is where a
     thumb expects it. */
  .topbar__toggle { margin-inline-end: auto; }

  .search { order: 5; flex: 1 0 100%; }
}

/* With no JavaScript there is nothing to open the disclosure with, so the
   sidebar stays expanded and the button that would do nothing is hidden. */
.no-js .sidebar { display: block; }
.no-js .topbar__toggle { display: none; }
.no-js .search { display: none; }

@media print {
  .topbar, .sidebar, .skip, .search, .pathnav, .pagefoot { display: none !important; }
  body.has-chrome > .page { padding-inline: 0; }
  body.has-chrome { display: block; }
}

/* The thumb darkens under the pointer, like the rest of the chrome does. */
.sidebar__inner:hover, .search__results:hover {
  scrollbar-color: var(--scroll-thumb-hover) transparent;
}

/* ==========================================================================
   THE COVER — index.html at the root of the site (cover.html.j2).

   One screen and never a scroll: the body is exactly the viewport and clips.
   That is safe only because nothing on it is the sole copy of anything — the
   language's home page says it all again — and because it gives way in a
   fixed order as the window shrinks: the picture shrinks into whatever is
   left, then the lede goes, then the counts.

   Every colour is a theme token or mixed from one, so the cover follows the
   theme toggle like the rest of the site with no palette of its own.
   ========================================================================== */

body.is-cover {
  height: 100vh;
  height: 100svh;
  overflow: hidden;
  margin: 0;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  background:
    radial-gradient(60rem 42rem at 88% 8%,
                    color-mix(in srgb, var(--accent) 20%, transparent), transparent 62%),
    radial-gradient(44rem 30rem at 0% 105%,
                    color-mix(in srgb, var(--accent) 12%, transparent), transparent 60%),
    var(--ground);
  color: var(--ink);
  position: relative;
}
.cover__bar {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1.25rem clamp(1.25rem, 5vw, 4.5rem);
}
.cover__brand {
  font-weight: 700;
  letter-spacing: -0.01em;
}

.cover__main {
  position: relative;
  min-height: 0;
  display: grid;
  grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr);
  gap: clamp(2rem, 5vw, 5rem);
  align-items: center;
  padding-inline: clamp(1.25rem, 5vw, 4.5rem);
}
body.is-cover:not(.has-shot) .cover__main {
  grid-template-columns: minmax(0, 44rem);
  justify-content: center;
}

.cover__kicker {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 0;
  color: var(--accent);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.cover__kicker::before {
  content: "";
  inline-size: 2rem;
  block-size: 2px;
  background: currentColor;
  border-radius: 2px;
}
.cover__title {
  margin: 0.35em 0 0.15em;
  font-size: clamp(2.4rem, min(5.4vw, 10vh), 5.2rem);
  line-height: 1.02;
  font-weight: 800;
  letter-spacing: -0.03em;
}
.cover__tagline {
  margin: 0;
  font-size: clamp(1.1rem, min(2.1vw, 3.4vh), 1.6rem);
  color: var(--ink-soft);
  font-weight: 500;
}
.cover__lede {
  margin: 1.4rem 0 0;
  max-width: 34rem;
  color: var(--ink-soft);
  line-height: 1.65;
}

.cover__facts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem 2.25rem;
  margin: 1.9rem 0 0;
}
.cover__facts div { display: flex; flex-direction: column; }
.cover__facts dt {
  font-size: clamp(1.5rem, 2.6vw, 2rem);
  font-weight: 800;
  line-height: 1.1;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.cover__facts dd {
  margin: 0;
  font-size: 0.8rem;
  color: var(--ink-faint);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.cover__ways {
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
  margin-top: 2.1rem;
}
.cover__edition {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem 1.5rem;
}
.cover__open {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.85rem 1.5rem;
  border-radius: 999px;
  background: var(--accent);
  color: var(--ground);
  font-weight: 650;
  text-decoration: none;
  box-shadow: 0 10px 28px color-mix(in srgb, var(--accent) 35%, transparent);
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}
.cover__open:hover {
  transform: translateY(-1px);
  box-shadow: 0 14px 34px color-mix(in srgb, var(--accent) 45%, transparent);
}
.cover__open:focus-visible { outline: 3px solid var(--accent-soft); outline-offset: 3px; }
[dir="rtl"] .cover__open svg { transform: scaleX(-1); }
.cover__links { display: inline-flex; flex-wrap: wrap; gap: 0.5rem 1.25rem; }
.cover__links a {
  white-space: nowrap;
  color: var(--ink-soft);
  text-decoration: underline;
  text-decoration-color: var(--rule);
  text-underline-offset: 0.3em;
}
.cover__links a:hover { color: var(--accent); text-decoration-color: currentColor; }

/* The product in a browser window, tilted towards the text and running off
   the far edge of the screen. Decoration: the figure is aria-hidden. */
.cover__shot {
  margin: 0;
  min-height: 0;
  height: 100%;
  display: flex;
  align-items: center;
  perspective: 2200px;
}
.cover__frame {
  width: 118%;
  max-height: 100%;
  display: flex;
  flex-direction: column;
  border-radius: 14px;
  overflow: hidden;
  border: 1px solid var(--rule);
  background: var(--ground-sunk);
  box-shadow:
    0 40px 80px -20px rgb(0 0 0 / 0.35),
    0 12px 30px -10px rgb(0 0 0 / 0.25);
  transform: rotateY(-9deg) rotateX(3deg);
  transform-origin: left center;
}
[dir="rtl"] .cover__frame {
  transform: rotateY(9deg) rotateX(3deg);
  transform-origin: right center;
}
.cover__chrome {
  flex: none;
  display: flex;
  gap: 7px;
  align-items: center;
  height: 30px;
  padding-inline: 14px;
  background: var(--ground);
  border-bottom: 1px solid var(--rule);
}
.cover__chrome i {
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: #ff5f57;
}
.cover__chrome i:nth-child(2) { background: #febc2e; }
.cover__chrome i:nth-child(3) { background: #28c840; }
.cover__frame img {
  display: block;
  width: 100%;
  min-height: 0;
  height: auto;
  max-height: calc(100svh - 13rem);
  object-fit: cover;
  object-position: top left;
}

.cover__foot {
  position: relative;
  padding: 1rem clamp(1.25rem, 5vw, 4.5rem) 1.25rem;
  font-size: 0.8rem;
  color: var(--ink-faint);
}

/* Narrow: one column. The picture takes whatever height the text leaves and
   is cut to fit, flat and edge to edge. */
@media (max-width: 860px) {
  .cover__main {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr);
    align-items: start;
    gap: 1.75rem;
  }
  .cover__shot { align-items: stretch; }
  .cover__frame {
    width: 100%;
    max-height: none;
    transform: none;
  }
  [dir="rtl"] .cover__frame { transform: none; }
  .cover__frame img {
    flex: 1;
    max-height: none;
    height: 100%;
  }
  .cover__ways { margin-top: 1.5rem; }
  .cover__facts { margin-top: 1.4rem; }
}

/* Phones: a strip of dashboard a few rows high says nothing, so the picture
   goes and the text is centred in the screen instead. */
@media (max-width: 560px) {
  .cover__shot { display: none; }
  .cover__main { grid-template-rows: auto; align-content: center; }
  .cover__facts { gap: 0.6rem 1.4rem; }
  .cover__facts dd { font-size: 0.7rem; }
}

/* Short windows give way in order: the lede, then the counts, then the
   picture. Nothing on the cover is the only copy of anything. */
@media (max-height: 760px) and (max-width: 860px), (max-height: 600px) {
  .cover__lede { display: none; }
}
@media (max-height: 620px) and (max-width: 860px), (max-height: 470px) {
  .cover__facts { display: none; }
}
@media (max-height: 520px) and (max-width: 860px) {
  .cover__shot { display: none; }
}
