
/* ── CONSOLE CHROME ─────────────────────────────────────────────────── */
.console {
  --brand-bar: #14213D;
  --brand-bar-raised: #2B3D66;
  --brand-bar-line: #3B4C73;
  --brand-bar-ink: #C7D2EA;
  --brand-mark: #A9B3F5;
  /*
   * The navy is painted here and the bar is centred inside it, the same way
   * app.css does it — so the product mark lines up with the report's own
   * masthead below rather than with the window's edge.
   */
  padding: 0; background: var(--brand-bar);
}
/*
 * The spec's bar, plus the three declarations a narrow window needs.
 *
 * app.css draws this as one unwrappable 64px row with a 260px switcher and a
 * signed-in address in it. That is fine on the console's own pages, which are
 * a table in a 1240px column. Over a REPORT it was not: measured at a 390px
 * phone viewport, the bar alone pushed the document's scrollWidth to 800px,
 * so the whole report — which fits 390 on its own, because .tabs scrolls
 * inside itself — became horizontally scrollable and shrank to fit. The bar
 * was the only cause; the same page without it measured 390.
 *
 * So the row may wrap, the height becomes a floor rather than a fixed value
 * (identical at every width where the row fits: the tallest thing in it is
 * the 36px switcher), and the switcher gives up its 260px when 260px is more
 * than there is. Written here rather than in app.css because the console's
 * own pages are not what was measured; that one is worth raising on its own.
 */
.console .appbar {
  min-height: 64px; background: var(--brand-bar); color: #fff;
  display: flex; align-items: center; flex-wrap: wrap; gap: 16px;
  position: relative;
  /*
   * No radius: the gallery draws app-bar as a boxed demo, and that frame's
   * card radius was copied into a bar that spans the window and has no
   * corners to round. app.css had the same thing and was fixed; this is the
   * second copy of these rules (backlog item 164), which is why it had to be
   * fixed twice.
   */
  border-radius: 0;
  /*
   * The bar spans the window; the body below it does not — the same change
   * app.css took for the console's own bar, arriving here second.
   *
   * It was capped at --frame and centred, so a report served inside the
   * console drew a bar that stopped at the body's width while the console's
   * own spanned the screen: two bars of different widths either side of one
   * navigation. That is backlog item 164 — these rules exist twice — and it
   * is why this had to be fixed twice.
   *
   * The gutter alone holds the contents off the edge, which is what keeps the
   * mark above the masthead's own mark rather than 230px inside it.
   */
  /*
   * One gutter token, two sheets that spell it differently.
   *
   * The console calls it --frame-pad (48px, and 32px below 1280px); the
   * report calls it --gutter (--space-8, which is also 48px). Neither sheet
   * defines the other's name, so the fallback picks whichever is present:
   * the console keeps its breakpoint, and the report resolves to its gutter.
   */
  padding: 8px var(--chrome-gutter);
}
.console .appbar-word { font-weight: 700; font-size: 16px; letter-spacing: -0.01em; }
.console .appbar-word span { color: var(--brand-mark); }
/* Staging only (spec S8): loud on purpose, so a staging page is never read as production. */
.console .appbar-env {
  font-size: 11px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;
  color: #3a2600; background: #f5b83d; border-radius: var(--radius); padding: 3px 8px;
}
.console .appbar-divider { width: 1px; height: 28px; background: var(--brand-bar-line); }
.console .appbar-admin {
  font-size: 11px; font-weight: 600; letter-spacing: 0.08em;
  color: var(--brand-bar-ink); border: 1px solid var(--brand-bar-line);
  border-radius: var(--radius); padding: 3px 8px;
}
/* min-width: 0 and a wrap rule because the text in it is an email address
   somebody else chose: a long one is one unbreakable word, and without these
   it widens the row rather than wrapping inside it. */
