:root {
  --bg: #10141a;
  --panel: #171d26;
  --panel2: #1e2632;
  --line: #2b3644;
  --ink: #e6edf5;
  --ink-dim: #97a5b8;
  --accent: #4da3ff;
  --z0: #4da3ff;   /* plastic      */
  --z1: #ffa64d;   /* inelastic LTB */
  --z2: #57d38c;   /* elastic LTB   */
  /* the fixed footer bar's height. `body` reserves exactly this much at the
     bottom so the bar can never sit on top of the last row of content; page 4's
     rows-per-page rule reads the same value off `body`'s computed padding. */
  --footbar: 33px;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  /* room for the fixed footer bar, which is out of the flow (see .foot below) */
  padding-bottom: var(--footbar);
  background: var(--bg);
  color: var(--ink);
  font: 14px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
        "Helvetica Neue", Arial, sans-serif;
}

.topbar { padding: 14px 22px; text-align: center;
          border-bottom: 1px solid var(--line); }
.topbar h1 { margin: 0; text-align: center; font-size: 20px; font-weight: 600;
             letter-spacing: .2px; }

/* round 24: the header is one centred block on every page that carries one. The
   five step-by-step pages lost the subtitle line again (client's request), so no
   page uses the old left-aligned two-line layout and the :has(.subtitle)
   exception that gave it to them is gone. The profile page's second line -- the
   engineer's title under his name -- keeps the subtitle styling and the 4px gap
   the heading used to carry for it, and .topbar's text-align centres it under the
   centred heading. */
.subtitle { margin: 4px 0 0; color: var(--ink-dim); font-size: 13px; }

/* shared menu bar (static markup, duplicated in each page - see README.md) */
.sitenav { background: var(--panel); border-bottom: 1px solid var(--line); padding: 0 22px; }
.sitenav ul { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; }
.sitenav li { margin: 0; }
.sitenav li + li { border-left: 1px solid var(--line); }
.sitenav a { display: inline-block; padding: 10px 14px; font-size: 13px;
             color: var(--ink-dim); text-decoration: none;
             border-bottom: 2px solid transparent; }
.sitenav a:hover { color: var(--ink); }
.sitenav a[aria-current="page"] { color: var(--accent); border-bottom-color: var(--accent); font-weight: 600; }

/* Small screens: the ten menu entries collapse behind a "hamburger" control so
   the menu stops eating the top of a phone screen (measured at 375 px wide:
   243 px of menu before, 42 px after). There is no JavaScript on this site -- a
   static page cannot fetch a manifest, see README.md -- so the toggle is a
   checkbox: the visitor clicks the label, the checkbox's :checked state opens
   the list through :has(), and CSS alone does the rest.
   The checkbox is nested INSIDE the label and clipped (class .sr-only), which
   is what lets the label carry the focus ring with :focus-within: a keyboard
   visitor tabs to the checkbox and sees the ring on the control they are
   actually looking at.
   :has() is the one modern selector here, so the collapse is gated on
   @supports -- a browser without it keeps the old always-expanded menu instead
   of losing the menu altogether. Wide screens are untouched either way. */
.navburger { display: none; }

