/* VarnaOps — full-width app shell (LAYOUT EXPERIMENT, 2026-09-03)
   ============================================================================
   WHY THIS IS A SEPARATE FILE, AND WHAT IT IS ALLOWED TO DO

   The site is one 860px column on every page, so a 1920px screen is about half
   empty. This file is the alternative the owner asked to SEE before deciding: it
   changes only WHERE things sit, never what they look like. No new colours, no new
   type scale, no new components — every rule below reuses the tokens and classes
   `styles.css` already defines.

   It is a separate file, linked AFTER styles.css, for two reasons:
     - reversibility. Deleting this file plus the body class on each page restores
       the current site exactly; nothing in styles.css is edited.
     - merge safety. Other writers are editing page COPY in the same tree, and a
       layout change appended into styles.css would collide with them.

   TWO PAGE TYPES, ONE CHROME
     .app-shell  — index only since 2026-09-03 (runs and account moved to
                   .page-wide the same day, owner: "centred exactly like the legal
                   pages"). Full width, persistent left sidebar at >=1100px.
     .page-wide  — the text pages (terms, dpa, privacy, methods, ...), login,
                   runs and account.
                   The prose column stays ~72ch because that is a READING measure
                   and widening it would be a regression; only the header and the
                   footer stretch, so both page types read as one product.

   Everything here is scoped under one of those two body classes. An unscoped rule
   would change pages that never opted in, including the generated
   attribution-standalone.html, which inlines styles.css and not this file.
   ========================================================================== */

:root {
  /* The gutter the chrome and the app content share, so the brand, the sidebar
     edge and the first card all line up on one vertical. */
  --shell-pad: clamp(20px, 2.6vw, 44px);
  --shell-side: 236px;
  /* The header's real height: 14px padding x2 + the 42px brand lockup + 1px
     border. The sidebar is fixed, so it needs the number rather than a layout
     relationship. Below 1100px the sidebar is gone, which is also where the
     brand shrinks, so the two never disagree. */
  --shell-head: 71px;
  /* App content cap. Wide enough for a runs table and a four-across card grid,
     short of the "text stretched across a billboard" failure. */
  --shell-max: 1480px;
}

/* ---- 1. chrome spans the viewport ---------------------------------------- */
/* The top bar spans the viewport on both page types. The footer does so only on
   app pages, whose content is full width too; on a text page it follows the reading
   column, otherwise the copyright line starts at the viewport edge while the text it
   sits under is centred (owner, 2026-09-03, seen on the contact page). */
.app-shell .nav .wrap,
.page-wide .nav .wrap,
.app-shell > footer .wrap {
  max-width: none;
  padding-left: var(--shell-pad);
  padding-right: var(--shell-pad);
}

/* The top bar follows the reader. It carries the brand, the two site dropdowns
   and the account avatar, which is the one control whose menu is injected by
   nav.js — so it stays exactly where that script expects to find it. */
.app-shell .nav,
.page-wide .nav {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--bg);
}

/* THE PRODUCT LINE, IN THE TOP BAR (owner, 2026-09-03). It is the sentence that
   used to sit above the stepper as `.app-head h2`, MOVED and not copied: index.html
   has one copy of it and every other page now has one too. Muted and one line,
   because it is a label for the mark beside it and not a headline — a second bold
   sentence in a header is two brands.

   It goes below 1024px rather than wrapping. At that width the nav's own links are
   already competing for the row, and a tagline that pushes them onto a second line
   costs more than it says. `white-space: nowrap` is what makes the breakpoint the
   only place it can disappear: without it the sentence would wrap silently at some
   width in between and the header would grow a line nobody chose. */
.app-shell .nav-tag,
.page-wide .nav-tag {
  /* BOLD, still muted (owner, iteration 4). The weight is what was asked for; the
     colour stays var(--muted) because bold AND var(--text) beside the wordmark is a
     second lockup rather than a label on the first one, and the point of the line is
     that it reads as secondary. */
  color: var(--muted);
  font-size: 0.86rem;
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
  padding-left: 14px;
  margin-left: 2px;
  border-left: 1px solid var(--line);
}
@media (max-width: 1024px) {
  .nav-tag { display: none; }
}

/* A sticky header hides whatever an in-page anchor scrolls to. The legal pages
   are all deep links, so every heading keeps a landing margin. */
.page-wide .prose h2,
.page-wide .prose h3,
.page-wide [id] {
  scroll-margin-top: calc(var(--shell-head) + 14px);
}