.console .appbar-user {
  display: flex; align-items: center; gap: 8px; min-width: 0;
  font-size: 13px; color: var(--brand-bar-ink); overflow-wrap: anywhere;
}
.console .appbar-avatar {
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--brand-bar-raised); color: #fff;
  font-size: 12px; font-weight: 600;
  display: flex; align-items: center; justify-content: center;
}
.console .appbar-nav {
  font-size: 13px; font-weight: 500; color: var(--brand-bar-ink); text-decoration: none; padding: 4px 4px;
}
.console .appbar-nav:hover { color: #fff; text-decoration: underline; }
.console .appbar-out {
  font-size: 13px; font-weight: 500; color: #fff; text-decoration: none;
  border: 1px solid var(--brand-bar-line); border-radius: var(--radius); padding: 4px 12px;
}
.console .appbar-out:hover { color: #fff; background: var(--brand-bar-raised); text-decoration: none; }
.console .switcher {
  height: 36px; min-width: min(260px, 100%); padding: 0 12px;
  border: 1px solid var(--brand-bar-line); background: var(--brand-bar-raised);
  border-radius: var(--radius); font: 500 14px var(--font-sans); color: #fff;
  display: flex; align-items: center; gap: 12px; cursor: pointer;
}
.console .switcher[aria-expanded="true"] { border-color: var(--brand-mark); }
.console .switcher:focus-visible { outline: 2px solid var(--brand-mark); outline-offset: 2px; }
/*
 * Hover and active. These were app.css's alone, so the same bar drawn on a
 * report page did not respond to a pointer at all — the kind of difference a
 * second copy produces and neither side can see.
 */
.console .switcher:hover, .console .switcher:active { background: var(--brand-bar-line); color: #fff; }
.console .switcher-k {
  font-size: 11px; font-weight: 600; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--brand-bar-ink);
}
.console .switcher-value { flex-grow: 1; text-align: left; }
/*
 * Over the sticky masthead, under the Cookee drawer, and inside the window.
 *
 * Three departures from app.css's rule, all measured on a rendered report page
 * rather than reasoned about.
 *
 * z-index: the bar is not sticky and the masthead below it is, so the masthead
 * paints across anything the bar drops onto the page unless the menu outranks
 * it. Opened the switcher and the client's name was on top of the menu.
 *
 * top: app.css writes 58px, which is a 64px bar less a 6px overlap. The bar
 * above wraps on a narrow window, so 58px puts the menu across the bar's own
 * second row. calc(100% - 6px) is the same 6px against whatever the bar is.
 *
 * left and width: 210 + 380 is 590px from the bar's origin and nothing clips
 * it, so an OPEN menu put the document's scrollWidth at 590 on a 390px and on
 * a 360px viewport — the same page-widening the bar's own flex-wrap had just
 * removed, half a rule away. The menu now sits where app.css puts it whenever
 * there is room for it and slides in against the right edge when there is not.
 */
.console .switcher-menu {
  position: absolute; top: calc(100% - 6px);
  left: min(210px, max(0px, 100% - 380px)); width: min(380px, 100%);
  z-index: 30; padding: 8px;
  background: var(--page); color: var(--ink-primary);
  border: 1px solid var(--card-border); border-radius: var(--radius-card);
  box-shadow: 0 12px 32px rgba(20, 33, 61, 0.18);
}
.console .switcher-menu ul { list-style: none; margin: 8px 0; padding: 0; }
/* The whole row is the link — picking a client is navigation, so the target
   is the row a pointer is already over, not the name inside it. */
.console .switcher-menu li { border-radius: var(--radius); }
.console .switcher-option {
  display: flex; align-items: center; gap: 12px;
  padding: 8px 12px; border-radius: var(--radius);
  color: inherit; text-decoration: none;
}
.console .switcher-option:hover { background: var(--inset); }
.console .switcher-option:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.console .switcher-option[aria-current] { background: var(--accent-fill); }
/* Clicking away closes the menu: a link over the viewport, under the menu and
   over nothing else. No colour — what it does is dismiss the thing on top of
   it, and a visible sheet would say the page is busy. */
.console .switcher-scrim { position: fixed; inset: 0; z-index: 29; }
/*
 * 'hidden' means hidden, whatever else is set.
 *
 * The switcher menu is in the DOM on every console page now and closed with
 * the hidden ATTRIBUTE, which is what keeps it out of the tab order and the
 * accessibility tree - a menu closed with CSS alone is one a keyboard user
 * tabs into off-screen. The UA stylesheet gives [hidden] display:none, but
 * one 'display: flex' on .switcher-menu would silently outrank it and put a
 * reachable menu in everybody's markup. Stated, so that cannot happen by
 * accident.
 */
.console [hidden] { display: none !important; }
.console .switcher-menu .grade { width: 24px; height: 24px; font-size: 13px; border-radius: var(--radius); }
.console .switcher-search {
  width: 100%; height: 36px; padding: 0 12px;
  border: 1px solid var(--rule); border-radius: var(--radius); font: 14px var(--font-sans);
}
.console .switcher-foot {
  border-top: 1px solid var(--hair); padding: 8px 12px 2px;
  display: flex; justify-content: space-between; font-size: 13px;
}
.console .switcher-foot a { color: var(--accent); text-decoration: none; }
.console .switcher-foot a:hover { text-decoration: underline; }
.console .cookee-title { flex-grow: 1; }
.console .muted { font-size: 12px; color: var(--ink-secondary); }
/* The spec's running pill, scoped to the menu inside the console: .running is
   a word this sheet does not otherwise use, and a component that only ever
   appears in one place should not start claiming it globally. */
.console .switcher-menu .running {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 12px; font-weight: 600;
  color: var(--accent-hover); background: var(--accent-fill);
  border-radius: var(--radius); padding: 2px 8px;
}
.console .switcher-menu .running::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%; background: var(--accent);
}


/* ── CLIENT BAND ────────────────────────────────────────────────────── */
/*
 * The gutter the chrome indents by, on one ladder for both sheets.
 *
 * The app bar and the client band have to start on the same vertical line -
 * app.css says so at length about --frame-pad, and it was true at exactly one
 * window width. The console narrowed its bar to 32px below 1280 and 16px
 * below 900 while the band stayed at 48; the report narrowed its CONTENT
 * (.app-shell) on the same ladder while its own masthead stayed at 48. So
 * each sheet had a width at which its chrome and its content disagreed, and
 * below 1280 the two sheets disagreed with each other as well - which is what
 * walking from a console page to a report at 1100px looked like.
 *
 * --frame-pad first, because the console defines it and already walks this
 * ladder; the fallbacks are what the report gets, and --space-7/--space-4 are
 * the same two tokens its own .app-shell narrows to at these widths.
 *
 * On :root, and in this block rather than the bar's: every rule in APP_BAR_CSS
 * is scoped under .console so the report's document cannot pick it up, and a
 * ladder declared there could not be read by the band. A custom property
 * paints nothing, so it is not something the print block has to override.
 */
:root {
  --chrome-gutter: var(--frame-pad, var(--gutter));
}
@media (max-width: 1279px) {
  :root { --chrome-gutter: var(--frame-pad, var(--space-7)); }
}
@media (max-width: 899px) {
  :root { --chrome-gutter: var(--frame-pad, var(--space-4)); }
}
.masthead {
  background: var(--page); border-bottom: 1px solid var(--card-border);
  padding: var(--space-5) calc((100% - min(100%, var(--frame))) / 2 + var(--chrome-gutter)) 0;
  position: sticky; top: 0; z-index: 10;
}
/* The console variant's actions, on the identity row the brand row left. */
.masthead-back { font-size: 13px; margin-bottom: 2px; }
.masthead-back a { color: var(--accent); text-decoration: none; }
.masthead-back a:hover { text-decoration: underline; }
.masthead-runs { display: flex; align-items: center; gap: 8px; margin: 0; }
.masthead-runs label { font-size: 13px; color: var(--ink-secondary); }
.masthead-runs select {
  height: 40px; padding: 0 10px; font: 400 14px var(--font-sans);
  border: 1px solid var(--card-border); border-radius: var(--radius);
  background: var(--page); color: var(--ink-primary);
}
/*
 * The submit button is the no-JavaScript path, so it is reachable by keyboard
 * and out of the way otherwise — not display:none, which would take it off
 * the tab order and leave that reader with a selector that does nothing.
 */
.masthead-runs-go {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.masthead-runs-go:focus { position: static; width: auto; height: 40px; margin: 0; clip: auto; }
.masthead-actions { display: flex; align-items: center; gap: 12px; flex: none; }
/*
 * The letterhead (the user, 2026-10-08, option B of the PDF header mockups):
 * the mark and wordmark centred, with a 1px navy line under them, and no fill.
 *
 * The navy is a literal hex rather than a token, as the spec's tokens table
 * directs: the console's brand variables are "never emitted into a report",
 * and the report masthead draws the mark in this exact colour on white. It is
 * written once, on the row — the wordmark and the line both take it as
 * currentColor — and report-page.test.ts counts every occurrence in a
 * rendered report against that budget, so a copy anywhere else fails.
 */
.masthead-brand { display: flex; align-items: center; justify-content: center; gap: 12px; padding-bottom: 12px; border-bottom: 1px solid; color: #14213D; }
.masthead-brand b { color: inherit; font-size: 22px; font-weight: 700; letter-spacing: -.01em; }
.masthead-brand b span { color: var(--accent); }
.masthead-id { display: flex; align-items: center; gap: 18px; margin: 16px 0; }
/*
 * A fixed slot, which is what retired .a1-logo--img's native sizing and
 * .a1-logo--captured's ceiling (item 129: a 1200px og:image took the card
 * and left the title stacked one character per line). object-fit: contain
 * inside a fixed box bounds an upload and a capture alike, without either
 * the caller or the markup having to know which it is.
 */
/*
 * As wide as the mark, up to 160px. A fixed width left dead space between a
 * narrow logo and the client's name, and the console's slot had the same —
 * both are sized to the mark now so one change does not part them.
 */
.masthead-logo { max-width: 160px; height: 56px; flex-shrink: 0; object-fit: contain; object-position: left center; }
/* overflow-wrap: anywhere is the spec's own answer to the long-client-name
   state: a 100-character legal entity name wraps rather than widening the
   row past the viewport. */
.masthead-name { margin: 0; font-size: 26px; font-weight: 600; overflow-wrap: anywhere; }
/*
 * One line, 1.5 of it, whatever is inside.
 *
 * Without it the row's height came from the union of the strut and every
 * inline box in it, and the mono container id does not sit on the same
 * metrics as the prose around it: the filled line measured 21.8px against a
 * reserved empty one at 21.0, so the two bands still differed by 0.8px.
 *
 * A length, not a ratio. A unitless line-height inherits as the number, so
 * the mono span recomputes it against its own 12.5px, gets a shorter box, and
 * the line box still has to contain it — which is the 0.8px. As a length the
 * spans inherit the row's own box and the row is exactly one of them.
 *
 * 21px is 1.5 x 14. The mono span is then given a shorter one of its own:
 * baseline alignment puts its box lower than the strut, so at the row's own
 * line-height it hung 1px below and the row grew to contain it. 18px fits
 * inside the strut, so the row is exactly one line whether or not there is a
 * container id in it.
 *
 * Measured in Chrome at 1600 wide, which is the only way to see it. One
 * number, in the one rule both sides read — not a number in a second copy
 * compensating for the first, which is what this replaces.
 */
.masthead-meta { font-size: 14px; line-height: 21px; color: var(--ink-body); }
.masthead-meta .mono { line-height: 18px; }
/*
 * An empty identity line still occupies one.
 *
 * The console's client pages draw this band with no run to describe, so the
 * line renders empty — and an empty block has no line box, which made the
 * console's header 21.8px shorter than the report's at the same width. That
 * difference is what app.css's 'min-height: 134px' was for: the number was
 * the gap, written down. Reserving the line here means the band is the same
 * height on both sides because it is the same three rows, not because two
 * numbers were matched.
 */
.masthead-meta:empty::before { content: "\00a0"; }
/* Keeps "version" and the missing chip on one baseline — the chip has a box
   of its own and would otherwise sit high beside the word. */
.masthead-version-missing { display: inline-flex; align-items: center; gap: 4px; }
/* The spec's shared primitive: pushes what follows it to the far end of a
   flex row. Two consumers now — "Prepared by …" and the PDF action beside it —
   and it is the spec's own name for the idea, not a one-off. */
.spacer { flex-grow: 1; }
/*
 * The one control the report carries — plan 3, task 5.
 *
 * Not in the spec, which describes the PDF as "its own export" and names
 * nothing that produces one (backlog item 158). Deliberately quiet: a
 * bordered pill in secondary ink, inheriting the 13px of the brand row it
 * sits in, so the first thing a reader meets at the top of a client's report
 * is still the client's name and not a call to action. It is hidden in print
 * by the rule in the print block — §9 drops "links styled as buttons" — and
 * the print document never draws it at all.
 */
.masthead-action {
  display: inline-flex; align-items: center; gap: var(--space-2);
  height: 40px; padding: 0 var(--space-4);
  border: 1px solid var(--card-border); border-radius: var(--radius);
  color: var(--ink-primary); text-decoration: none; font-weight: 500;
  white-space: nowrap;
}
.masthead-action:hover { color: var(--ink-primary); border-color: var(--ink-muted); }
/*
 * Printing. The route launches a browser and lays out 35 sheets, which is
 * seconds - long enough that a control which looks untouched gets pressed
 * again, and a second press is what the concurrency cap refuses.
 *
 * The printer glyph becomes the spinner rather than a second element appearing
 * beside it: the control keeps its width while it works, so the actions row
 * does not reflow under the reader's pointer. min-width holds the box against
 * the label changing - "Preparing PDF..." is wider than "Download PDF", and
 * "Downloaded" is narrower.
 *
 * Driven by aria-busy, not by a class alone: the attribute is what a screen
 * reader reads, so styling off it means the two cannot describe different
 * states. CHROME_SCRIPT sets both; without script neither appears and the
 * link is a link.
 */
.masthead-action[aria-busy="true"] {
  color: var(--ink-secondary); border-color: var(--card-border); cursor: progress;
}
.masthead-action[aria-busy="true"] svg {
  animation: masthead-spin 900ms linear infinite;
  transform-origin: 50% 50%;
}
.masthead-action .pdf-label { min-width: 104px; }
@keyframes masthead-spin { to { transform: rotate(360deg); } }
/*
 * A reader who asked for less motion gets the state without the spin. The
 * colour change and the label still say it is working.
 */
@media (prefers-reduced-motion: reduce) {
  .masthead-action[aria-busy="true"] svg { animation: none; }
}
/*
 * The flat tab bar — report-tabs in the spec, inside the masthead, which is
 * what supplies its gutter (task 7 carried padding: 0 var(--space-7) here because
 * the bar was a sibling of .topbar; that deviation is gone with the merge).
 *
 * One deviation is left: text-decoration: none on .tabs a, the same addition
 * .tile and .rank-more a also carry — this stylesheet has no page-wide
 * anchor reset, unlike the spec's own gallery chrome.
 *
 * It replaces the grouped rail's sticky sidebar (.rail/.rail-card, retired)
 * and its rows (.rail-group/.nav/.nav:hover/.nav.on, retired — --nav-on,
 * the selected row's own token, went with .nav.on).
 */
/*
 * Below this it does not stick, and the number is measured rather than picked.
 *
 * The masthead is 188.6px at every width down to 768 and then grows as the
 * client's name, the meta line and the tab bar start wrapping: 208px at 500,
 * 253px at a 390px phone. A 253px header that follows the reader takes 30% of
 * an 844px phone screen and 40% of a 640px one, which is the "no content
 * area" the plan warned about — and the single anchor offset above could not
 * be right for both heights either. So the header sticks where it is one
 * stable band and scrolls with the page where it is not. 720px is the one
 * breakpoint this sheet already has (.cookee, below).
 */
@media (max-width: 720px) {
  .masthead { position: static; }
  /*
     And the offset goes with it. A header that scrolls away covers nothing, so
     196px of scroll-padding here is 196px of empty viewport above the thing
     the reader just clicked — measured at 390x844, the target landed at
     top: 196.5 with the masthead 2,100px above it, 23% of the screen given to
     nothing. The old 96px was half as wrong and wrong in the same direction.
     Declared here rather than by a second token, so the offset and the header
     it clears cannot be turned off separately.

     This one reaches the standalone file, which is read on a screen as well as
     printed: everything else in this task is scoped to the rendering the
     console serves.
  */
  :root { --sticky-h: 0px; }
}
.tabs { display: flex; gap: 26px; height: 48px; align-items: stretch; overflow-x: auto; font-size: 14px; }
.tabs a { display: flex; align-items: center; gap: 8px; color: var(--ink-secondary); text-decoration: none; white-space: nowrap; border-bottom: 2px solid transparent; }
.tabs a[aria-current="page"] { color: var(--ink-primary); font-weight: 600; border-bottom-color: var(--accent); }
.tabs-divider { width: 1px; background: var(--card-border); margin: 14px 0; }
.tabs-label { display: flex; align-items: center; font-size: 11px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-muted); }
.count { font: 500 12px var(--font-mono); color: var(--ink-secondary); background: var(--fill-neutral); border-radius: var(--radius); padding: 1px 6px; }