@media (max-width: 760px) {
  .sitenav { padding: 0 12px; }
  @supports selector(:has(*)) {
    .navburger {
      display: block;
      padding: 11px 2px;
      font-size: 13px;
      font-weight: 600;
      color: var(--ink-dim);
      cursor: pointer;
      -webkit-user-select: none;
      user-select: none;
    }
    .navburger:hover { color: var(--ink); }
    .navburger:focus-within { outline: 2px solid var(--accent); outline-offset: 0; }
    .sitenav ul { display: none; }
    .sitenav:has(#navToggle:checked) ul { display: block; }
    .sitenav li + li { border-left: 0; border-top: 1px solid var(--line); }
    .sitenav a { display: block; }
  }
}

.layout {
  display: grid;
  grid-template-columns: 260px minmax(0, 1fr) 360px;
  gap: 14px;
  padding: 14px 22px 22px;
  align-items: start;
}

/* Sections 2 and 3 share a grid row and are held to the SAME HEIGHT: the plot
   card (page 2) or the two-plot column (page 3) sets the row height and the info
   panel stretches to it instead of stopping at its own content. The info column
   is 360px wide (was 300px) so the panel's rows stay readable at that height. The
   panel is a flex column, so its table takes the slack and its note stays at the
   bottom of the card rather than one gap opening under the table.
   The descendant combinator (not `>`) is deliberate: the combined page's harness
   applies this stylesheet by hand and reads descendant selectors only. */
.layout .plotwrap, .layout .plots, .layout .info { align-self: stretch; }
.info { display: flex; flex-direction: column; }
.info .props { flex: 1 1 auto; }

@media (max-width: 1100px) {
  .layout { grid-template-columns: 1fr; }
  /* one column: there is no row to share, so the panel keeps its own height */
  .layout .info .props { flex: 0 0 auto; }
}

.panel {
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 14px;
}

.field { margin-bottom: 14px; }
.field label { display: block; font-size: 12px; color: var(--ink-dim);
               margin-bottom: 5px; text-transform: uppercase; letter-spacing: .4px; }

/* every control on a page -- the dropdowns, the shape search box and the
   combined page's column-length text box -- is painted from these custom
   properties, so no control falls through to the UA default field rendering */
select, input[type="search"], input[type="text"] {
  width: 100%;
  background: var(--panel2);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: 7px;
  padding: 7px 9px;
  font: inherit;
}
select:focus, input:focus { outline: 2px solid var(--accent); outline-offset: 0; }
select[size] { padding: 4px; }
select option { padding: 2px 4px; }
select optgroup { color: var(--ink-dim); font-style: normal; font-weight: 600; }

.field.check { display: flex; align-items: flex-start; gap: 8px; }
.field.check input { margin-top: 2px; width: auto; }
.field.check label { margin: 0; text-transform: none; letter-spacing: 0;
                     font-size: 13px; color: var(--ink); }

.hint { margin-top: 5px; font-size: 12px; color: var(--ink-dim); }

.basis { margin: 16px 0 0; padding-top: 12px; border-top: 1px solid var(--line);
         display: grid; grid-template-columns: auto 1fr; gap: 3px 10px; font-size: 12px; }
.basis dt { color: var(--ink-dim); }
.basis dd { margin: 0; }

.plotwrap { padding: 6px; position: relative; min-height: 520px; }
.plot { width: 100%; height: 640px; }

.notice {
  margin: 10px; padding: 12px 14px; border-radius: 8px;
  background: #3a2a12; border: 1px solid #7a5a22; color: #ffd9a0; font-size: 13px;
}
.notice strong { color: #ffc06a; }

.info h2 { margin: 0 0 10px; font-size: 16px; font-weight: 600; }
.props { width: 100%; border-collapse: collapse; font-size: 13px; }
.props th { text-align: left; font-weight: 500; color: var(--ink-dim);
            padding: 4px 8px 4px 0; vertical-align: top; white-space: nowrap; }
.props td { padding: 4px 0; vertical-align: top; }
.props tr + tr th, .props tr + tr td { border-top: 1px solid rgba(43,54,68,.55); }
.small { font-size: 12px; color: var(--ink-dim); margin: 12px 0 0; }

/* the site footer is a SLIM BAR PINNED TO THE BOTTOM OF THE WINDOW (client
   request, round 10): on screen on every page, at every window size, with
   nothing to scroll to reach it. It is out of the flow, so `body` reserves its
   height (--footbar) and the bar can never cover the last row of content. The
   bar spans the content box -- fixed positioning resolves against the layout
   viewport, which excludes the classic scrollbar -- so the line inside it is
   centred on the content box on every page, page 4's reserved scrollbar gutter
   included (measured: text centre 632.4 against the content box centre 632.5 at
   a 1280 px window, and 792.4 against 792.5 at 1600 px). The bottom padding is
   trimmed to 7 px so the bar stays slim: it costs page 4 ~116 px, about five
   rows, where the old full footer block cost ~150 px. */
.foot {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 20;
  padding: 7px 22px;
  background: var(--panel);
  border-top: 1px solid var(--line);
  color: var(--ink-dim); font-size: 12px; text-align: center;
}
.foot p { margin: 0; }
.foot code { color: #b9c6d6; background: var(--panel2); padding: 1px 4px; border-radius: 4px; }
.foot p + p { margin-top: 8px; }
/* the site footer's one link (Privacy), in the same --accent the menu bar uses
   for the current page */
.foot a { color: var(--accent); text-decoration: none; }
.foot a:hover { text-decoration: underline; }

/* two-plot pages (combined axial + bending): the plot column stacks two panels,
   each with its own notice area. Built only from the custom properties above. */
.plots { display: grid; gap: 14px; align-content: start; }
.plots .plotwrap { min-height: 0; }
.plot-combined { height: 430px; }

.hint.bad { color: #ffc06a; }

/* page 4 (shape-properties.html): the W-shape property table and its pager.
   Values only -- that page computes nothing. Built only from the custom
   properties above; no new colour and no new font. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.shapesmain { padding: 14px 22px 22px; }

.shapespanel { display: flex; flex-direction: column; gap: 10px; }

.shapeslegend { margin: 0; font-size: 12px; line-height: 1.5; color: var(--ink-dim); }
.shapeslegend strong { color: var(--ink); font-weight: 600; }
.shapeslegend code { color: var(--ink); background: var(--panel2);
                     padding: 1px 4px; border-radius: 4px; }

/* the table region is capped at the measured available height by app-shapes.js
   (min-height) and never scrolls VERTICALLY: a page of rows always fits by
   construction. It DOES scroll sideways when the window is narrower than the
   table's own width (round 16 -- a phone), so all 21 columns stay reachable
   instead of being squeezed; on a desktop width there is no overflow and no
   scrollbar appears. app-shapes.js subtracts that scrollbar's height from the
   room it gives the rows, so the last row is never left under it. */
.shapesregion {
  overflow-x: auto;
  overflow-y: hidden;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel2);
}

/* the viewport scrollbar gutter is reserved on this page only, so the layout
   cannot shift when a page of rows makes the document taller -- without it the
   legend rewraps, the region moves down and the rows-per-page measurement made
   at load goes stale. Scoped to this page's <body>, so no other page moves. */
body.shapespage { overflow-y: scroll; }

.shapestable {
  width: 100%;
  border-collapse: collapse;
  font-size: 12px;
  font-variant-numeric: tabular-nums;
}
.shapestable th, .shapestable td { padding: 3px 6px; white-space: nowrap;
                                   text-align: right; }
.shapestable thead th { color: var(--ink-dim); font-weight: 600;
                        border-bottom: 1px solid rgba(43,54,68,.55); }
.shapestable thead tr:last-child th { font-weight: 400; font-size: 11px;
                                      border-bottom: 1px solid var(--line); }
.shapestable th.shape, .shapestable td.shape { text-align: left; }
.shapestable tbody tr + tr td { border-top: 1px solid rgba(43,54,68,.55); }
.shapestable tbody tr:nth-child(even) td { background: var(--panel); }

.pager { display: flex; align-items: center; gap: 10px; }
/* the pager buttons take the same treatment as the controls on the other
   pages: the same --panel2 background, --ink text, border, radius, padding,
   inherited font and --accent focus ring */
.pagebtn {
  background: var(--panel2);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: 7px;
  padding: 7px 9px;
  font: inherit;
  cursor: pointer;
}
.pagebtn:hover:not(:disabled) { color: var(--accent); border-color: var(--accent); }
.pagebtn:focus { outline: 2px solid var(--accent); outline-offset: 0; }
.pagebtn:disabled { color: var(--ink-dim); opacity: .55; cursor: default; }
.pageinfo { margin-left: auto; font-size: 13px; color: var(--ink-dim); }

/* page 5 (privacy.html): the privacy statement -- one panel of text, built only
   from the custom properties above; no new colour and no new font. */
.privacymain { padding: 14px 22px 22px; }

/* the statement runs the FULL WIDTH of the page (client request, round 10): no
   max-width, so the panel spans the content box and the line length is whatever
   the window gives it (the panel's own padding keeps the text off the edges). */
.privacy { max-width: none; }
.privacy h2 { margin: 0 0 10px; font-size: 16px; font-weight: 600; }
.privacy p { margin: 0; }
.privacy p + p { margin-top: 12px; }

/* page 6 (shear-capacity.html): the input card, the solution card and the
   incomplete-input pop-up. Built ONLY from the custom properties above -- no new
   colour and no new font. The selectors stay inside what the page harnesses can
   read (descendant combinators, sibling combinators spaced out, no pseudo-class
   beyond :hover / :focus, no child combinator), because the combined-page
   harness applies this whole stylesheet by hand. */
.shearmain { padding: 14px 22px 22px; display: grid; gap: 14px; align-items: start; }

.card h2 { margin: 0 0 6px; font-size: 16px; font-weight: 600; }
/* The sub-paragraph under a card's heading runs the full width of the card.
   It carried `max-width: 95ch`, which left a 1200 px card showing a ~570 px
   column of text down its left side. Client's call, 2026-10-15. */
.cardsub { margin: 0 0 14px; font-size: 12px; color: var(--ink-dim); }

.fieldgrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 18px; }
@media (max-width: 900px) { .fieldgrid { grid-template-columns: 1fr; } }

/* the stiffener-spacing box while the stiffener select says "None": it is not an
   input parameter of the check in that state, so it is disabled and dimmed
   rather than silently required */
.field.off { opacity: .55; }

.actions { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin-top: 4px; }
.actionbtn {
  background: var(--accent);
  color: var(--bg);
  border: 1px solid var(--accent);
  border-radius: 7px;
  padding: 9px 14px;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
.actionbtn:hover { opacity: .88; }
.actionbtn:focus { outline: 2px solid var(--accent); outline-offset: 2px; }

/* the solution card: empty state, verdict banner, then one block per step */
.solbody { font-size: 13px; }
.sol-empty {
  margin: 0; padding: 14px; border: 1px dashed var(--line); border-radius: 8px;
  background: var(--panel2); color: var(--ink-dim); font-size: 13px;
}
.solsub { margin: 8px 0 14px; font-size: 12px; color: var(--ink-dim); }

.verdict {
  margin: 0 0 6px; padding: 10px 12px; border-radius: 8px;
  border: 1px solid var(--line); background: var(--panel2); font-size: 14px;
}
.verdict strong { font-weight: 700; letter-spacing: .3px; }
.verdict.pass { border-color: var(--z2); color: var(--z2); }
.verdict.fail { border-color: var(--z1); color: var(--z1); }

.step { padding: 12px 0 2px; border-top: 1px solid var(--line); }
.step h4 { margin: 0 0 8px; font-size: 14px; font-weight: 600; }
.stepno {
  display: inline-block; margin-right: 8px; padding: 1px 7px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--panel2); color: var(--accent);
  font-size: 11px; font-weight: 600; letter-spacing: .4px; text-transform: uppercase;
}

.eq { display: grid; grid-template-columns: 104px minmax(0, 1fr); gap: 4px 12px;
      padding: 6px 0; border-top: 1px solid rgba(43,54,68,.45); }
.eq.ans { border-top: 1px solid var(--line); }
.eqlabel { padding-top: 2px; color: var(--ink-dim); font-size: 11px;
           text-transform: uppercase; letter-spacing: .4px; }
.eqbody { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
          font-size: 13px; overflow-wrap: anywhere; }
.eq.ans .eqbody { color: var(--ink); }

.sollist { width: 100%; margin: 0 0 12px; border-collapse: collapse; font-size: 13px; }
.sollist caption { padding: 0 0 6px; text-align: left; font-size: 12px; color: var(--ink-dim); }
.sollist th { padding: 4px 10px 4px 0; text-align: left; font-weight: 500;
              color: var(--ink-dim); vertical-align: top; white-space: nowrap; }
.sollist td { padding: 4px 0; vertical-align: top; }
.sollist tr + tr th, .sollist tr + tr td { border-top: 1px solid rgba(43,54,68,.55); }

/* the clause a step's formula came from */
.cite { margin: 8px 0 0; padding-left: 10px; border-left: 2px solid var(--line);
        font-size: 12px; color: var(--ink-dim); }

/* the incomplete-input pop-up. z-index is above the pinned footer bar (20). */
.modalwrap {
  position: fixed; inset: 0; z-index: 40; display: flex; align-items: center;
  justify-content: center; padding: 22px; background: rgba(16,20,26,.72);
}
.modalwrap[hidden] { display: none; }
.modal {
  width: 100%; max-width: 640px; max-height: 80vh; overflow: auto;
  background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
  padding: 18px;
}
.modal h3 { margin: 0 0 8px; font-size: 16px; font-weight: 600; color: var(--z1); }
.modal p { margin: 0 0 12px; font-size: 13px; color: var(--ink-dim); }
.missing { margin: 0 0 14px; padding-left: 18px; font-size: 13px; }
.missing li + li { margin-top: 6px; }

/* page 11 (profile.html): the professional record -- portrait card, summary,
   core competencies, industry and professional experience, selected projects,
   codes and education. Built ONLY from the custom properties above: no new
   colour and no new font. `.job` is the dated entry block -- the ten posts and
   the two degrees use it; only the posts carry a `.dates` span, the degrees
   carry `.note-inline`. The selectors stay inside what the page harnesses can
   read (descendant combinators, sibling combinators spaced out, no child
   combinator, no pseudo-class beyond :hover / :focus), because the combined-page
   harness applies this whole stylesheet by hand. */
.profilemain { padding: 14px 22px 22px; display: grid; gap: 14px; align-items: start; }
.profilemain h2 { margin: 0 0 10px; font-size: 16px; font-weight: 600; }
.profilemain h3 { margin: 0; font-size: 14px; font-weight: 600; }
.profilemain p { margin: 0; }
.profilemain p + p { margin-top: 10px; }

.profilecard { display: flex; flex-wrap: wrap; gap: 18px; align-items: flex-start; }
.portrait { display: block; width: 190px; height: auto;
            border: 1px solid var(--line); border-radius: 10px; }
.profileintro { flex: 1 1 340px; }
.profileintro h2 { margin: 0 0 2px; font-size: 20px; }
.role { margin: 0 0 12px; color: var(--ink-dim); font-size: 13px; }
.quote { margin: 12px 0 0; color: var(--ink-dim); font-size: 12px; font-style: italic; }
.note-inline { color: var(--ink-dim); font-size: 12px; }

.props.contact th { width: 96px; }
.props.industry th, .props.industry td { white-space: nowrap; }
.props.industry td { text-align: right; }
.props.industry tr.total th, .props.industry tr.total td { color: var(--ink); font-weight: 600; }
.props.projects th, .props.projects td { white-space: normal; }
.props.projects th { padding-right: 14px; }
.props.projects td { color: var(--ink-dim); }

.skills { margin: 0; padding-left: 18px; column-gap: 30px; }
.skills li { margin: 0 0 5px; }
@media (min-width: 761px) { .skills { columns: 2; } }

.job { padding-top: 12px; }
.job + .job { margin-top: 14px; border-top: 1px solid var(--line); }
.jobhead { display: flex; flex-wrap: wrap; gap: 2px 12px; align-items: baseline; }
.dates { color: var(--ink-dim); font-size: 12px; }
.jobtitle { margin: 3px 0 8px; color: var(--ink-dim); font-size: 12px;
            text-transform: uppercase; letter-spacing: .4px; }
.job ul { margin: 0; padding-left: 18px; }
.job li { margin: 0 0 5px; }

@media (max-width: 760px) { .portrait { width: 150px; } }
.mkey { color: var(--ink); font-weight: 600; }

/* round 18: the site mark in the menu bar, and the two centred stage pages
   (index.html's landing, design.html's hub, the loading/concrete/misc section
   pages). Built ONLY from the
   custom properties above: no new colour and no new font. The selectors stay
   inside what the page harnesses can read (descendant and sibling combinators
   only -- no child combinator, no pseudo-class beyond :hover / :focus), because
   the combined-page harness applies this whole stylesheet by hand. */
.navmark { display: flex; align-items: center; }
.navmark a { padding: 8px 12px 8px 0; line-height: 0; }
.navmark img { display: block; width: 22px; height: 24px; }
/* no divider between the mark and the first worded entry */
.navmark + li { border-left: 0; }

/* The stage: the page's whole content is one big line -- and, on the landing
   page, the quote under it -- held in the middle of the window, the way the
   desktop app's new-chat view holds its greeting. `body` is the flex column,
   `main` takes the slack between the menu bar and the footer bar and centres
   its content in it, so the block stays centred at every window height,
   including when the menu bar wraps to two lines on a narrow screen. */
body.stagepage { min-height: 100vh; display: flex; flex-direction: column; }
body.stagepage .stagemain {
  flex: 1 1 auto; display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  padding: 22px; text-align: center;
}
.stagetitle { margin: 0; font-size: 40px; font-weight: 600; letter-spacing: .2px; }
.stagequote { margin: 26px 0 0; max-width: 58ch; color: var(--ink-dim);
              font-size: 15px; font-style: italic; }

/* ROUND 29 -- THE LANDING PAGE'S TITLE BLOCK. Ian's answer to the round-29
   design PDF, verbatim: "I like B, let's impliment that one." Round 18's centred
   hero is untouched -- the name and the Archimedes line still sit in the middle
   of the window, and `main` is still the flex column that holds them there. What
   the page gains is the treatment an engineering drawing's title block gets:
     .stagemark   the site mark, above the name
     .stagesub    the engineer's own title, the line the profile page carries
     .stagerule   a dimension rule -- end ticks, hairline, accent centre tick
     .stagecode   the three standards the site's sections answer to
     .stagelinks  one hairline-separated row of those four sections
   No new colour and no new font: the rule, the ticks and the separators are
   --line and --accent, and the code line is the page's own dim ink. The paper
   texture and the band that frames the hero are mixed from --line and --panel
   with color-mix, so a browser without color-mix draws no texture rather than a
   wrong one. Every rule here is a descendant or sibling selector of the stage,
   plus :hover/:focus -- the same rule the tiles follow. */
.stagemark { margin: 0 0 24px; line-height: 0; }
.stagesub { margin: 12px 0 0; color: var(--ink-dim); font-size: 13px; }
.stagerule { width: 210px; height: 15px; margin: 26px 0 0;
             /* the dimension rule is four background layers -- the accent centre
                tick, the hairline, and a tick at each end. Drawn this way rather
                than with ::before/::after on purpose: the stylesheet had never
                used a pseudo-element selector before this pass, and
                combined_page_harness.js refuses to read one ("selector this
                checker cannot read") -- a guard that has kept every rule in this
                file resolvable, and not worth weakening for one hairline. */
             background-image:
               linear-gradient(var(--accent), var(--accent)),
               linear-gradient(var(--line), var(--line)),
               linear-gradient(var(--line), var(--line)),
               linear-gradient(var(--line), var(--line));
             background-size: 1px 15px, 100% 1px, 1px 15px, 1px 15px;
             background-position: center center, center center, left center, right center;
             background-repeat: no-repeat; }
.stagecode { margin: 26px 0 0; color: var(--ink-dim); font-size: 11px;
             letter-spacing: 1.2px;
             font-family: ui-monospace, SFMono-Regular, Menlo, Consolas,
                          "Liberation Mono", monospace; }
.stagelinks { list-style: none; display: flex; flex-wrap: wrap; justify-content: center;
              margin: 34px 0 0; padding: 0; }
.stagelinks li { margin: 0 11px; }
.stagelinks li + li { border-left: 1px solid var(--line); }
.stagelinks a { color: var(--ink); font-size: 13px; text-decoration: none; }
.stagelinks a:hover, .stagelinks a:focus { color: var(--accent); text-decoration: underline; }
/* The texture and the band are painted on `body`, not on a pseudo-element of the
   stage: a ::before inside the flex container would be laid out as a flex item
   and would push the hero off centre. Two grid layers over one soft band -- the
   band's stops are percentages of the body, which puts its middle within a few
   pixels of the hero's at every window height. */
body.stagepage {
  background-image:
    linear-gradient(to bottom, transparent 30%,
                    color-mix(in srgb, var(--panel) 60%, transparent) 45%,
                    color-mix(in srgb, var(--panel) 60%, transparent) 62%,
                    transparent 77%),
    linear-gradient(color-mix(in srgb, var(--line) 30%, transparent) 1px, transparent 1px),
    linear-gradient(90deg, color-mix(in srgb, var(--line) 30%, transparent) 1px, transparent 1px);
  background-size: 100% 100%, 40px 40px, 40px 40px;
  background-repeat: no-repeat, repeat, repeat;
}
@media (max-width: 760px) {
  .stagetitle { font-size: 28px; }
  .stagequote { font-size: 14px; }
  .stagemark { margin-bottom: 18px; }
  .stagemark img { width: 34px; height: 37px; }
  .stagerule { width: 170px; }
  .stagelinks { margin-top: 28px; }
  .stagelinks li { margin: 0 9px; }
}

/* The hub's and the concrete section's tiles (round 19). `main` is already a
   centring flex column, so the grid is the one child it centres; each tile is a
   single link, so the whole tile is clickable. The grid is two columns on a
   desktop and one column below the site's own 760px breakpoint, where a 2-up
   grid would leave the labels no room. Same rule as above: built only from the
   custom properties already defined, and only descendant/sibling selectors plus
   :hover/:focus.

   ROUND 23 -- THE TILE RULES ARE SCOPED TO .cardgrid, and that scoping is the
   whole fix. The class `card` is worn by two different things: these tiles, and
   the input and solution sections of the five step-by-step pages
   (`<section class="panel card" id="inputCard">`). An unscoped `.card { display:
   flex; justify-content: center; text-align: center; min-height: 132px;
   font-size: 20px }` therefore turned those two sections into a centred flex
   ROW -- heading, explanatory text, the field grid and the Solve button side by
   side -- instead of the vertical stack the markup is written as. Every rule
   here is now a descendant of the grid the tiles actually live in, so the
   sections keep their own layout: `.panel` for the box, `.card h2`, `.cardsub`,
   `.fieldgrid` and `.actions` for what stacks inside it. `combined_page_harness`
   resolves the paint of both to keep this from coming back. */
.cardgrid { list-style: none; margin: 0; padding: 0; display: grid;
            grid-template-columns: repeat(2, minmax(210px, 300px)); gap: 16px; }
.cardgrid li { margin: 0; }
/* a landing page with a single tile (the concrete section's, round 23):
   the shared grid is 2-up for the hub's four, which would leave one tile
   sitting in the left column of an empty pair */
.cardgrid.single { grid-template-columns: minmax(210px, 300px); margin-top: 18px; }
.cardgrid .card { display: flex; align-items: center; justify-content: center;
        min-height: 132px; padding: 18px; text-align: center;
        background: var(--panel); border: 1px solid var(--line); border-radius: 10px;
        color: var(--ink); font-size: 20px; font-weight: 600; letter-spacing: .2px;
        text-decoration: none; }
.cardgrid .card:hover { background: var(--panel2); border-color: var(--accent); color: var(--accent); }
.cardgrid .card:focus { outline: 2px solid var(--accent); outline-offset: 2px; }
@media (max-width: 760px) {
  .cardgrid { grid-template-columns: 1fr; gap: 12px; width: 100%; max-width: 420px; }
  .cardgrid .card { min-height: 96px; font-size: 18px; }
}

/* Concrete Design section: the ASTM standard reinforcing bar table
   (Concrete Design/rebar-properties.html), the page the design hub's Concrete
   Design card opens. Same visual language as the W-shape properties table --
   the same two header rows, the same right-aligned tabular numerals, the same
   zebra rows and bordered region -- built ONLY from the custom properties
   above: no new colour and no new font. The selectors stay inside descendant
   and sibling combinators plus :hover / :focus (the W-shape table's own
   nth-child zebra rule is the one exception, kept because it is what the
   neighbouring table already uses), because the combined-page harness applies
   this whole stylesheet by hand. */
.rebarsmain { padding: 14px 22px 22px; }

.rebarspanel { display: flex; flex-direction: column; gap: 10px; }

.rebarslegend { margin: 0; font-size: 12px; line-height: 1.5; color: var(--ink-dim); }
.rebarslegend strong { color: var(--ink); font-weight: 600; }
.rebarslegend code { color: var(--ink); background: var(--panel2);
                     padding: 1px 4px; border-radius: 4px; }

/* the table is 4 columns of short values, so it fits every window the site
   targets; the region still scrolls sideways rather than squeezing if it ever
   does not */
.rebarsregion {
  overflow-x: auto;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--panel2);
}

.rebarstable {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
}
.rebarstable th, .rebarstable td { padding: 5px 10px; white-space: nowrap;
                                   text-align: right; }
.rebarstable thead th { color: var(--ink-dim); font-weight: 600;
                        border-bottom: 1px solid rgba(43,54,68,.55); }
.rebarstable thead tr:last-child th { font-weight: 400; font-size: 11px;
                                      border-bottom: 1px solid var(--line); }
.rebarstable th.size, .rebarstable td.size { text-align: left; }
.rebarstable tbody tr + tr td { border-top: 1px solid rgba(43,54,68,.55); }
.rebarstable tbody tr:nth-child(even) td { background: var(--panel); }

.rebarsmeta { margin: 0; font-size: 12px; color: var(--ink-dim); }
.rebarssource { margin: 4px 0 0; padding-top: 10px;
                border-top: 1px solid var(--line);
                font-size: 12px; color: var(--ink-dim); }

/* --- the concrete section's calculator pages (round 25) -------------------
   The same two-card column the steel check pages use, under a name of the
   section's own so a concrete page never reads `.shearmain`. The field-group
   line is the only addition to the card: it names a block of the input card
   (section and materials, demand, reinforcement, development conditions) and
   spans both columns of the grid. No colour and no font of its own -- it uses
   the tokens this stylesheet already defines. */
.concretemain { padding: 14px 22px 22px; display: grid; gap: 14px; align-items: start; }

.fieldgroup {
  grid-column: 1 / -1;
  margin: 6px 0 0;
  padding-bottom: 4px;
  border-bottom: 1px solid var(--line);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-dim);
}