/* ---- 2. the rail IS the stepper (index.html only) -----------------------
   The first version put a hand-written <aside> here listing Analyse / My runs /
   Account. The owner's iteration replaced it with the WALKTHROUGH'S OWN STEPS,
   which is better than a copy of them for one reason worth writing down: this is
   not a new list. `#stepper` is the element walkthrough.js already builds,
   already marks `.active` / `.done` / `.incomplete`, and already listens on for
   clicks (its handler is delegated on #stepper and calls goStep(data-i)). Turning
   it on its side with CSS therefore inherits the state AND the navigation for
   free, and there is no second list that can disagree with the first about which
   step you are on.

   NO JS CHANGED FOR THIS. If a step's state ever stops being a class on .step,
   this rail goes blank in the same breath as the horizontal tab bar does, which
   is the correct coupling.

   The site nav that used to live here moved into the account dropdown, which now
   carries all three destinations (nav.js LINKS).

   BELOW 1100px THE SAME DESIGN APPLIES, LAID OUT SIDEWAYS (owner, 2026-09-05, after
   seeing the app on a 390px phone). What used to happen below the breakpoint was that
   none of this applied and styles.css's older underline tab bar came back, so the
   phone got a different product from the desktop. The steps are now one horizontal
   strip in the rail's own visual system.

   That is why this section is split in two. Everything about how a STEP LOOKS -- the
   numbered circle, the filled circle for a step that is done, the pill for the one you
   are on -- is written once, outside any breakpoint (2a). Only the GEOMETRY is per
   width: a fixed column at >=1100px (2b), a scrolling row below it (section 6). A
   colour or a weight written twice is a colour that can disagree with itself. */

/* ---- 2a. what a step looks like, at every width ------------------------- */
/* A row, not a tab: the underline that marked the current step horizontally becomes a
   filled pill, because an underline under a left-aligned row reads as a rule between
   two rows. On the narrow strip the pill is what carries the active state too, so the
   two layouts mark the same step the same way. */
.app-rail .stepper .step {
  gap: 10px;
  padding: 8px 10px;
  margin-bottom: 0;
  border-bottom: 0;
  border-radius: 8px;
}
.app-rail .stepper .step:hover { background: var(--subtle); }
.app-rail .stepper .step.active { background: var(--accent-soft); }
/* THE STEP NUMBER COMES BACK. styles.css hides .sdot because a number beside a
   tab label is noise in a tab bar; in the rail it is the state marker -- hollow
   for a step not reached, filled for one that is done, accent-ringed for the one
   you are on. Three states, no new markup, no new colours. */
.app-rail .stepper .sdot {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 20px;
  height: 20px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--surface);
  color: var(--faint);
  font-size: 0.7rem;
  font-weight: 600;
  line-height: 1;
}
.app-rail .stepper .step.done .sdot {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}
.app-rail .stepper .step.active .sdot {
  border-color: var(--accent);
  color: var(--accent);
}

/* PRE-PAINT, mirroring styles.css lines 472-476. boot.js stamps data-step on
   <html> before the body is parsed so an F5 on step 3 does not flash step 1;
   those rules colour the restored step's LABEL, and the rail's marks (the pill
   and the filled dots) need the same treatment or the flash comes back through
   a different door. Enumerated because CSS cannot compare an attribute to an
   index -- the same reason the rules they mirror are enumerated. */
html[data-step="1"] .app-rail .stepper .step[data-i="1"],
html[data-step="2"] .app-rail .stepper .step[data-i="2"],
html[data-step="3"] .app-rail .stepper .step[data-i="3"] { background: var(--accent-soft); }
html[data-step="1"] .app-rail .stepper .step[data-i="1"] .sdot,
html[data-step="2"] .app-rail .stepper .step[data-i="2"] .sdot,
html[data-step="3"] .app-rail .stepper .step[data-i="3"] .sdot {
  border-color: var(--accent); color: var(--accent);
}
html[data-step="1"] .app-rail .stepper .step[data-i="0"] .sdot,
html[data-step="2"] .app-rail .stepper .step[data-i="0"] .sdot,
html[data-step="2"] .app-rail .stepper .step[data-i="1"] .sdot,
html[data-step="3"] .app-rail .stepper .step[data-i="0"] .sdot,
html[data-step="3"] .app-rail .stepper .step[data-i="1"] .sdot,
html[data-step="3"] .app-rail .stepper .step[data-i="2"] .sdot {
  background: var(--accent); border-color: var(--accent); color: var(--accent-ink);
}

/* ---- 2b. the rail's geometry, at >=1100px ------------------------------- */
@media (min-width: 1100px) {
  .app-rail .stepper {
    position: fixed;
    left: 0;
    top: var(--shell-head);
    bottom: 0;
    width: var(--shell-side);
    flex-direction: column;
    align-items: stretch;
    gap: 2px;
    padding: 20px 14px;
    border-bottom: 0;
    border-right: 1px solid var(--line);
    background: var(--canvas);
    overflow-x: visible;
    overflow-y: auto;
    z-index: 40;
  }
  /* Left-aligned and free to wrap: a rail row has the width for a label on two
     lines, where the horizontal strip does not. */
  .app-rail .stepper .step {
    justify-content: flex-start;
    text-align: left;
    white-space: normal;
  }
  .app-rail .stepper .slabel { white-space: normal; }

  /* ---- 3. the shell at >=1100px ----------------------------------------
     The rail is fixed, so the flow is opened up for it rather than laid out
     around it. ONLY on .app-rail: My runs and the account page have no rail of
     their own any more (they had a copy of the site nav, which is now in the
     account menu), so their content starts at the window edge like every other
     page's does. */
  /* In the rail it is a full-width row on the steps' own rhythm, held off them by
     the divider so it reads as "and, separately, this" rather than as a fifth
     step. margin-left is reset because `auto` is what put it at the far end of the
     horizontal bar, and in a column that would shove it off the left edge. */
  .app-rail .stepper .step-cta {
    margin: 14px 0 0;
    padding: 8px 10px;
    width: 100%;
    justify-content: center;
    border-top: 1px solid var(--line);
    border-radius: 0;
    border-left: 0;
    border-right: 0;
    border-bottom: 0;
    color: var(--muted);
    font-size: 0.86rem;
    font-weight: 500;
    padding-top: 16px;
    white-space: normal;
    text-align: center;
  }
  .app-rail .stepper .step-cta:hover { color: var(--accent); border-top-color: var(--line); }
  /* "How it works" is the ONE .step-cta that sits ABOVE the steps (owner, 2026-09-07), so
     its rule is on the BOTTOM edge: the divider has to be between it and step 1, which is
     what the top rule would do for every other entry down here. Same muted, full-width,
     ruled treatment otherwise, because it is the same kind of thing: a door out of the
     four steps rather than one of them. */
  .app-rail .stepper .step-howto {
    margin: 0 0 10px;
    padding-top: 8px;
    padding-bottom: 16px;
    border-top: 0;
    border-bottom: 1px solid var(--line);
  }
  .app-rail .stepper .step-howto:hover { border-bottom-color: var(--line); }
  /* While the view is showing it IS where you are, so it takes the pill the active step
     takes, from the same token. walkthrough.js sets .active; nothing else does. */
  .app-rail .stepper .step-howto.active {
    background: var(--accent-soft);
    color: var(--accent);
    border-radius: 8px;
  }
  /* "Send feedback" (owner, 2026-09-07): the SECOND link under the steps, a mailto so it
     covers opinions about anything -- the interface, the terms, a missing feature -- not
     only a run. Same rule above it as the example link has, so each sits in its own
     compartment under the steps (owner: "a separation bar like the one above the
     example"). No override: it is a .step-cta and inherits that rule. */

  .app-rail main,
  .app-rail > footer { padding-left: var(--shell-side); }
}

/* ---- 3b. the example link, in the stepper ------------------------------
   "See a complete example" is the LAST CHILD OF #stepper in the markup, which is
   the whole trick: one home, two layouts. In the rail it is the row under the four
   steps; below the breakpoint, where the rail is a horizontal tab bar again, it is
   the last item of that row, pushed to its right-hand end. No second copy to keep
   in step, and nothing to hide in either direction.

   It is inert to the step machinery by construction: walkthrough.js reads
   `querySelectorAll('.step')` and its click handler is `closest('.step')`, so an
   anchor in this container is never a step and never counted as one. */
.stepper .step-cta {
  flex: none;
  align-self: center;
  margin-left: auto;              /* the right-hand end of the horizontal tab bar */
  padding: 7px 14px;
  font-size: 0.84rem;
  white-space: nowrap;
}

/* ---- 4. what fills the freed width, and what must NOT -------------------- */
/* The walkthrough page gets a wide measure anchored to the rail: it has a rail to
   anchor to, and its steps fill the width. (Runs and Account left .app-shell on
   2026-09-03; see the header.) */
.app-shell main .wrap {
  max-width: var(--shell-max);
  margin-left: 0;
  margin-right: auto;
  padding-left: var(--shell-pad);
  padding-right: var(--shell-pad);
}
/* The page title block reads as a page header, not as a headline stretched to
   the window. */
.app-shell .page-head { padding-top: 34px; }
.app-shell .page-head .lead { max-width: 70ch; }

/* Runs and Account are .page-wide since 2026-09-03: the same 860px centred column as
   the legal pages. The only thing they keep from their app-page days is that the
   account cards span the column instead of stopping at styles.css's 520px. */
.page-wide #acctProfile { max-width: none; }
.page-wide #acctMain .acct-page { max-width: none; }

/* The gate cards (signed out / not approved) are a sentence and a button. Full
   width they read as a banner, which is not what they are. */
.app-shell #uploadGate,
.app-shell #accessGate,
.page-wide #runsGate,
.page-wide #acctGate { max-width: 720px; }

/* ---- 4b. NO OUTER FRAME (owner, 2026-09-03) ------------------------------
   The walkthrough sat in `.app-box`: a bordered, rounded, drop-shadowed card
   with a tinted title bar, holding the whole app. On an 860px column that card
   WAS the app's edge and did useful work. Full width it is a 1400px box drawn
   around everything, which is the bounding box the complaint started with,
   redrawn one level in.

   So the frame goes and the content sits on the page. The parts are unchanged
   and keep their own boxes where a box means something: a run row, a callout, a
   cell-type chip, the account tiles, the gate cards. What is removed is only the
   container around them.

   Written as overrides here rather than edits to .app-box in styles.css, so
   deleting this file restores the card. */