/* the inline mark a step row carries when it reports a limit check (round 25):
   the same two colours the verdict banner uses, so a reader scanning the worked
   steps sees which line failed without reading the sentence. */
.passmark { color: var(--z2); font-weight: 600; }
.failmark { color: var(--z1); font-weight: 700; }

/* the Loads section (round 26). Its landing page is the hub pattern, unchanged:
   `body.stagepage`, `.stagemain`, the `.cardgrid` of tiles. Its seven topic pages
   are stage pages too, but they carry more than one line -- a scope, the clauses
   behind it and its build status -- and `.stagemain` centres its content, which is
   right for a one-line stage page and wrong for a list. `.loadcol` is the
   left-aligned, width-capped column those pages put inside `.stagemain`; it is
   still centred as a block in the stage, and the title and the standfirst stay
   centred inside it so the page opens like the other stage pages.

   The topic pages also take `body.loadpage`, which stops the stage centring them
   vertically: a stage page is one line and looks right in the middle, a page you
   read starts at the top.

   Built only from the custom properties above; no new colour and no new font, and
   no selector here is worn by any other page. */
.loadcol { width: 100%; max-width: 760px; text-align: left; }
.loadcol .stagetitle, .loadcol .stagequote { text-align: center; }
.loadcol .panel { margin-top: 18px; }
.loadcol h2 { margin: 0 0 8px; font-size: 16px; font-weight: 600; }
.loadcol h2 + p { margin-top: 0; }
.loadcol p { margin: 0 0 10px; }
.loadcol p:last-child, .loadcol ul:last-child { margin-bottom: 0; }
.loadcol ul { margin: 0 0 10px; padding-left: 20px; }
.loadcol li { margin: 0 0 5px; }
body.loadpage .stagemain { justify-content: flex-start; padding-top: 28px; }

/* --- the Loads section's calculator pages (round 27) ----------------------
   Load Combinations is the first page in the section that computes rather than
   states, so it wears the same two-card column the steel check pages and the
   concrete procedure pages wear -- under a name of the section's own, so a
   Loads page never reads `.concretemain` or `.shearmain`. The declaration is
   the same one; the field groups, the panel, the empty state, the worked steps
   and the verdict banner are all the shared rules above, unchanged. No new
   colour and no new font.
   `minmax(0, 1fr)` and the zero minimum on the panels are load-bearing: a
   worked step or a combination table has a min-content width far wider than a
   phone, and a plain `auto` grid column grows to fit its widest item, which
   drags the INPUT card out to the same width and gives the whole page a
   horizontal scrollbar at 375 px. The table gets its own scroll box instead. */
.loadmain { padding: 14px 22px 22px; display: grid; gap: 14px; align-items: start;
            grid-template-columns: minmax(0, 1fr); }
/* a descendant selector, not a child one: the page harness that reads this
   stylesheet supports the descendant combinator only and throws on `>`, `+`
   and `~` rather than mis-evaluating them. A `.panel` is never nested inside
   another here, so the two are equivalent. */
.loadmain .panel { min-width: 0; }
/* position: relative is load-bearing. .sr-only is position: absolute, so with no
   positioned ancestor its containing block is the initial containing block --
   it lays out at its static position (inside a table that is far wider than the
   screen) and escapes this wrapper's clip, stretching the whole document. The
   Dead Load row table did exactly that: a 406 px document in a 305 px viewport.
   Making the wrapper positioned contains the labels and lets overflow-x clip
   them like everything else. */