.app-shell .app-box {
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  overflow: visible;
}
/* The title bar was a bar because it was the card's head. With no card it is the
   page's heading, sitting on the background beside its one action. The size step
   comes with it: 1.02rem was right for a strip inside a card and reads as a
   caption on open ground, where every other app page carries a real page title
   (.page-head h1). Still below that h1, because it is still the smaller of the
   two kinds of title. */
/* .app-head IS GONE FROM index.html (owner, iteration 4). It was the card's title
   bar; iteration 3 took its title into the top bar and this one takes its single
   action into the rail, so what was left was an empty row holding the step content
   down. The step content is now the first thing on the page. */
.app-shell .panels { padding: 14px 0 0; }
.app-shell .app { padding: 26px 0 60px; }

/* At the rail's width the stepper IS the rail, so the tab bar's line goes with
   the frame. Below 1100px it is still a tab bar and still needs it. */
@media (min-width: 1100px) {
  .app-rail .stepper { border-bottom: 0; }
  .app-rail .panels { padding-top: 0; }
  /* A question is a line of prose. It does not get read faster for being 1200px
     wide. */
  .app-shell .panel-h,
  .app-shell .ctx-body p,
  .app-shell .run-sum { max-width: 78ch; }
  /* Form controls keep their own measure: a select or a text field stretched to
     the container is the single loudest "this is a webpage, not an app" tell. */
  .app-shell .ctx-selectors select { min-width: 190px; }
  .app-shell .ref-table { max-width: 760px; }
  /* SELECT OUTCOMES, 50/50 (owner, iteration 5). styles.css gives the list all the
     free width (`minmax(0,1fr) 340px`), which was right when the free width was
     ~500px and is wrong at this one: the list is short labels with a tick and grew
     a hand's breadth of empty row, while the details box — paragraphs, radios,
     notes, a textarea — stayed at its fixed 340px and scrolled. 1fr 2fr was the
     first correction and overshot in the other direction: it squeezed the longest
     outcome name onto three lines beside its requires-note to buy width a detail
     box that is often two paragraphs did not need. Even halves. */
  .app-shell .outcomes-layout {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 22px;
  }
  /* The step's own footer is a "back / next" pair held apart by
     justify-content: space-between. Left to the full width it puts Next a metre
     from the panel it belongs to, and the reader has to cross the table to find
     it. Capped to the reading measure the panel already uses. */
  .app-shell .panel-foot { max-width: 1040px; }
  /* Same measure for the drop zone, so the step's one target and the step's
     controls share an edge instead of ending 400px apart. */
  .app-shell .drop { max-width: 1040px; }
}

/* ---- 4c. the select-context step, after the iteration-5 cut ---------------
   The step is a picker, one sentence, a name and the list of cell types. Both of
   the rules below are about a LINE OF TEXT, which is what the chips and the facts
   table became. */
/* The cell types, comma-separated. A reading measure rather than the container's
   width: at 1400px it is one long line the eye cannot get back from, and the list
   is read by scanning for a name, not left to right. */
.app-shell .ref-celltypes {
  max-width: 90ch;
  margin: 0 0 6px;
  color: var(--ink-soft);
  font-size: 0.92rem;
  line-height: 1.6;
}
/* "An appropriate pre-trained model will be used ... not be used for training."
   Sits under the picker, in .pmute's own colour and size: it answers a question
   rather than making a claim the step is about, and the page says the same thing
   in full on the privacy and data-handling pages. */
.app-shell .ctx-privacy {
  max-width: 78ch;
  margin: 0 0 18px;
}

/* ---- 5. login ------------------------------------------------------------
   The card stays centred and stays the size it is. Only the chrome around it
   changed, which is the whole point: same product, wider frame. */
.page-wide .login-grid { max-width: 900px; margin-left: auto; margin-right: auto; }

/* ---- 6. the same design on a phone (below 1100px) -------------------------
   WHAT THIS REPLACES. Until 2026-09-05 nothing in this file applied below 1100px,
   so a narrow screen fell back to what styles.css builds on its own: the header's
   three site entries wrapped onto two extra rows (141px of chrome on a 390px
   phone) and the steps went back to the older underline tab bar. The phone was
   getting a different product from the desktop.

   Nothing here is a new look. The steps keep the marks defined in section 2a and
   only change geometry; the header keeps its own height, colours and controls and
   only stops wrapping. Every rule is inside a max-width breakpoint, so the layout
   at and above 1100px is the one that was signed off, unchanged. */

/* The compact site menu nav.js builds. It is in the DOM at every width and painted
   at one of them, which is why the hiding is here rather than in the script: a
   control that comes and goes with the viewport belongs to CSS. */
.app-shell .nav-more,
.page-wide .nav-more { display: none; }

/* 1099.98px, not 1099px: a viewport can be fractional, and `max-width: 1099px` beside
   `min-width: 1100px` leaves a hairline of widths that match NEITHER, where the rail is
   gone and the strip has not arrived and styles.css's old tab bar comes back. nav.js
   watches the same string. */
@media (max-width: 1099.98px) {
  /* -- the header, one row ------------------------------------------------
     nav.js MOVES the three site entries into .nav-more. That part cannot be CSS:
     a menu is a container, and CSS can hide or move a box but cannot make one
     element the child of another. This rule keeps them out of the row for the
     frame before the script lands. It is written with the child combinator on
     purpose, so it stops matching the moment they are inside the menu, and it is
     gated on the class the script sets, so a page whose JS never ran keeps the
     wrapping header rather than losing the links altogether. */
  .nav-compact .app-shell .nav-links > .nav-dd:not(.nav-acct):not(.nav-more),
  .nav-compact .page-wide .nav-links > .nav-dd:not(.nav-acct):not(.nav-more),
  .nav-compact .app-shell .nav-links > a[href="contact.html"],
  .nav-compact .page-wide .nav-links > a[href="contact.html"],
  .nav-compact .app-shell .nav-links > a.nav-app,
  .nav-compact .page-wide .nav-links > a.nav-app { display: none; }
  .nav-compact .app-shell .nav-links > .nav-more,
  .nav-compact .page-wide .nav-links > .nav-more { display: block; }
  /* The trigger is the bordered pill the account avatar already is, so the row
     reads as two controls of one kind rather than a link beside a button. */
  .app-shell .nav-more > summary,
  .page-wide .nav-more > summary {
    padding: 6px 8px 6px 10px;
    border: 1px solid var(--line);
    border-radius: 999px;
    color: var(--text);
    gap: 7px;
  }
  .app-shell .nav-more > summary:hover,
  .page-wide .nav-more > summary:hover { background: var(--subtle); }
  .app-shell .nav-more-ico,
  .page-wide .nav-more-ico { display: block; width: 16px; height: 16px; }
  /* Right-aligned and capped to the viewport: the trigger already sits at the edge
     of a 390px screen, so a centred panel would hang off it. */
  .app-shell .nav-more > .nav-dd-menu,
  .page-wide .nav-more > .nav-dd-menu {
    left: auto; right: 0; transform: none;
    min-width: 210px; max-width: calc(100vw - 24px);
  }
  /* The two groups stay <details>, and inside the panel they open IN FLOW. Left as
     popovers they would be a second floating layer hanging off the side of the
     first one, which on a phone is off the screen. */
  .app-shell .nav-more .nav-dd,
  .page-wide .nav-more .nav-dd { position: static; }
  .app-shell .nav-more .nav-dd-menu .nav-dd-menu,
  .page-wide .nav-more .nav-dd-menu .nav-dd-menu {
    position: static; transform: none; min-width: 0;
    padding: 0 0 4px 12px; border: 0; border-radius: 0; box-shadow: none;
  }
  .app-shell .nav-more .nav-dd > summary,
  .page-wide .nav-more .nav-dd > summary { padding: 9px 12px; font-size: 0.92rem; }

  /* -- the steps, one horizontal strip ------------------------------------
     Same element, same classes, same marks as the rail; a row instead of a column,
     directly under the header. It scrolls sideways rather than wrapping: four steps
     on two rows stop reading as a sequence, and the strip's height would then change
     with the label lengths of whichever reference is selected. */
  .app-rail .stepper {
    align-items: center;
    gap: 4px;
    padding: 8px 12px;
    background: var(--canvas);
  }
  .app-rail .stepper .step { white-space: nowrap; }
  /* Back to a plain link at the end of the row. The rail gives it a top rule and a
     full width, which in a horizontal strip would draw it as a fifth step. */
  .app-rail .stepper .step-cta { border-top: 0; }
  /* Except this one, which leads the strip. `.stepper .step-cta` sets `margin-left: auto`
     to push an entry to the far end; on the FIRST item that would push the four steps
     along with it, so it is reset here rather than there. */
  .app-rail .stepper .step-howto {
    margin-left: 0;
    margin-right: 4px;
    border-bottom: 0;
    padding-bottom: 7px;
  }
  .app-rail .stepper .step-howto.active { background: var(--accent-soft); color: var(--accent); }
}

/* -- the panel, at phone width ---------------------------------------------
   620px rather than 1099px: these are about a CONTROL and a BUTTON PAIR running out
   of room, which happens well below the layout breakpoint. */
@media (max-width: 620px) {
  /* One picker per row. Three selects across 350px are three truncated labels, and
     the answer to "which tissue" is the widest of the three. */
  .app-shell .ctx-selectors { flex-direction: column; gap: 12px; }
  .app-shell .ctx-field select { width: 100%; }
  /* The step's actions: side by side while they fit, a full-width row each when they
     do not. .panel-foot already wraps, so the only thing added is that a button
     grows into the width it is given. */
  .app-shell .panel-foot .pbtn { flex: 1 1 auto; justify-content: center; }
  .app-shell .panel-foot > .pmute { flex: 1 1 100%; }
  /* The submit card's pair, same rule and same reason as the step's own actions above:
     "View in My runs" and "Start a new analysis" are 34 characters between them and do not
     fit beside each other at 390px. .sc-acts already wraps, so this only says that a button
     which has taken its own row fills it. */
  .app-shell .sc-acts .pbtn { flex: 1 1 auto; justify-content: center; }
}
@media (max-width: 560px) {
  /* 20px of gutter each side of a 390px screen is a tenth of the reading width. */
  :root { --shell-pad: 16px; }
}