.loadmain .tablewrap { position: relative; max-width: 100%; overflow-x: auto; }
.loadmain .eqbody, .loadmain .sollist td { overflow-wrap: anywhere; }
/* the repeating element table of a page whose card takes a list (dead load, ice).
   A descendant selector, for the same reason as the rule above. */
.loadmain .rowtable { width: 100%; border-collapse: collapse; }
.loadmain .rowtable th, .loadmain .rowtable td { padding: 4px 5px; text-align: left;
  vertical-align: middle; }
.loadmain .rowtable thead th { font-size: 12px; font-weight: 600; color: var(--ink-dim);
  border-bottom: 1px solid var(--line); white-space: nowrap; }
.loadmain .rowtable tbody tr + tr td { border-top: 1px solid var(--line); }
.loadmain .rowtable input, .loadmain .rowtable select { width: 100%; }
.loadmain .rowdel { background: none; border: 1px solid var(--line); border-radius: 7px;
  color: var(--ink-dim); font: inherit; line-height: 1; padding: 3px 8px; cursor: pointer; }
.loadmain .rowdel:hover { color: var(--ink); border-color: var(--accent); }
.actionbtn.ghost { background: none; color: var(--ink); border: 1px solid var(--line); }
.actionbtn.ghost:hover { border-color: var(--accent); }