/* THE BLOCK DIAGRAM, and it IS the page (owner, 2026-09-07: the first cut was "too
   much stuff", a rule-separated list and a caption per stage, which is a page of text
   wearing four boxes). THREE blocks since the Engine one went: load, configure,
   download, which is the whole interaction, and the two arrows are the run. Anything
   that needs a sentence belongs on methods.html, not in a rounded rectangle.

   One grid serves both layouts: at >=1100px the arrows are cells between the blocks
   and read left to right; below it the grid is a single column and the same arrows
   point down. minmax(0, 1fr) is what keeps a long item from widening the grid past
   the viewport, which would put a horizontal scrollbar on the whole page. */
/* THE STACKED VALUES ARE THE BASE ONES, and the desktop query restates its own. The
   diagram is read on a phone in a column, where 34px of air under it is a third of a
   thumb's travel; the numbers below are the desktop ones less about a fifth (owner,
   2026-09-07, "a little too much whitespace, not more than 20 to 25 percent"). Nothing
   about the boxes themselves changes: same border, same shadow, same radius. */
/* CAPPED AND CENTRED WHILE STACKED (owner, 2026-09-07). The stacked state is not only the
   phone: Chrome's "desktop site" on a handset is ~980px and a tablet in portrait is ~820,
   and there the one column stretched a box across the entire width, which is a paragraph
   shape, not a card. The cap is about the width the boxes take on a phone, so the same
   proportions carry all the way up to the breakpoint and the leftover width becomes air on
   both sides. Below the cap nothing binds: it is not reached and the auto margins compute
   to zero. `margin-inline` AFTER the shorthand, or the shorthand's zero would win.

   336px, AND IT IS A WRAPPING DECISION, not a taste one (owner, 2026-09-08: "in the mobile
   phone version ... the description is 3 lines, with columns being only word in the last
   line ... in the mobile version with 'desktop site' activated in chrome, the description is
   only 2 lines, which makes the width of the box too large, which is inherited by the other
   2 boxes"). The first block's one item -- the h5ad/10x line -- is the longest line in the
   diagram, so it is what decides the width all three boxes take, and where it breaks is the
   whole ruling.

   THE NUMBER CARRIES MARGIN FOR A NARROWER FONT, which is what round 12 got wrong. That
   round measured the break on this machine, found the box flips from three lines to two
   between 365 and 366px, and capped at 365 -- one pixel inside the flip. The owner's phone
   resolves the stack to a narrower face than a Linux desktop does (Roboto), the same box fit
   the line in two there, and desktop-site mode was still wrong on the only device that
   matters. So the cap is not the flip: it is the flip with room under it.

   Measured, at a text column of `box - 68` (22px of box padding either side, 2px of border,
   20px of list indent): two lines need that column to hold the 297.6px "...optional
   metadata" run, so the flip sits at `box = 297.6 x (1 - f) + 68` for a face f narrower than
   this one. f=0 gives 365.6, which is the round 12 flip; 336 holds the three-line wrap down
   to f = 9.9%, and was verified against stand-in faces at 5% and 8% narrower as well as the
   shipped one. 341 would clear the 8% asked for by 0.3% and is the same mistake at a smaller
   scale.

   The cost, and it is bigger than round 12's: the cap binds from a 383px viewport up, so
   phones at 360 (313px box) and 375 (328px) are still untouched, while 390 loses 7px
   (343 -> 336) and 412 loses 29px (365 -> 336); a 430px phone loses 47px (383 -> 336). All
   of them gain the three-line wrap, which is the shape the owner is asking for. */
.hiw {
  display: grid; gap: 0; margin: 17px 0 26px; margin-inline: auto;
  max-width: 336px; grid-template-columns: minmax(0, 1fr);
}
/* THE BOXES CARRY THE PAGE (owner, 2026-09-07): three of them now, so each one has to
   look like the object it is rather than like a faint outline around a list. The border
   is the site's own --line at 2px and the shadow is the pair `.card` uses in styles.css,
   copied rather than invented so the diagram sits at the same depth as every other card
   on the site. No new colour enters here. */
.hiw-stage {
  border: 2px solid var(--line); border-radius: 16px; background: var(--surface);
  padding: 25px 22px 28px;
  box-shadow: 0 1px 2px rgba(17,24,39,0.04), 0 10px 30px rgba(17,24,39,0.05);
}
/* The heading stays centred over the block; the list under it is LEFT-aligned, because a
   centred bulleted list staggers its own bullets, and the bullets are the column the eye
   reads down. */
/* At the size a `.panel-h` takes (1.08rem in styles.css), because these three titles ARE
   the headings of the landing view and at 0.76rem they read as labels on the lists under
   them. The tracking comes down with the size: 0.1em is a caption's spacing and it looks
   stretched on a heading.

   BACK ON --accent (owner, 2026-09-07). The mark's purple was tried here for an afternoon
   and reverted; the token it needed went with it.

   NO RESERVED LINE ANY MORE -- THE ROWS ARE SHARED INSTEAD (owner, 2026-09-08, twice: "the
   title of the boxes are a lil bit too far from their own content", and then "i wanted to
   reduce the space between title and text a bit also in the laptop version, not just
   phone"). The problem was never the margin. At desktop widths one title wraps and its
   neighbours do not, so its list started a line below theirs and the three columns read as
   staggered; the fix for two rounds was `min-height: 2.6em`, which reserves a second line in
   EVERY box whether or not any title needs it. Round 13 took that out of the stacked state,
   where no title wraps at all. At 1440 no title wraps either, so the reserved line was still
   pure air on a laptop, and 24px of it.

   Alignment across the three boxes is a job the browser has a primitive for, so the
   reservation is gone at every width and `subgrid` does it instead (see the desktop query):
   the three boxes span the same two parent rows, the title row is sized to the tallest title
   ACROSS them, and a box whose title does not wrap simply gets a short row. Nothing is
   reserved, nothing is measured in ems, and a title that grows to three lines moves all
   three lists together instead of overflowing one box.
   The line-height is still stated rather than inherited: the @supports fallback below
   reserves TWO OF IT, and a value nobody can see is a value nobody can keep in step.
   The margin stays 12px at both layouts (20px until round 13). The owner asked for "a bit",
   so this is the floor, not a dial to keep turning. */
.hiw-stage h3 {
  margin: 0 0 12px; font-size: 1.08rem; font-weight: 700; letter-spacing: 0.04em;
  line-height: 1.3;
  text-transform: uppercase; color: var(--accent); text-align: center;
}
/* A REAL dotted list (owner, 2026-09-07). The blocks list CONTENTS -- a file and what is
   in it, a run and what you pick for it -- and a bullet is what says "and also" where a
   bare centred line read as "or". The marker takes the muted token so it sits under the
   text rather than competing with it.
   ONE LEVEL ONLY. The nested `.hiw-sub` rules went with the bullet they styled: the
   metadata is part of the file's own line now, and a second level of structure for a
   four-word aside is what made one upload read as two things to bring. The numbered
   circles went earlier, with the "Four steps" block. */
/* SHRINK-WRAPPED AND CENTRED, at every width (owner, round 11). The list was as wide as
   the box, so its bullets sat hard against the left padding while the title was centred
   over them and the block read as two different alignments. `width: max-content` makes the
   ul only as wide as its longest line, `margin-inline: auto` centres that block, and the
   lines inside it stay LEFT-aligned so the bullets form one straight column: centring the
   text instead would stagger them, which is the version this replaces.
   `max-width: 100%` is what keeps a long line from pushing the box wider than its column,
   in which case the list simply fills the box as before. */
.hiw-list {
  margin: 0; padding: 0 0 0 20px; list-style: disc;
  width: max-content; max-width: 100%; margin-inline: auto; text-align: left;
}
.hiw-list li { font-size: 0.95rem; color: var(--text); padding: 5px 0; line-height: 1.4; }
.hiw-list li::marker { color: var(--faint); }
/* The file format after a deliverable's name. em, not rem, so it stays in proportion to the
   item it hangs off; the weight is stated rather than inherited so a label that ever goes
   semibold does not take its suffix with it. */
.hiw-fmt { color: var(--muted); font-size: 0.85em; font-weight: 400; }
/* Bolder than the first cut, which is the same request the boxes got: a 2rem --faint
   glyph between two heavy cards disappears. Larger, weighted, and on --muted instead,
   which is a token the site already uses for secondary text. */
.hiw-arrow {
  color: var(--muted); font-size: 2.8rem; font-weight: 700; line-height: 1;
  display: flex; align-items: center; justify-content: center; padding: 12px 0;
  /* Positioned in BOTH layouts, because the caption is out of the flow in both: while it
     was an in-flow sibling of the stacked arrow it pushed the glyph sideways, and the
     second arrow no longer lined up with the first down the column (owner, 2026-09-07). */
  position: relative;
}
.hiw-arrow-g::before { content: "\2193"; }   /* down while the blocks are stacked */
/* The caption on the second arrow. Small and muted so it labels the arrow rather than
   competing with the block titles either side of it, and it takes no letter-spacing of its
   own, which is what the rail's step labels do: the tracked-and-uppercase treatment in this
   view belongs to the titles.
   IT NEVER TAKES PART IN THE LAYOUT. Stacked, it sits to the RIGHT of the down glyph and
   the glyph stays on the column's centre line with the first arrow's; side by side (below)
   it moves above the glyph. Both placements are measured from the cell's own middle, which
   is where the glyph is centred, so in neither one can the label move the thing it labels.
   1.2rem clears the half-width of the glyph. */