/* --- the Miscellaneous section's calculators (round 29) -------------------
   The five tool pages became real calculators, so the stage-page column they
   wore while they were scaffolding (`.misccol`, `body.miscpage`) is gone with
   them: the section's landing page is the only page here that is still a stage,
   and it wears the hub's own `.cardgrid`. What a calculator page needs is what
   the Loads calculators need -- this section's own name for the body of the
   page, and the same load-bearing declarations: `minmax(0, 1fr)` with a zero
   minimum on the panels, so a wide solution table gets its own scroll box
   instead of dragging a horizontal scrollbar across the page, and
   `position: relative` on the table wrapper, so a `.sr-only` label inside it
   cannot escape the wrapper and stretch the document. Declared under this
   section's own name (`.miscmain`), never borrowed from `.loadmain` -- the same
   rule the other section columns follow (`.shearmain`, `.rebarsmain`,
   `.concretemain`). Built only from the custom properties above: no new colour,
   no new font.
   NOTE, and fixed in round 30 on Ian's call: the `.loadmain` block this mirrors
   painted its table headers and its row-delete button with `var(--muted)`, and
   `--muted` was defined NOWHERE in this stylesheet -- so those two declarations
   were dropped by every browser and the colours fell back to inherited ink. Both
   now read `var(--ink-dim)`, the token that exists and means the same thing, and
   the undefined token is gone from the site. The chrome harness now
   asserts the invariant this defect was a breach of: every custom property the
   stylesheet READS is one the stylesheet DEFINES. */
.miscmain { padding: 14px 22px 22px; display: grid; gap: 14px; align-items: start;
            grid-template-columns: minmax(0, 1fr); }
.miscmain .panel { min-width: 0; }
.miscmain .tablewrap { position: relative; max-width: 100%; overflow-x: auto; }
.miscmain .eqbody, .miscmain .sollist td { overflow-wrap: anywhere; }
.miscmain .rowtable { width: 100%; border-collapse: collapse; }
.miscmain .rowtable th, .miscmain .rowtable td { padding: 4px 5px; text-align: left;
  vertical-align: middle; }
.miscmain .rowtable thead th { font-size: 12px; font-weight: 600; color: var(--ink-dim);
  border-bottom: 1px solid var(--line); white-space: nowrap; }
.miscmain .rowtable tbody tr + tr td { border-top: 1px solid var(--line); }
.miscmain .rowtable input, .miscmain .rowtable select { width: 100%; }
.miscmain .rowdel { background: none; border: 1px solid var(--line); border-radius: 7px;
  color: var(--ink-dim); font: inherit; line-height: 1; padding: 3px 8px; cursor: pointer; }
.miscmain .rowdel:hover { color: var(--ink); border-color: var(--accent); }