.hiw-arrow-cap { font-size: 0.8rem; color: var(--muted); line-height: 1; }
.hiw-arrow-run .hiw-arrow-cap {
  position: absolute; top: 50%; left: calc(50% + 1.2rem); bottom: auto;
  transform: translateY(-50%); white-space: nowrap;
}
@media (min-width: 1100px) {
  .hiw {
    grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr) auto minmax(0, 1fr);
    align-items: stretch;
    /* Both of the stacked state's constraints are lifted here, not one: the cap would
       squeeze three columns into the width of one phone and the shorthand is what puts the
       inline margins back to zero. */
    margin: 22px 0 34px;
    max-width: none;
  }
  /* An eighth off each side (owner, round 9). The boxes were sized when there were four of
     them and each held a short list; three wider boxes with two or three items in them were
     carrying more air than content. The phone keeps the round 8 values, which are tighter
     still. */
  .hiw-stage { padding: 28px 24px 31px; }
  /* THE THREE BOXES SHARE THE DIAGRAM'S ROWS. Each one spans both parent rows and lays its
     own title and list out on them (`subgrid`), so the title row is as tall as the TALLEST
     title of the three and every list starts on the same line -- which is the alignment the
     `min-height` reservation was faking, without reserving a line in boxes that do not need
     one. At 1440 no title wraps and the row collapses to one line, which is the whole point
     of the round: on a laptop that reserved line was 24px of air under every title.
     The arrows are grid items in this row too, so they span both rows as well; without it
     they would sit in row 1 alone and stop being centred against the boxes beside them. */
  .hiw { grid-template-rows: auto auto; }
  .hiw-stage {
    display: grid; grid-template-rows: subgrid; grid-row: span 2;
    /* ONE COLUMN, STATED. The rows are subgridded and the columns are not, so without this
       the box gets a single implicit column sized to its widest item -- and `.hiw-list` is
       `width: max-content`, which is exactly the item that would blow it out. The list then
       overflowed the box to the right and stopped being centred in it. `100%` rather than
       `minmax(0, 1fr)` for the same reason the parent uses the latter: a percentage track
       cannot be grown by its contents at all, and the count of floored columns in this
       query stays a count of the diagram's three. */
    grid-template-columns: 100%;
  }
  .hiw-arrow { grid-row: span 2; }
  /* THE FALLBACK IS THE OLD TRICK, kept for browsers without subgrid (pre-117 Chrome,
     pre-16.4 Safari): there the lists would stagger again, which is a worse defect than the
     air this round removes, so those browsers keep the reserved line. `not (...)` rather
     than a version sniff, and it is scoped to the desktop query because the stacked state
     never needed the reservation at all. */
  @supports not (grid-template-rows: subgrid) {
    .hiw-stage h3 { min-height: 2.6em; }
  }
  /* BOTH CELLS GET THE SAME LAYOUT, and the caption is taken OUT of it (owner, 2026-09-07).
     While the caption was an in-flow column item it pushed its own glyph down and shrank the
     box it shared, so the second arrow sat lower than the first and read smaller: a label
     was deciding where the thing it labels is drawn. Absolute, so the glyph's size and
     centre are decided by the same two rules in both cells and the caption can only float
     above them.
     `bottom: calc(50% + 1.8rem)` is measured from the cell's own middle, which is where the
     glyph is centred: 1.8rem clears the 1.4rem half-height of a 2.8rem glyph, so the caption
     ends above the arrow rather than on it. */
  .hiw-arrow { padding: 0 14px; }
  .hiw-arrow-run .hiw-arrow-cap {
    left: 50%; top: auto; bottom: calc(50% + 1.8rem);
    transform: translateX(-50%);
  }
  .hiw-arrow-g::before { content: "\2192"; }
}
/* The three lines under the diagram, and there are only ever three. */
.hiw-foot { margin: 0 auto; max-width: 62ch; text-align: center; }
/* 0.82rem since 2026-09-09 (owner): at 0.95 the one foot line "dominated" the diagram above it. */
.hiw-foot p { margin: 0 0 8px; color: var(--muted); font-size: 0.82rem; }
/* The view is not inside `.prose`, so it does not inherit that block's accent link colour
   and the one link here paints as a default blue underline. */
.hiw-foot a { color: var(--accent); }
/* The view's headline and its call to action. The headline takes the size the site's own h2
   uses (1.3rem in styles.css, `.prose h2`) rather than a number of its own: it is one step
   above the 1.08rem the panel headings and the block titles share, which is what makes it
   read as the page's title and not as a fourth block title. Left-aligned, because it aligns
   with the left edge of the first box and a centred headline over left-aligned content is a
   third alignment on one screen. */
.panel-hiw .hiw-h {
  margin: 4px 0 0; font-size: 1.3rem; font-weight: 600; letter-spacing: -0.02em;
  text-align: left;
}
.panel-hiw .hiw-cta { justify-content: center; margin-top: 26px; }
