/* BlueWorkz design system
 * ============================================================================
 * Built from the existing brand pack, not invented: Blue #0E52C9, dark-UI blue
 * #7B92FF, Ink #0F1626, Paper #FFFFFF. The mark is "the Hub" and its usage
 * rules are enforced here where CSS can enforce them (clear space, minimum
 * size, the light/dark variant swap) and stated on /styleguide/ where it
 * cannot.
 *
 * Conventions
 * -----------
 *   - Every class is prefixed `bwz-`. Block, `__element`, `--modifier`.
 *   - Light and dark are both first class. Nothing is "the dark override".
 *   - Brand hexes appear exactly once, in §1, as --bwz-brand-*. Everything
 *     else refers to a *role* token, so a component never names a colour.
 *   - Every foreground/background pair in §2 is checked against WCAG AA by
 *     core/design.py, and core/tests.py fails the build if one drops below.
 *
 * Targets: 320px minimum width, desktop and mobile parity, current and
 * previous major of Chrome, Safari, Firefox and Edge. No CSS nesting, no
 * :has(), no container queries — all four browsers support them at current,
 * not all at previous-major.
 * ============================================================================ */

/* ---------------------------------------------------------------------------
 * 1. Brand constants — the same in every theme, because they are the brand
 * ------------------------------------------------------------------------- */

:root {
  --bwz-brand-blue: #0E52C9;
  --bwz-brand-blue-dark-ui: #7B92FF;
  --bwz-brand-ink: #0F1626;
  --bwz-brand-paper: #FFFFFF;

  /* [CRITICAL] Windows PowerShell's own chrome, for the shell buttons in §24
   * (869f03448, Dan 2026-09-14: "I'd like these ssh links to be buttons, with
   * the buttons looking like tiny powershell windows").
   *
   * They live HERE, as named constants, rather than as hexes inside the
   * component — CLAUDE.md §6 — and they are deliberately theme-independent,
   * which the rest of this file's colours are not. Two reasons. A terminal
   * window that goes white in light mode is a picture of nothing, and on this
   * dashboard colour means "this check is failing", so the one thing a
   * declared alias must never wear is a palette that can be read as a status.
   * A button that looks the same in both themes cannot be mistaken for one.
   *
   * Not BlueWorkz's palette, so core/design.py does not document them and the
   * contrast sweep there does not cover them: it is a sweep of ROLE tokens,
   * and these are a reproduction. The two pairs that matter are measured
   * anyway — screen-fg on screen is 16.5:1 and chrome-fg on chrome is 11.5:1,
   * both well past AA — and core/tests.py asserts it so a "tidy-up" of these
   * five values cannot quietly drop below it. */
  --bwz-pwsh-chrome: #2B2B2B;
  --bwz-pwsh-chrome-fg: #E8E8E8;
  --bwz-pwsh-icon: #1E4FA1;
  --bwz-pwsh-screen: #000000;
  --bwz-pwsh-screen-fg: #F2E5A8;
}

/* ---------------------------------------------------------------------------
 * 2. Role tokens — light is the default, and is defined on bare :root so that
 *    no colour has its only definition inside a media query.
 * ------------------------------------------------------------------------- */

:root {
  color-scheme: light;

  /* Surfaces, back to front */
  --bwz-bg: #FFFFFF;
  --bwz-surface: #FFFFFF;
  --bwz-surface-2: #F4F6FB;
  --bwz-surface-3: #E9EEF7;

  /* Text */
  --bwz-fg: #0F1626;
  --bwz-fg-muted: #4A5568;
  --bwz-fg-subtle: #5C6880;

  /* Lines */
  --bwz-rule: #DDE3ED;
  --bwz-rule-strong: #B9C3D4;
  --bwz-field-border: #767F94;   /* 4.0:1 on paper — SC 1.4.11 wants 3:1 */

  /* Accent */
  --bwz-accent: #0E52C9;
  --bwz-accent-hover: #0B41A1;
  --bwz-accent-active: #083079;
  --bwz-accent-fg: #FFFFFF;
  --bwz-accent-surface: #EAF0FD;

  /* Illustration hues — the hero drawings in section 10c, and NOTHING else.
   * Dan, 2026-09-11 (869ezr12u): "Blueprint is the closest to being right, but
   * I would like something MUCH more complex, more colourful."
   *
   * [CRITICAL] These are read only inside section 10c, and a test fails the
   * build if one is read anywhere else. Three of them are a green, an amber
   * and a rose, and on /dashboard/ those three colours mean "this check is
   * failing" — a drawing hue that leaked onto a tile would be a monitoring
   * defect wearing an illustration. They are hue names on purpose, never
   * status names, so nothing here can be mistaken for --bwz-ok.
   *
   * They invert like everything else, and they have to: the hero gradient runs
   * Blue -> Ink under white in light and dark-UI-blue -> Paper-ish under
   * near-black in dark, so the ground a drawing sits on is DARK in the light
   * theme and LIGHT in the dark one. Light values are therefore bright and
   * dark values are deep. Every one clears 3:1 against BOTH ends of the
   * gradient in its own theme, and core/design.py grades all ten pairs. */
  --bwz-illus-cyan: #67E8F9;
  --bwz-illus-green: #86EFAC;
  --bwz-illus-amber: #FCD34D;
  --bwz-illus-rose: #FDA4AF;
  --bwz-illus-violet: #C4B5FD;

  /* The trend series (869f58teh, Dan 2026-09-22: "make them look nicer! use
   * colours"). ONE INK PER RESOURCE, and [CRITICAL] never a status: a trend
   * line says WHICH resource it is, never how that resource is doing.
   * ops_resourcesample stores no status, so a graded line would be today's
   * thresholds applied to an old reading -- see 26. Kept well clear of the
   * green/amber/red family for exactly that reason, and graded in
   * core/design.py against both grounds a chart is ever drawn on. */
  --bwz-series-cpu: #1D4ED8;
  --bwz-series-memory: #6D28D9;
  --bwz-series-disk: #0E7490;
  --bwz-series-swap: #A21CAF;

  /* Status — the dashboard is a traffic light, so these are load-bearing */
  --bwz-ok: #0F7040;
  --bwz-ok-surface: #E6F4EC;
  --bwz-warn: #8A5300;
  --bwz-warn-surface: #FCF1DE;
  --bwz-danger: #B3251C;
  --bwz-danger-surface: #FCEBE9;

  /* A tile for somebody else's logo. Paper in BOTH themes, on purpose: a
   * partner mark is supplied in one colourway and may not be recoloured,
   * inverted or filtered, so the ground moves to it rather than the other way
   * round. The precedent is the brand pack's own instruction for putting a
   * lockup on a dark ground — it sits on its own Paper tile. */
  --bwz-logo-tile: #FFFFFF;
  --bwz-logo-tile-rule: #DDE3ED;

  /* Elevation. Deliberately restrained: the brand is flat. */
  --bwz-shadow-sm: 0 1px 2px rgba(15, 22, 38, 0.06);
  --bwz-shadow-md: 0 2px 8px rgba(15, 22, 38, 0.08);
  --bwz-shadow-lg: 0 12px 32px rgba(15, 22, 38, 0.12);
  --bwz-overlay: rgba(15, 22, 38, 0.55);
}

/* Dark, by system preference — unless the reader has explicitly chosen light. */
@media (prefers-color-scheme: dark) {
  :root:not([data-bwz-theme="light"]) {
    color-scheme: dark;

    --bwz-bg: #0F1626;
    --bwz-surface: #161F33;
    --bwz-surface-2: #1C2740;
    --bwz-surface-3: #24314E;

    --bwz-fg: #EEF1F7;
    --bwz-fg-muted: #A9B4C8;
    --bwz-fg-subtle: #8F9BB1;

    --bwz-rule: #2A3853;
    --bwz-rule-strong: #3D4E70;
    --bwz-field-border: #68799F;

    --bwz-accent: #7B92FF;
    --bwz-accent-hover: #9EAFFF;
    --bwz-accent-active: #B9C5FF;
    --bwz-accent-fg: #0B1020;
    --bwz-accent-surface: #1A2444;

    /* Deep, because in the dark theme the hero gradient is the LIGHT ground.
     * See the note beside the light values. */
    --bwz-illus-cyan: #0B3A47;
    --bwz-illus-green: #0E4429;
    --bwz-illus-amber: #6B3005;
    --bwz-illus-rose: #7A0F2E;
    --bwz-illus-violet: #4C1D95;

    /* The trend series. Light on the dark ground, for the same reason every
     * other pair here inverts: a line is only a line if it can be seen. */
    --bwz-series-cpu: #93C5FD;
    --bwz-series-memory: #C4B5FD;
    --bwz-series-disk: #67E8F9;
    --bwz-series-swap: #F0ABFC;

    --bwz-ok: #4FC98C;
    --bwz-ok-surface: #12301F;
    --bwz-warn: #E9B159;
    --bwz-warn-surface: #33260F;
    --bwz-danger: #FF8B82;
    --bwz-danger-surface: #3A1B18;

    /* Unchanged in the dark theme. See the note beside the light value. */
    --bwz-logo-tile: #FFFFFF;
    --bwz-logo-tile-rule: #DDE3ED;

    --bwz-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
    --bwz-shadow-md: 0 2px 8px rgba(0, 0, 0, 0.45);
    --bwz-shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.55);
    --bwz-overlay: rgba(4, 8, 16, 0.7);
  }
}

/* Dark, chosen explicitly. Repeated rather than shared so the toggle wins in
 * both directions: a reader on a light system can still pick dark. */
:root[data-bwz-theme="dark"] {
  color-scheme: dark;

  --bwz-bg: #0F1626;
  --bwz-surface: #161F33;
  --bwz-surface-2: #1C2740;
  --bwz-surface-3: #24314E;

  --bwz-fg: #EEF1F7;
  --bwz-fg-muted: #A9B4C8;
  --bwz-fg-subtle: #8F9BB1;

  --bwz-rule: #2A3853;
  --bwz-rule-strong: #3D4E70;
  --bwz-field-border: #68799F;

  --bwz-accent: #7B92FF;
  --bwz-accent-hover: #9EAFFF;
  --bwz-accent-active: #B9C5FF;
  --bwz-accent-fg: #0B1020;
  --bwz-accent-surface: #1A2444;

  /* Deep, because in the dark theme the hero gradient is the LIGHT ground.
   * See the note beside the light values. */
  --bwz-illus-cyan: #0B3A47;
  --bwz-illus-green: #0E4429;
  --bwz-illus-amber: #6B3005;
  --bwz-illus-rose: #7A0F2E;
  --bwz-illus-violet: #4C1D95;

  /* The trend series. Light on the dark ground, for the same reason every
   * other pair here inverts: a line is only a line if it can be seen. */
  --bwz-series-cpu: #93C5FD;
  --bwz-series-memory: #C4B5FD;
  --bwz-series-disk: #67E8F9;
  --bwz-series-swap: #F0ABFC;

  --bwz-ok: #4FC98C;
  --bwz-ok-surface: #12301F;
  --bwz-warn: #E9B159;
  --bwz-warn-surface: #33260F;
  --bwz-danger: #FF8B82;
  --bwz-danger-surface: #3A1B18;

  /* Unchanged in the dark theme. See the note beside the light value. */
  --bwz-logo-tile: #FFFFFF;
  --bwz-logo-tile-rule: #DDE3ED;

  --bwz-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
  --bwz-shadow-md: 0 2px 8px rgba(0, 0, 0, 0.45);
  --bwz-shadow-lg: 0 12px 32px rgba(0, 0, 0, 0.55);
  --bwz-overlay: rgba(4, 8, 16, 0.7);
}

/* ---------------------------------------------------------------------------
 * 3. Type, space and shape — theme-independent
 * ------------------------------------------------------------------------- */

:root {
  --bwz-font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
  --bwz-font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
    "Liberation Mono", monospace;

  /* Type scale. Body is fixed; headings are fluid so that a 320px phone and a
   * 1440px desktop both get a sensible measure without a breakpoint each. */
  --bwz-text-xs: 0.75rem;      /* 12px — meta, table captions            */
  --bwz-text-sm: 0.875rem;     /* 14px — helper text, badges             */
  --bwz-text-base: 1rem;       /* 16px — body. Never smaller on mobile.  */
  --bwz-text-md: 1.125rem;     /* 18px — lede                            */
  --bwz-text-lg: clamp(1.25rem, 0.5vw + 1.15rem, 1.375rem);
  --bwz-text-xl: clamp(1.5rem, 1vw + 1.3rem, 1.75rem);
  --bwz-text-2xl: clamp(1.75rem, 2vw + 1.4rem, 2.25rem);
  --bwz-text-3xl: clamp(2rem, 4vw + 1.25rem, 3rem);

  --bwz-leading-tight: 1.15;
  --bwz-leading-snug: 1.35;
  --bwz-leading-normal: 1.6;

  --bwz-weight-normal: 400;
  --bwz-weight-medium: 500;
  --bwz-weight-semibold: 600;
  --bwz-weight-bold: 700;

  --bwz-tracking-tight: -0.02em;
  --bwz-tracking-normal: 0;
  --bwz-tracking-wide: 0.08em;

  /* Spacing — a 4px base, named by step, not by intent. */
  --bwz-space-1: 0.25rem;
  --bwz-space-2: 0.5rem;
  --bwz-space-3: 0.75rem;
  --bwz-space-4: 1rem;
  --bwz-space-5: 1.5rem;
  --bwz-space-6: 2rem;
  --bwz-space-7: 3rem;
  --bwz-space-8: 4rem;
  --bwz-space-9: 6rem;

  --bwz-radius-sm: 4px;
  --bwz-radius-md: 8px;
  --bwz-radius-lg: 14px;
  --bwz-radius-full: 999px;

  --bwz-border: 1px;
  --bwz-border-thick: 2px;

  /* Layout.
   *
   * Widened on Dan's instruction, ticket 869evwwzn, 2026-09-07: "All the
   * content is constrained to a width which is too narrow — on wider screens,
   * make it flow wider if the window is wider."
   *
   * The container went 68rem -> 96rem, so a card grid, a picture and the
   * header now use a 1600px screen instead of leaving a third of it empty.
   * The reading measure did NOT double with it, and that is deliberate: line
   * length is the one width where more is worse. 34rem is about 68 characters
   * and 40rem is about 80, which is the top of the readable band; twice 34rem
   * would be roughly 135 characters a line and a reader loses their place
   * returning to the left edge. So the page got wider and the paragraph got a
   * little wider — the empty space a wide window used to show is now filled by
   * the things that can use it. */
  --bwz-container: 96rem;
  --bwz-container-narrow: 52rem;
  --bwz-container-wide: 112rem;

  /* [CRITICAL] THE WIDTH OF ONE PRODUCT CARD, AND EVERY WIDTH DERIVED FROM IT
   * (869fcjex3; promoted out of §17e by 869fczpww, 2026-10-08). It lived on
   * `.bwz-dash-grid--aligned`, which is where it is consumed -- but the roll-up's
   * own container is now derived from it too (§17e), and a token a container
   * needs cannot be declared on a grid inside that container. The measurement
   * that chose 18rem is in §17e, beside the rule that applies it. */
  --bwz-dash-card-wide: 18rem;

  /* [CRITICAL] THE ROLL-UP IS AS WIDE AS TEN CARDS AND THE BOX AROUND THEM
   * (869fczpww, Dan 2026-10-08). Ten cards and nine gaps is the row; a section
   * card adds `.bwz-disclosure__body`'s padding and its own border; the
   * container adds its gutters. DERIVED, never typed -- change the card width
   * and this follows it, which is the rule §17b states about the grey band.
   * The whole reasoning, and the measurement off Dan's screenshot that forced
   * it, is in §17e. */
  --bwz-container-dash: calc(
    10 * var(--bwz-dash-card-wide) + 9 * var(--bwz-space-4)
    + 2 * var(--bwz-space-5) + 2 * var(--bwz-border) + 2 * var(--bwz-gutter)
  );
  --bwz-measure: 40rem;      /* ~80 characters — the top of the readable band */
  /* [DAN'S DECISION, and it is his second time asking] 2026-09-09, 869evwx03:
   * "make the content area twice as wide, if the browser window is wide enough
   * to support it". The last session widened the container and deliberately did
   * NOT widen the reading measure, on the argument recorded above; he has now
   * repeated the instruction having seen that result, so the measure doubles.
   *
   * It doubles only where the clause in his sentence is satisfied — at 90em and
   * up, which is a window of about 1440px. Below that nothing moves at all, so
   * every phone, tablet and laptop keeps the 40rem measure. And it applies to
   * the marketing pages only (section 10c), never to a form, the dashboard or
   * the questionnaire, where a 1280px-wide text field is a defect. */
  --bwz-measure-wide: 80rem;
  --bwz-gutter: 1rem;

  /* The Hub mark: the centre circle is r=8 in a 48 viewBox, so its width is
   * exactly one third of the mark. That is the brand pack's clear space. */
  --bwz-mark-size: 2rem;
  --bwz-mark-clearspace: calc(var(--bwz-mark-size) / 3);
  --bwz-mark-min: 20px;      /* below this, use the favicon variant */

  --bwz-duration: 140ms;
  --bwz-ease: cubic-bezier(0.2, 0, 0.2, 1);

  --bwz-z-header: 100;
  --bwz-z-overlay: 200;
}

@media (min-width: 40em) {
  :root { --bwz-gutter: 1.5rem; }
}

/* A 96rem container on a 1600px screen has almost no margin left. The gutter
 * grows once there is room for it, which is the one place a breakpoint earns
 * its keep: it changes a spacing value, not a layout. */
@media (min-width: 90em) {
  :root { --bwz-gutter: 2.5rem; }
}

/* ---------------------------------------------------------------------------
 * 4. Reset and element defaults
 * ------------------------------------------------------------------------- */

*,
*::before,
*::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body,
h1, h2, h3, h4, h5, h6,
p, figure, blockquote, dl, dd, ul, ol { margin: 0; }

ul[class], ol[class] { padding: 0; list-style: none; }

img, svg, video, canvas { display: block; max-width: 100%; }
img, svg { height: auto; }

input, button, textarea, select { font: inherit; color: inherit; }

.bwz-body {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--bwz-bg);
  color: var(--bwz-fg);
  font-family: var(--bwz-font-sans);
  font-size: var(--bwz-text-base);
  line-height: var(--bwz-leading-normal);
  -webkit-font-smoothing: antialiased;
  /* An overflowing table or code block must scroll inside its own box, never
   * push the page sideways. At 320px that is the difference between usable
   * and not. */
  overflow-x: hidden;
}

:focus-visible {
  outline: var(--bwz-border-thick) solid var(--bwz-accent);
  outline-offset: 2px;
  border-radius: var(--bwz-radius-sm);
}

::selection {
  background: var(--bwz-accent);
  color: var(--bwz-accent-fg);
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ---------------------------------------------------------------------------
 * 5. Layout primitives
 * ------------------------------------------------------------------------- */

.bwz-container {
  width: 100%;
  max-width: var(--bwz-container);
  margin-inline: auto;
  padding-inline: var(--bwz-gutter);
}
.bwz-container--narrow { max-width: var(--bwz-container-narrow); }
.bwz-container--wide { max-width: var(--bwz-container-wide); }
.bwz-container--dash { max-width: var(--bwz-container-dash); }

.bwz-main { flex: 1 0 auto; }

.bwz-section { padding-block: var(--bwz-space-7); }
.bwz-section--tight { padding-block: var(--bwz-space-5); }
.bwz-section--loose { padding-block: clamp(2.5rem, 8vw, 5.5rem); }
.bwz-section--tinted { background: var(--bwz-surface-2); }

/* A framed, separate document -- the clickable prototype captured off
 * demo.blueworkz.com (869f0ce5k), and since 39.4.0 the sandboxed preview of
 * each transactional email on /dashboard/email-templates/ (869fm4gua), whose
 * HTML carries its own inline colours exactly as a mail client receives it.
 * An <iframe> and not an
 * inline include, because that capture carries its own :root custom properties,
 * its own `body` rule and its own prefers-color-scheme block; inlined it would
 * redefine this stylesheet's tokens on a page that also serves the dashboard,
 * where colour means "this check is failing".
 *
 * Height is viewport-relative rather than aspect-ratio: the frame holds a wide
 * application interface, not a video, so what matters is how much of it is
 * visible at once. 70vh with a floor keeps it usable at 320px, where the
 * prototype's own max-width:780px rules take over inside the frame.
 */
.bwz-embed {
  border: var(--bwz-border) solid var(--bwz-rule);
  border-radius: var(--bwz-radius-md);
  background: var(--bwz-surface);
  overflow: hidden;
}
.bwz-embed iframe {
  display: block;
  width: 100%;
  height: max(28rem, 70vh);
  border: 0;
}

/* Vertical rhythm without margin arithmetic in every component. */
.bwz-stack > * + * { margin-top: var(--bwz-space-4); }
.bwz-stack--tight > * + * { margin-top: var(--bwz-space-2); }
.bwz-stack--loose > * + * { margin-top: var(--bwz-space-6); }

.bwz-cluster {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bwz-space-3);
}
.bwz-cluster--between { justify-content: space-between; }

/* auto-fit, so it collapses to one column at 320px with no breakpoint. */
.bwz-grid {
  display: grid;
  gap: var(--bwz-space-5);
  grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
}
.bwz-grid--2 { grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr)); }

.bwz-measure { max-width: var(--bwz-measure); }
.bwz-visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ---------------------------------------------------------------------------
 * 6. Typography
 * ------------------------------------------------------------------------- */

.bwz-h1, .bwz-h2, .bwz-h3, .bwz-h4 {
  font-weight: var(--bwz-weight-bold);
  letter-spacing: var(--bwz-tracking-tight);
  line-height: var(--bwz-leading-tight);
  text-wrap: balance;
}
.bwz-h1 { font-size: var(--bwz-text-3xl); max-width: 22ch; }
.bwz-h2 { font-size: var(--bwz-text-2xl); }
.bwz-h3 { font-size: var(--bwz-text-xl); line-height: var(--bwz-leading-snug); }
.bwz-h4 {
  font-size: var(--bwz-text-lg);
  font-weight: var(--bwz-weight-semibold);
  line-height: var(--bwz-leading-snug);
}

.bwz-eyebrow {
  font-size: var(--bwz-text-sm);
  font-weight: var(--bwz-weight-semibold);
  letter-spacing: var(--bwz-tracking-wide);
  text-transform: uppercase;
  color: var(--bwz-accent);
}

.bwz-lede {
  font-size: var(--bwz-text-md);
  max-width: var(--bwz-measure);
}
.bwz-prose { max-width: var(--bwz-measure); }
.bwz-prose > * + * { margin-top: var(--bwz-space-4); }
.bwz-muted { color: var(--bwz-fg-muted); }
.bwz-subtle { color: var(--bwz-fg-subtle); font-size: var(--bwz-text-sm); }
.bwz-meta {
  font-size: var(--bwz-text-xs);
  color: var(--bwz-fg-subtle);
  font-variant-numeric: tabular-nums;
}

/* [CRITICAL] IT WRAPS, EVERYWHERE, AND THAT IS DAN'S TICK RATHER THAN A STYLE
 * CHOICE (869fczpww, his tick of **Q3(a)** read back with `cu.py ticks`,
 * 2026-10-08, the recommended option: *"Fix it: let long code spans wrap
 * everywhere. It affects the public site, the portal and the questionnaire as
 * well as the dashboard."* -- (c), the dashboard-only fix, was offered and not
 * taken).
 *
 * MEASURED before it was fixed: `code.bwz-code` was the one declared entry in
 * `KNOWN_OVERFLOWS` in drills/check_detail_controls_drill.py, re-measured on a
 * tree without that ticket's changes at all and found identical -- a long env
 * key name is wider than a 320px phone and made the check page scroll sideways.
 * CLAUDE.md §6 names 320px as a hard target.
 *
 * `anywhere` and not `break-word`: these are KEY NAMES and PATHS with no spaces
 * in them, so there is no break opportunity to prefer and `break-word` would
 * leave the overflow exactly where it was. The entry is REMOVED from
 * `KNOWN_OVERFLOWS` in the same act, which is that list's own second rule --
 * an entry that has stopped overflowing is reported so the list shrinks instead
 * of covering the next fault. */
.bwz-code {
  overflow-wrap: anywhere;
  font-family: var(--bwz-font-mono);
  font-size: 0.9375em;
  background: var(--bwz-surface-3);
  border-radius: var(--bwz-radius-sm);
  padding: 0.1em 0.35em;
}

/* [CRITICAL] The type selector is load-bearing, and it is a fix rather than a
 * flourish. The reset in section 4 says `ul[class], ol[class] { padding: 0;
 * list-style: none; }` -- which is the right default, because most lists on
 * this site are navigation. But `ol[class]` is (0,1,1) and a bare `.bwz-list`
 * is (0,1,0), so the reset **won**, and every bullet and every step number on
 * this site had silently rendered as an unmarked line since the stylesheet was
 * written. Nobody saw it because nothing had used a bulleted list in anger
 * until Dan asked for one on 2026-09-09 (869evwx03) -- at which point three
 * cards on /what-we-do/ came out as run-on lines with no markers.
 *
 * `ul.bwz-list` ties the reset on specificity and wins on source order.
 *
 * ⚠️ `--plain` has to be raised WITH it, and the first attempt got this wrong
 * on the reasoning that `--plain` is used on its own. It is not: every one of
 * its four uses is `class="bwz-list bwz-list--plain"`, so raising only the base
 * put bullets on the site-wide footer navigation. Caught by looking at a
 * screenshot of the footer, one deploy later. Both are (0,1,1) and `--plain`
 * comes second, which is what makes the modifier win.
 * `core/tests.py::test_a_bulleted_list_actually_gets_its_bullets` pins both. */
ul.bwz-list,
ol.bwz-list {
  padding-left: 1.25em;
  list-style: disc;
}
.bwz-list > li + li { margin-top: var(--bwz-space-2); }
ol.bwz-list--ordered { list-style: decimal; }
ul.bwz-list--plain,
ol.bwz-list--plain { padding-left: 0; list-style: none; }

/* ---------------------------------------------------------------------------
 * 7. Links
 * ------------------------------------------------------------------------- */

a { color: var(--bwz-accent); }

.bwz-link {
  color: var(--bwz-accent);
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.15em;
  border-radius: var(--bwz-radius-sm);
}
.bwz-link:hover { color: var(--bwz-accent-hover); text-decoration-thickness: 2px; }
.bwz-link:active { color: var(--bwz-accent-active); }

/* For links inside a nav or a card, where the underline is noise. It still
 * underlines on hover and focus, so it is never colour-alone. */
.bwz-link--quiet { text-decoration: none; }
.bwz-link--quiet:hover,
.bwz-link--quiet:focus-visible { text-decoration: underline; }

.bwz-link--plain { color: inherit; text-decoration: none; }
.bwz-link--plain:hover { color: var(--bwz-accent); text-decoration: underline; }

/* Keyboard users reach the content without tabbing the whole header. */
.bwz-skip {
  position: absolute;
  left: -9999px;
  top: var(--bwz-space-2);
  z-index: var(--bwz-z-overlay);
  background: var(--bwz-accent);
  color: var(--bwz-accent-fg);
  padding: var(--bwz-space-2) var(--bwz-space-4);
  border-radius: var(--bwz-radius-md);
  text-decoration: none;
}
.bwz-skip:focus { left: var(--bwz-space-2); }

/* ---------------------------------------------------------------------------
 * 8. The mark and the lockups
 *
 * The brand pack forbids recolouring, gradients, stretching, rotation, an
 * added outline and a drop shadow. There is therefore no modifier here that
 * does any of those, and nothing sets `filter` or `transform` on a mark.
 * ------------------------------------------------------------------------- */

.bwz-mark {
  display: block;
  width: var(--bwz-mark-size);
  height: var(--bwz-mark-size);
  min-width: var(--bwz-mark-min);
  min-height: var(--bwz-mark-min);
  flex: none;
}
.bwz-mark--sm { --bwz-mark-size: 1.5rem; }
.bwz-mark--lg { --bwz-mark-size: 3rem; }
.bwz-mark--xl { --bwz-mark-size: 5rem; }

/* Clear space: at least the width of the centre circle, all the way round. */
.bwz-mark--clearspace {
  box-sizing: content-box;
  padding: var(--bwz-mark-clearspace);
}

.bwz-lockup { display: block; height: auto; }
.bwz-lockup--h { width: min(16.25rem, 100%); }
.bwz-lockup--stacked { width: min(12.5rem, 100%); }

/* The colour mark is solid #0E52C9 and goes muddy on the dark ground, so the
 * white variant is swapped in by CSS rather than by script. All three theme
 * states are handled: system-light, system-dark, and an explicit choice. */
.bwz-mark--on-dark { display: none; }

@media (prefers-color-scheme: dark) {
  :root:not([data-bwz-theme="light"]) .bwz-mark--on-light { display: none; }
  :root:not([data-bwz-theme="light"]) .bwz-mark--on-dark { display: block; }
}
:root[data-bwz-theme="dark"] .bwz-mark--on-light { display: none; }
:root[data-bwz-theme="dark"] .bwz-mark--on-dark { display: block; }

.bwz-logo {
  display: inline-flex;
  align-items: center;
  gap: var(--bwz-space-3);
  text-decoration: none;
  color: inherit;
  border-radius: var(--bwz-radius-md);
}
.bwz-logo__word {
  font-weight: var(--bwz-weight-bold);
  font-size: var(--bwz-text-md);
  letter-spacing: var(--bwz-tracking-tight);
  white-space: nowrap;
}
.bwz-logo__word b { color: var(--bwz-accent); font-weight: inherit; }

/* ---------------------------------------------------------------------------
 * 9. Buttons
 * ------------------------------------------------------------------------- */

.bwz-btn {
  display: inline-flex;
  align-items: center;
  justify-content: start;
  gap: var(--bwz-space-2);
  min-height: 44px;                 /* a real touch target at 320px */
  padding: var(--bwz-space-2) var(--bwz-space-5);
  border: var(--bwz-border-thick) solid transparent;
  border-radius: var(--bwz-radius-md);
  background: transparent;
  color: var(--bwz-fg);
  font-size: var(--bwz-text-base);
  font-weight: var(--bwz-weight-semibold);
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition: background-color var(--bwz-duration) var(--bwz-ease),
              border-color var(--bwz-duration) var(--bwz-ease),
              color var(--bwz-duration) var(--bwz-ease);
}

.bwz-btn--primary {
  background: var(--bwz-accent);
  border-color: var(--bwz-accent);
  color: var(--bwz-accent-fg);
}
.bwz-btn--primary:hover { background: var(--bwz-accent-hover); border-color: var(--bwz-accent-hover); }
.bwz-btn--primary:active { background: var(--bwz-accent-active); border-color: var(--bwz-accent-active); }

.bwz-btn--secondary {
  border-color: var(--bwz-accent);
  color: var(--bwz-accent);
}
.bwz-btn--secondary:hover { background: var(--bwz-accent-surface); color: var(--bwz-accent-hover); border-color: var(--bwz-accent-hover); }

.bwz-btn--ghost { color: var(--bwz-fg-muted); }
.bwz-btn--ghost:hover { background: var(--bwz-surface-2); color: var(--bwz-fg); }

.bwz-btn--danger {
  background: var(--bwz-danger);
  border-color: var(--bwz-danger);
  color: var(--bwz-brand-paper);
}
:root[data-bwz-theme="dark"] .bwz-btn--danger { color: var(--bwz-brand-ink); }
@media (prefers-color-scheme: dark) {
  :root:not([data-bwz-theme="light"]) .bwz-btn--danger { color: var(--bwz-brand-ink); }
}

.bwz-btn--sm { min-height: 36px; padding: var(--bwz-space-1) var(--bwz-space-3); font-size: var(--bwz-text-sm); }
.bwz-btn--lg { min-height: 52px; padding: var(--bwz-space-3) var(--bwz-space-6); font-size: var(--bwz-text-md); }
.bwz-btn--block { display: flex; width: 100%; }

.bwz-btn[disabled],
.bwz-btn[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}

.bwz-btn-group {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bwz-space-3);
}
/* At 320px a row of buttons must stack rather than shrink below the target. */
@media (max-width: 22.5em) {
  .bwz-btn-group > .bwz-btn { flex: 1 1 100%; }
}

/* ---------------------------------------------------------------------------
 * 10. Cards
 * ------------------------------------------------------------------------- */

.bwz-card {
  display: flex;
  flex-direction: column;
  background: var(--bwz-surface);
  border: var(--bwz-border) solid var(--bwz-rule);
  border-radius: var(--bwz-radius-lg);
  box-shadow: var(--bwz-shadow-sm);
  overflow: hidden;
}
.bwz-card__header,
.bwz-card__body,
.bwz-card__footer { padding: var(--bwz-space-5); }
.bwz-card__header { border-bottom: var(--bwz-border) solid var(--bwz-rule); }
.bwz-card__footer {
  border-top: var(--bwz-border) solid var(--bwz-rule);
  background: var(--bwz-surface-2);
  margin-top: auto;
}
.bwz-card__title {
  font-size: var(--bwz-text-lg);
  font-weight: var(--bwz-weight-semibold);
  line-height: var(--bwz-leading-snug);
}
.bwz-card--flush { box-shadow: none; }
.bwz-card--tinted { background: var(--bwz-surface-2); }

.bwz-card--interactive { transition: border-color var(--bwz-duration) var(--bwz-ease), box-shadow var(--bwz-duration) var(--bwz-ease); }
.bwz-card--interactive:hover { border-color: var(--bwz-accent); box-shadow: var(--bwz-shadow-md); }
.bwz-card--interactive:focus-within { border-color: var(--bwz-accent); }

/* A stat tile for the dashboard. */
.bwz-stat__label {
  font-size: var(--bwz-text-sm);
  color: var(--bwz-fg-muted);
  font-weight: var(--bwz-weight-medium);
}
.bwz-stat__value {
  font-size: var(--bwz-text-2xl);
  font-weight: var(--bwz-weight-bold);
  line-height: var(--bwz-leading-tight);
  font-variant-numeric: tabular-nums;
  letter-spacing: var(--bwz-tracking-tight);
}

/* ---------------------------------------------------------------------------
 * 10a. Logos and pictures
 *
 * Added for ticket 869evwwzn, 2026-09-07, on two instructions: "Use logos for
 * Asset Giant and Just Granted", and "use the mockups as graphics ... plus a
 * screenshot".
 *
 * Two rules govern everything here.
 *
 * A supplied mark is not recoloured. Asset Giant's logo carries a gradient and
 * a red; Just Granted's is a fixed teal. Neither survives an invert or a CSS
 * filter, and both would fail against the dark ground. So they sit on a Paper
 * tile in both themes -- the brand pack's own answer for a lockup on dark.
 *
 * Every picture reserves its space before it loads. width and height attributes
 * are on the markup and aspect-ratio is here, so nothing below an image jumps
 * down the page when it arrives.
 * ------------------------------------------------------------------------- */

.bwz-logo-tile {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  background: var(--bwz-logo-tile);
  border: var(--bwz-border) solid var(--bwz-logo-tile-rule);
  border-radius: var(--bwz-radius-md);
  padding: var(--bwz-space-4);
  min-height: 4.5rem;
}
.bwz-logo-tile img {
  width: auto;
  max-height: 2.5rem;
  max-width: 100%;
  object-fit: contain;
}
.bwz-logo-tile--centred { justify-content: center; }

/* A row of logos that wraps at 320px with no breakpoint. */
.bwz-logo-strip {
  display: grid;
  gap: var(--bwz-space-4);
  grid-template-columns: repeat(auto-fit, minmax(min(12rem, 100%), 1fr));
}

/* A picture with an optional caption. <figure> is the element; this is only
 * the frame round it. */
.bwz-figure { display: flex; flex-direction: column; gap: var(--bwz-space-3); }
.bwz-figure img {
  width: 100%;
  border-radius: var(--bwz-radius-lg);
  border: var(--bwz-border) solid var(--bwz-rule);
  background: var(--bwz-surface-2);
}
.bwz-figure figcaption {
  font-size: var(--bwz-text-sm);
  color: var(--bwz-fg-subtle);
}

/* The gallery on a case study. auto-fit again, so one picture fills the width
 * and three share it. */
.bwz-gallery {
  display: grid;
  gap: var(--bwz-space-5);
  grid-template-columns: repeat(auto-fit, minmax(min(22rem, 100%), 1fr));
}

/* A picture at the top of a card, above the title. It is cropped to a fixed
 * shape so a row of cards keeps its titles on one line. */
.bwz-card__media {
  aspect-ratio: 16 / 9;
  background: var(--bwz-surface-2);
  border-bottom: var(--bwz-border) solid var(--bwz-rule);
}
.bwz-card__media img { width: 100%; height: 100%; object-fit: cover; }
.bwz-card__media--logo {
  aspect-ratio: auto;
  background: var(--bwz-logo-tile);
  padding: var(--bwz-space-5);
  display: flex;
  align-items: center;
}
.bwz-card__media--logo img {
  height: 2.25rem;
  width: auto;
  max-width: 100%;
  object-fit: contain;
}

/* ---------------------------------------------------------------------------
 * 10b. Design concepts — /design-concepts/ only, staff only
 *
 * Dan, ticket 869evwwzn, 2026-09-07: "I'd like the site to be a lot more
 * visually arresting - include graphics and more colours etc ... professional,
 * modern, slight hints at AI capabilities but really very subtle on that part,
 * simple (just not quite this simple as we currently have). Draft various
 * designs / graphics etc for my approval."
 *
 * So these are DRAFTS. Nothing here is applied to a page a visitor can reach:
 * every rule is scoped under .bwz-dc, the page it renders on is signed-in-staff
 * only, and picking one is a separate piece of work.
 *
 * Three constraints held throughout, because they are not negotiable:
 *   - no component names a hex; everything is built from the role tokens in
 *     section 2, so all three directions follow the light and dark themes;
 *   - the mark is never recoloured, gradient-filled, rotated or outlined;
 *   - nothing here relies on CSS nesting, :has() or container queries, so the
 *     previous major of all four target browsers still renders it.
 *
 * The "AI hint" is a field of faint dots behind a heading and nothing else. No
 * circuitry, no glowing orb, no robot.
 * ------------------------------------------------------------------------- */

.bwz-dc { border-radius: var(--bwz-radius-lg); overflow: hidden; }
.bwz-dc__frame {
  border: var(--bwz-border) solid var(--bwz-rule);
  border-radius: var(--bwz-radius-lg);
  overflow: hidden;
}
.bwz-dc__pad { padding: clamp(1.5rem, 4vw, 3.5rem); }
.bwz-dc__cards {
  display: grid;
  gap: var(--bwz-space-4);
  grid-template-columns: repeat(auto-fit, minmax(min(14rem, 100%), 1fr));
}
.bwz-dc__card {
  background: var(--bwz-surface);
  border: var(--bwz-border) solid var(--bwz-rule);
  border-radius: var(--bwz-radius-md);
  padding: var(--bwz-space-5);
}
.bwz-dc__card p { color: var(--bwz-fg-muted); font-size: var(--bwz-text-sm); }
.bwz-dc__title {
  font-size: var(--bwz-text-2xl);
  font-weight: var(--bwz-weight-bold);
  letter-spacing: var(--bwz-tracking-tight);
  line-height: var(--bwz-leading-tight);
  /* 26ch, not the 22ch .bwz-h1 uses: at 20ch this wrapped to five lines on a
   * 1280px screen and left the right two-thirds of the hero empty, which read
   * as a layout fault rather than as a design. Measured in a screenshot. */
  max-width: 26ch;
}
.bwz-dc__lede {
  font-size: var(--bwz-text-md);
  color: var(--bwz-fg-muted);
  max-width: var(--bwz-measure);
  margin-top: var(--bwz-space-4);
}

/* -- A. Quiet ---------------------------------------------------------------
 * What the site is now, given room to breathe: one accent, a lot of white
 * space, and the pictures doing the work instead of the colour. The safe
 * choice, and the one that ages best. */
.bwz-dc--quiet { background: var(--bwz-bg); }
.bwz-dc--quiet .bwz-dc__title { color: var(--bwz-fg); }
.bwz-dc--quiet .bwz-dc__rule {
  width: 3rem;
  height: var(--bwz-border-thick);
  background: var(--bwz-accent);
  margin-bottom: var(--bwz-space-5);
}

/* -- B. Structured ----------------------------------------------------------
 * Colour arrives through surfaces rather than through hue: a tinted ground,
 * white cards floating on it, an accent rule and a numbered spine down the
 * left. Reads as an operations document, which is close to what is being
 * sold. */
.bwz-dc--structured { background: var(--bwz-surface-2); }
.bwz-dc--structured .bwz-dc__card {
  box-shadow: var(--bwz-shadow-md);
  border-color: transparent;
  position: relative;
  padding-left: var(--bwz-space-7);
}
.bwz-dc--structured .bwz-dc__num {
  position: absolute;
  left: var(--bwz-space-5);
  top: var(--bwz-space-5);
  font-variant-numeric: tabular-nums;
  font-weight: var(--bwz-weight-bold);
  color: var(--bwz-accent);
}
.bwz-dc--structured .bwz-dc__title {
  border-left: 4px solid var(--bwz-accent);
  padding-left: var(--bwz-space-5);
}

/* -- C. Signal --------------------------------------------------------------
 * The loud one. A gradient hero built from the two ends of the palette, a
 * field of faint dots behind it, and the section below it on the accent tint.
 * Both gradient stops are role tokens, so it inverts correctly in the dark
 * theme instead of turning to mud.
 *
 * The dot field is a repeating radial-gradient rather than an image: it is
 * about 90 bytes, it scales to any viewport, and there is no file to keep. */
.bwz-dc--signal .bwz-dc__hero {
  position: relative;
  background-image: linear-gradient(140deg, var(--bwz-accent) 0%, var(--bwz-fg) 100%);
  color: var(--bwz-accent-fg);
  isolation: isolate;
}
.bwz-dc--signal .bwz-dc__hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background-image: radial-gradient(currentColor 1px, transparent 1px);
  background-size: 18px 18px;
  opacity: 0.12;
}
.bwz-dc--signal .bwz-dc__title,
.bwz-dc--signal .bwz-dc__lede { color: var(--bwz-accent-fg); }
.bwz-dc--signal .bwz-dc__band { background: var(--bwz-accent-surface); }
.bwz-dc--signal .bwz-dc__card { border-color: var(--bwz-accent); }

/* ---------------------------------------------------------------------------
 * 10c. Signal — the chosen direction, on the real site
 *
 * Dan picked C, "Signal", on 2026-09-08 (ticket 869evwwzn, read back off the
 * tick boxes on 869eyf9jx). ⚠️ He did NOT pick the recommended option — the
 * sweep starred B, Structured. Anybody revisiting this works from the tick.
 *
 * This is 10b's third draft made real. The draft stays where it is, so the
 * three directions can still be compared, and this section is what a visitor
 * actually gets. Three devices and no more:
 *
 *   1. a gradient hero, built from the two ends of the palette;
 *   2. a field of faint dots behind it — the only nod to AI anywhere on the
 *      site, and deliberately not a circuit board, an orb or a robot;
 *   3. the accent tint on a banded section, and an accent hairline on cards.
 *
 * [CRITICAL] Scoped to the marketing site, not to the stylesheet. The
 * dashboard, the client portal and the discovery questionnaire all extend the
 * same base.html and share this one stylesheet, so every rule below is behind
 * `.bwz-body--signal`, a body class that only content/page.html and
 * content/case_study.html set. Restyling `.bwz-card` or `.bwz-section--tinted`
 * outright would have repainted the operations dashboard, where colour means
 * "this check is failing" and nothing else.
 *
 * No hex appears here. Both gradient stops are role tokens, so the hero
 * inverts with the theme instead of turning to mud: light runs Blue -> Ink
 * under white text, dark runs dark-UI-blue -> Paper-ish under near-black text.
 * ------------------------------------------------------------------------- */

/* The hero. `isolation: isolate` gives the dot field a stacking context of its
 * own, so `z-index: -1` puts it behind the text and NOT behind the page. */
.bwz-hero {
  position: relative;
  isolation: isolate;
  background-image: linear-gradient(140deg, var(--bwz-accent) 0%, var(--bwz-fg) 100%);
  color: var(--bwz-accent-fg);
}

/* The AI hint: a repeating radial-gradient rather than an image. About ninety
 * bytes, scales to any viewport, and there is no file to keep in step with a
 * release. `currentColor` means it follows the hero's own text colour, so it
 * needs no token of its own in either theme. */
.bwz-hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background-image: radial-gradient(currentColor 1px, transparent 1px);
  background-size: 18px 18px;
  opacity: 0.12;
}

/* Everything inside the hero is on the gradient, so it takes the hero's
 * foreground rather than the page's. The eyebrow and the meta line are named
 * explicitly because both carry a colour of their own in section 6. */
.bwz-hero .bwz-h1,
.bwz-hero .bwz-lede,
.bwz-hero .bwz-prose,
.bwz-hero .bwz-eyebrow,
.bwz-hero .bwz-meta,
.bwz-hero .bwz-muted,
.bwz-hero .bwz-subtle { color: var(--bwz-accent-fg); }

/* A link on the gradient. The accent is the darkest thing in the light theme's
 * gradient and would vanish into it, so the link takes the hero's foreground
 * and keeps its underline — which is what carries the affordance once colour
 * is no longer doing it. */
.bwz-hero .bwz-link,
.bwz-hero a { color: var(--bwz-accent-fg); }
.bwz-hero .bwz-link:hover,
.bwz-hero a:hover { color: var(--bwz-accent-fg); text-decoration-thickness: 2px; }

/* [CRITICAL] The focus ring is `--bwz-accent` everywhere else, and the accent
 * is one END of this gradient: a keyboard user tabbing across the hero would
 * get an outline the same colour as the thing behind it. On the gradient the
 * ring is the hero's foreground instead, which is the highest-contrast colour
 * available against every point of it. */
.bwz-hero :focus-visible { outline-color: var(--bwz-accent-fg); }

/* The band under the hero: the accent tint, where `--tinted` is the neutral
 * one. A separate modifier rather than a redefinition of `--tinted`, because
 * `--tinted` is also the dashboard's table header and the case study's body. */
.bwz-section--band { background: var(--bwz-accent-surface); }

/* An accent hairline on the cards, so the colour carries past the hero rather
 * than stopping at it. Border only: the card keeps its own surface, because a
 * tinted card on a tinted band is two surfaces and no edge. */
.bwz-body--signal .bwz-card { border-color: var(--bwz-accent); }
.bwz-body--signal .bwz-card__header,
.bwz-body--signal .bwz-card__footer { border-color: var(--bwz-rule); }

/* The wide measure. Dan's instruction of 2026-09-09; the reasoning and the
 * limits are on --bwz-measure-wide in section 3. Scoped to the marketing body
 * because .bwz-prose is also the dashboard's and the questionnaire's, and
 * neither of those asked for a longer line.
 *
 * To put it back: delete this block. To go halfway, change the token to 60rem.
 * Nothing else reads --bwz-measure-wide. */
@media (min-width: 90em) {
  .bwz-body--signal .bwz-prose,
  .bwz-body--signal .bwz-lede,
  .bwz-body--signal .bwz-measure { max-width: var(--bwz-measure-wide); }
  /* The hero is the exception, and it is not a compromise: a lede set across
   * 1280px directly under a heading capped at 22ch reads as two unrelated
   * blocks. The hero keeps the readable measure. */
  .bwz-body--signal .bwz-hero .bwz-lede,
  .bwz-body--signal .bwz-hero .bwz-prose,
  .bwz-body--signal .bwz-hero .bwz-measure { max-width: var(--bwz-measure); }
}

/* The step drawings on /how-we-work/ (869evwx03, 2026-09-09; redrawn and re-set
 * 869ezr12u, 2026-09-11). The icon is `currentColor` throughout, so it takes
 * the accent here and inverts with the theme like everything else — there is
 * no second colour to keep in step, and the illustration hues are deliberately
 * NOT available to it: they are graded against the hero gradient and this is
 * not on the hero.
 *
 * Dan: "these icons look really amateur, design something much more
 * professional please." Half of that was the drawings and is in
 * content/icons.py. This is the other half: a 2rem drawing floating in
 * whitespace above a heading looks unfinished however well it is drawn, so it
 * now sits in a tinted tile on the heading's own row.
 *
 * `--bwz-accent` on `--bwz-accent-surface` is an existing documented contrast
 * pair, so the tile introduced no new pair for core/tests.py to grade. */
.bwz-body--signal .bwz-step__head {
  display: flex;
  align-items: center;
  gap: var(--bwz-space-3);
}
.bwz-body--signal .bwz-step__mark {
  display: inline-flex;
  align-items: center;
  justify-content: start;
  flex: none;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: var(--bwz-radius-lg);
  border: var(--bwz-border) solid var(--bwz-accent);
  background: var(--bwz-accent-surface);
  color: var(--bwz-accent);
}
.bwz-body--signal .bwz-step__icon {
  width: 1.5rem;
  height: 1.5rem;
}

/* The tile is the first thing on a 320px line and the heading has to wrap
 * beside it rather than under it, so the heading gives up its balanced wrap —
 * `text-wrap: balance` on a narrow flex item produces two very short lines and
 * a ragged tile. */
@media (max-width: 30em) {
  .bwz-body--signal .bwz-step__mark {
    width: 2.25rem;
    height: 2.25rem;
  }
  .bwz-body--signal .bwz-step__icon {
    width: 1.25rem;
    height: 1.25rem;
  }
  .bwz-body--signal .bwz-step__head .bwz-h4 {
    text-wrap: initial;
  }
}

/* The hero drawing (869ezr12u, 2026-09-10). Dan asked for illustrations and
 * asked to compare three styles on three pages first, so three pages carry one
 * and everything else carries none. The drawing is inline SVG from
 * content/illustrations.py; the reasoning for that is in that module and is the
 * step icons' reasoning at a larger size.
 *
 * `--inner` is applied to EVERY marketing hero and `--split` only to the three,
 * so a page with no figure is a one-item grid: identical to the plain block
 * that was here before, which is what makes the trial reversible by deleting
 * three lines of Python and touching no CSS.
 *
 * No colour appears anywhere below, and none is wanted: the drawing is
 * `currentColor` throughout, and inside .bwz-hero that resolves to
 * --bwz-accent-fg — the one colour section 10c has already guaranteed against
 * every point of the gradient, in both themes. There is no second value to
 * keep in step and no new pair for core/tests.py to grade. */
.bwz-hero__inner {
  display: grid;
  gap: var(--bwz-space-7);
  align-items: center;
}

/* Under the text on a phone, never beside it: the first screen has to carry
 * the point on its own (content-drafting-rules section 4), and a decorative
 * drawing above the heading is scroll spent before the reader is told
 * anything. Source order puts the copy first, so this needs no rule — but the
 * width cap does, or a 400-unit-wide vector fills a 320px screen top to bottom. */
.bwz-hero__figure {
  justify-self: center;
  width: 100%;
  max-width: 26rem;
}

/* `height: auto` with the viewBox is what keeps the aspect ratio without a
 * hardcoded height per drawing, so a fourth style of a different shape needs
 * no rule of its own. */
.bwz-hero__art {
  display: block;
  width: 100%;
  height: auto;
  opacity: 0.92;
}

/* The five illustration hues (869ezr12u, 2026-09-11). Dan: "Blueprint is the
 * closest to being right, but I would like something MUCH more complex, more
 * colourful."
 *
 * [CRITICAL] `color`, not `fill` and not `stroke`. The drawings are
 * `currentColor` throughout — exactly as they were when there was only one
 * colour — so setting `color` on a group re-points every shape inside it and
 * content/illustrations.py still names no colour at all. Painting `fill` here
 * would half-work: a stroked shape in the group would keep the hero's white
 * and nothing would say which of the two had been meant.
 *
 * A class here with no drawing using it is harmless. A drawing using a class
 * with no rule here is silent — the group simply inherits the hero's
 * foreground and the drawing quietly loses a colour — so a test walks the
 * drawings and fails the build on one.
 *
 * These tokens are read here and nowhere else in the stylesheet, and a test
 * holds that too: three of the five are a green, an amber and a rose, and on
 * /dashboard/ those three colours mean "this check is failing". */
.bwz-hero__art .bwz-art--cyan { color: var(--bwz-illus-cyan); }
.bwz-hero__art .bwz-art--green { color: var(--bwz-illus-green); }
.bwz-hero__art .bwz-art--amber { color: var(--bwz-illus-amber); }
.bwz-hero__art .bwz-art--rose { color: var(--bwz-illus-rose); }
.bwz-hero__art .bwz-art--violet { color: var(--bwz-illus-violet); }

/* Two columns only once there is genuinely room for two. Below this the grid
 * stays single-column and the figure sits under the lede.
 *
 * `minmax(0, ...)` on the text column, not `1fr` alone: a grid item's automatic
 * minimum size is its content, so a long unbroken word in a heading would push
 * the column wider than the track and take the figure off the right edge. */
@media (min-width: 60em) {
  .bwz-hero__inner--split {
    grid-template-columns: minmax(0, 1fr) minmax(0, 24rem);
  }
  .bwz-hero__inner--split .bwz-hero__figure {
    max-width: none;
  }
}

/* ---------------------------------------------------------------------------
 * 11. Header, nav and footer
 * ------------------------------------------------------------------------- */

.bwz-header {
  border-bottom: var(--bwz-border) solid var(--bwz-rule);
  background: var(--bwz-bg);
  padding-block: var(--bwz-space-3);
}
.bwz-header__inner {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bwz-space-3);
}
/* The logo takes the free space so the nav and the controls stay together on
 * the right, whether or not there is a nav to show. An auto margin does that
 * for any number of children; `justify-content` did not, once there were
 * three of them. */
.bwz-header__inner > .bwz-logo { margin-right: auto; }

/* The nav wraps at every width the reader has JavaScript turned off at, and
 * collapses behind a disclosure below 48em when they have it. Ticket
 * 869evwwz8, on Dan's instruction: "Create a hamburger nav."
 *
 * [CRITICAL] The collapsed state is scoped to `:root[data-bwz-nav="js"]`, an
 * attribute set by bwz-nav.js and by nothing else. With no JavaScript there is
 * no attribute, so the nav is exactly the wrapping row it has always been and
 * the button that would hide it never appears. A menu whose only way open is a
 * script that did not load is a site with no navigation at all. */
.bwz-nav__list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bwz-space-1) var(--bwz-space-4);
  margin: 0;
  padding: 0;
  list-style: none;
}
.bwz-nav__link {
  display: inline-block;
  padding: var(--bwz-space-2) 0;
  color: var(--bwz-fg-muted);
  font-weight: var(--bwz-weight-medium);
  text-decoration: none;
  border-bottom: var(--bwz-border-thick) solid transparent;
}
.bwz-nav__link:hover { color: var(--bwz-fg); border-bottom-color: var(--bwz-rule-strong); }
/* Current page is marked by weight and a rule, not by colour alone. */
.bwz-nav__link[aria-current="page"] {
  color: var(--bwz-fg);
  font-weight: var(--bwz-weight-semibold);
  border-bottom-color: var(--bwz-accent);
}

/* The disclosure itself. It ships `hidden` in the markup and bwz-nav.js is
 * what reveals it, for the same reason the theme toggle does: a control that
 * cannot do anything must not be on the page. */
.bwz-nav-toggle {
  display: none;
  align-items: center;
  gap: var(--bwz-space-2);
  min-height: 2.75rem;
  padding: var(--bwz-space-2) var(--bwz-space-3);
  border: var(--bwz-border) solid var(--bwz-rule-strong);
  border-radius: var(--bwz-radius-md);
  background: var(--bwz-surface);
  color: var(--bwz-fg);
  font: inherit;
  font-size: var(--bwz-text-sm);
  font-weight: var(--bwz-weight-medium);
  cursor: pointer;
}
.bwz-nav-toggle:hover { border-color: var(--bwz-accent); }

/* Three bars, drawn in `currentColor` so they follow the button's text in both
 * themes and there is no colour to keep in step. */
.bwz-nav-toggle__bars {
  display: block;
  position: relative;
  width: 1.125rem;
  height: 0.75rem;
  flex: none;
}
.bwz-nav-toggle__bars span {
  position: absolute;
  left: 0;
  width: 100%;
  height: 2px;
  border-radius: 1px;
  background: currentColor;
  transition: transform var(--bwz-duration) var(--bwz-ease),
    opacity var(--bwz-duration) var(--bwz-ease);
}
.bwz-nav-toggle__bars span:nth-child(1) { top: 0; }
.bwz-nav-toggle__bars span:nth-child(2) { top: calc(50% - 1px); }
.bwz-nav-toggle__bars span:nth-child(3) { bottom: 0; }

/* Open: the bars become a cross. The middle one fades rather than moving, so
 * nothing has to travel behind anything else. */
.bwz-header--nav-open .bwz-nav-toggle__bars span:nth-child(1) {
  transform: translateY(calc(0.375rem - 1px)) rotate(45deg);
}
.bwz-header--nav-open .bwz-nav-toggle__bars span:nth-child(2) { opacity: 0; }
.bwz-header--nav-open .bwz-nav-toggle__bars span:nth-child(3) {
  transform: translateY(calc(1px - 0.375rem)) rotate(-45deg);
}

/* Collapsed navigation: below 48em, and only where the script ran.
 *
 * The panel is in normal flow on its own row rather than floating over the
 * page, so opening it pushes the page down instead of covering it. That is
 * what makes it safe with no trap, no overlay and no scroll lock -- three
 * things that go wrong on real phones and cannot be tested from here. */
@media (max-width: 47.99em) {
  :root[data-bwz-nav="js"] .bwz-nav-toggle { display: inline-flex; }

  :root[data-bwz-nav="js"] .bwz-nav {
    order: 3;
    flex-basis: 100%;
    display: none;
  }
  :root[data-bwz-nav="js"] .bwz-header--nav-open .bwz-nav { display: block; }

  :root[data-bwz-nav="js"] .bwz-nav__list {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding-top: var(--bwz-space-2);
  }
  /* Each link is its own row, at least 44px tall, with the rule under it
   * rather than under the text -- a whole-row target is what a thumb hits. */
  :root[data-bwz-nav="js"] .bwz-nav__link {
    display: block;
    padding: var(--bwz-space-3) 0;
    border-bottom: var(--bwz-border) solid var(--bwz-rule);
  }
  :root[data-bwz-nav="js"] .bwz-nav__link[aria-current="page"] {
    border-bottom-color: var(--bwz-accent);
  }
}

/* The header's sign-in / sign-out control. Ticket 869f06k66.
 *
 * [CRITICAL] <details> and <summary>, never a script -- the same rule the
 * runner cards follow in section 21. The header navigation is the one place on
 * this site where JavaScript may hide something, and it may only because
 * bwz-nav.js also CREATES the collapsed state it operates. Nothing here is
 * created by a script, so nothing here may be hidden by one: with JavaScript
 * off the panel is a native disclosure that opens on a click, and the two doors
 * are reachable.
 *
 * The panel floats rather than pushing the header taller, which is the opposite
 * call to the collapsed navigation two rules up. The reason they differ is
 * size: the nav panel is a full menu and covering the page with it needs a
 * focus trap and a scroll lock -- three things that go wrong on real phones --
 * while this is one short form, right-anchored, and clamped to the viewport so
 * it cannot reach an edge at 320px.
 *
 * It held two LINKS until 2026-09-12, when Dan's Q1(b) on 869f06k66 replaced
 * them with a real username and password box posting to /signin/. The two
 * `.bwz-account__door*` rules that styled the links went with them: a rule for a
 * component the project no longer renders is worse than no rule, because the
 * next person reads it as current.
 *
 * Layout and role tokens only. No hex, and no colour that means anything: this
 * renders on the operations dashboard too, where a colour is a check's status.
 */
.bwz-account {
  display: flex;
  align-items: center;
  gap: var(--bwz-space-2);
}

/* Signed in: who, then the way out. The name is clamped rather than allowed to
 * push the header controls apart -- a username has no maximum length. */
.bwz-account__who {
  max-width: 10rem;
  overflow: hidden;
  color: var(--bwz-fg-muted);
  font-size: var(--bwz-text-sm);
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Signed out: the disclosure. Sized like .bwz-nav-toggle on purpose, so the
 * three header controls are one row of one shape.
 *
 * .bwz-admin__summary (869f0kbp2) is the same shape and shares the rule rather
 * than copying it: two disclosures in one header row that drifted apart would
 * read as two different kinds of control. */
.bwz-account__summary,
.bwz-admin__summary,
.bwz-admin__link {
  display: inline-flex;
  align-items: center;
  gap: var(--bwz-space-2);
  min-height: 2.75rem;
  padding: var(--bwz-space-2) var(--bwz-space-3);
  border: var(--bwz-border) solid var(--bwz-rule-strong);
  border-radius: var(--bwz-radius-md);
  background: var(--bwz-surface);
  color: var(--bwz-fg);
  font-size: var(--bwz-text-sm);
  font-weight: var(--bwz-weight-medium);
  cursor: pointer;
  list-style: none;
}
.bwz-account__summary:hover,
.bwz-admin__summary:hover,
.bwz-admin__link:hover { border-color: var(--bwz-accent); }
/* An anchor, so it needs the two things a <summary> does not: the underline off
 * and the colour pinned, or a visited link in the header goes purple. */
.bwz-admin__link { text-decoration: none; }
.bwz-admin__link[aria-current="page"] {
  background: var(--bwz-surface-2);
  box-shadow: inset 0 -2px 0 0 var(--bwz-accent);
}
/* The default marker is a different glyph in every engine and cannot be
 * rotated. Ours is one span drawn with a border, exactly as .bwz-runner__arrow. */
.bwz-account__summary::-webkit-details-marker,
.bwz-admin__summary::-webkit-details-marker { display: none; }
.bwz-account__arrow {
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(-45deg);
  transition: transform var(--bwz-duration) var(--bwz-ease);
}
[open] > .bwz-account__summary .bwz-account__arrow { transform: rotate(45deg); }
@media (prefers-reduced-motion: reduce) {
  .bwz-account__arrow { transition: none; }
}

/* [CRITICAL] Anchored to the HEADER ROW, not to the control that opens it.
 *
 * `.bwz-account` was the containing block until the browser suite measured it:
 * below 48em the header wraps, the controls land on their own row at the LEFT,
 * and `right: 0` on a 90px summary put a 288px panel at x=-174 -- most of it
 * off the left edge of a 320px screen, with the whole control unreachable. The
 * bug the control exists to prevent, reproduced by the control. It was two doors
 * then and it is a login box now; the geometry is the same and so is the lesson.
 *
 * `.bwz-header__inner` is `.bwz-container`, so anchoring here means the panel
 * hangs from the bottom of the header at the reading gutter, at every width and
 * whatever the row above it did.
 * `test_the_open_sign_in_panel_stays_inside_a_320px_screen` in the browser suite
 * is what found it and what holds it. */
.bwz-account__panel {
  position: absolute;
  top: calc(100% + var(--bwz-space-3));
  right: var(--bwz-gutter);
  z-index: var(--bwz-z-overlay);
  width: 18rem;
  /* [CRITICAL] `100vw`, NOT `100%`, and it cost a browser-suite failure to learn.
   *
   * This clamp was `min(20rem, calc(100% - 2 * var(--bwz-gutter)))` until
   * 2026-09-12. A CLOSED <details> puts its content in a `content-visibility:
   * hidden` subtree, and Chrome resolves a percentage in there against a
   * ZERO-width containing block -- so `calc(100% - 48px)` went negative, clamped
   * to 0, and the panel laid out 34px wide (its padding) with the children
   * sticking out to x=1348 on a 1280px screen. The CONTENTS then failed
   * `test_the_page_does_not_scroll_sideways` on every page, at every width, in
   * both themes, while the panel itself measured fine and the document's
   * scrollWidth never moved.
   *
   * It did not bite the doors version because every child of that panel was
   * wrappable text, which simply reflowed inside 34px. The box's children are a
   * `white-space: nowrap` lockup and an `inline-flex` button, and neither can.
   *
   * `100vw` is the viewport and has nothing to collapse. It keeps the whole point
   * of the clamp -- the panel cannot reach an edge at 320px -- and it is the same
   * number at every width this site supports, because the panel is only 18rem
   * wide and the clamp bites below 336px of viewport. It ignores a desktop
   * scrollbar, which is harmless here for exactly that reason: at a width where
   * the clamp is doing anything there is no persistent scrollbar.
   *
   * The fix is NOT `overflow: hidden` on this panel. That would satisfy the
   * sweep's own "inside a box that scrolls on purpose" exemption while leaving
   * the layout wrong -- which is the masking that test's docstring warns about. */
  max-width: calc(100vw - 2 * var(--bwz-gutter));
  padding: var(--bwz-space-4);
  border: var(--bwz-border) solid var(--bwz-rule);
  border-radius: var(--bwz-radius-lg);
  background: var(--bwz-surface);
  box-shadow: var(--bwz-shadow-lg);
}

/* "show the Blueworkz logo" -- the lockup, above the fields. `.bwz-mark` and
 * `.bwz-logo__word` are the header's own, reused rather than copied: the mark has
 * a light and a dark file and the swap lives with those classes. */
.bwz-account__brand {
  display: flex;
  align-items: center;
  gap: var(--bwz-space-2);
  margin: 0 0 var(--bwz-space-2);
}

/* `.bwz-field`, `.bwz-label`, `.bwz-input` and `.bwz-btn` are the site's own form
 * components and this panel uses them unchanged, so a box in the chrome cannot
 * drift from the forms on the pages. The only thing the panel adds is the stack
 * rhythm, and `.bwz-stack` already owns that -- so there is nothing here for the
 * form itself. */

/* The header's admin menu. Ticket 869f0kbp2.
 *
 * Dan: "Put this in the header, left aligned, next to the blueworkz logo."
 *
 * [CRITICAL] It is a <details>, never a script -- the same rule as the sign-in
 * control above it and the runner cards in section 21. Nothing here is created
 * by JavaScript, so nothing here may be hidden by it.
 *
 * The arrow is section 23's generic `.bwz-arrow`, which is what that component
 * was drawn for: `details[open] > summary .bwz-arrow` matches any disclosure,
 * so this one needed no rotation selector of its own. The three older copies
 * are deliberately left where they are.
 *
 * WHERE THE AUTO MARGIN LIVES. `.bwz-header__inner > .bwz-logo` takes the free
 * space, which is what holds the nav and the controls together on the right.
 * "Next to the logo" means the menu has to sit on the logo's right with the
 * free space AFTER it, so the auto margin moves one element along -- and only
 * when there is a menu, because `show_admin_menu` is false for everybody but a
 * signed-in superuser and the header must be unchanged for them. The template
 * sets the modifier on the same flag that renders the menu, so the two cannot
 * disagree; `:has()` would have done it without a class and is not used,
 * because the previous major of Firefox in the supported matrix predates it.
 *
 * Layout and role tokens only. No hex, and no colour that means anything: this
 * renders on the operations dashboard too, where a colour is a check's status.
 */
.bwz-admin {
  display: flex;
  align-items: center;
}
.bwz-header__inner--admin > .bwz-logo { margin-right: 0; }
/* [CRITICAL] THE AUTO MARGIN IS ON THE BAR AND NOT ON THE MENU, since
 * 2026-10-15. Dan asked for "three buttons left to right which read Admin,
 * Runners, Cheat Sheet"; with `margin-right: auto` still on `.bwz-admin` the
 * two new anchors would have been pushed to the right-hand cluster with the
 * sign-in control, which is the one place he said they are not. It wraps,
 * because three buttons plus a logo plus a sign-in box is more than a 320px row
 * holds -- and it is FOUR since 2026-10-19 (869fjnmxf, `Dashboard` between Admin
 * and Runners), which needed no rule here at all: `flex-wrap` was already the
 * answer and `.bwz-admin__link` already shares the shape, the hover and the
 * `aria-current` state. The quote above is Dan's of 2026-10-15 and keeps its own
 * count; `core/tests.py:HEADER_BUTTONS` is where the bar's contents are declared. */
.bwz-admin-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bwz-space-2);
}
.bwz-header__inner--admin > .bwz-admin-bar { margin-right: auto; }

/* [CRITICAL] Anchored to the HEADER ROW, not to the summary that opens it --
 * the lesson `.bwz-account__panel` paid for in a 320px browser test. Below 48em
 * the header wraps and the controls move, and a panel anchored to a 90px
 * summary lands wherever that summary ended up. `.bwz-header__inner` is
 * `.bwz-container`, so this hangs from the bottom of the header at the reading
 * gutter at every width.
 *
 * `left`, where the sign-in panel uses `right`: this control is on the left of
 * the row and a panel that opened away from its own handle reads as somebody
 * else's. */
/* ⚠️ This still clamps on `100%`, where `.bwz-account__panel` had to move to
 * `100vw`: a closed <details> resolves a percentage against a zero-width box in
 * Chrome. It is not broken here because every child of this panel is wrappable
 * text, which reflows inside the collapsed width instead of overflowing it. Put
 * a `nowrap` lockup or an `inline-flex` button in here and it will be -- read the
 * note on `.bwz-account__panel` before adding one. */
.bwz-admin__panel {
  position: absolute;
  top: calc(100% + var(--bwz-space-3));
  left: var(--bwz-gutter);
  z-index: var(--bwz-z-overlay);
  width: max-content;
  max-width: min(20rem, calc(100% - 2 * var(--bwz-gutter)));
  padding: var(--bwz-space-4);
  border: var(--bwz-border) solid var(--bwz-rule);
  border-radius: var(--bwz-radius-lg);
  background: var(--bwz-surface);
  box-shadow: var(--bwz-shadow-lg);
}

/* A whole-block target, at least 44px tall, for the same reason the two doors
 * are: a thumb has to be able to hit it at 320px. */
.bwz-admin__item {
  display: block;
  padding: var(--bwz-space-3);
  border-radius: var(--bwz-radius-md);
  color: inherit;
  text-decoration: none;
}
.bwz-admin__item + .bwz-admin__item { margin-top: var(--bwz-space-2); }
.bwz-admin__item:hover { background: var(--bwz-surface-2); }
.bwz-admin__item:hover .bwz-admin__item-label,
.bwz-admin__item:focus-visible .bwz-admin__item-label { text-decoration: underline; }
.bwz-admin__item-label {
  display: block;
  font-weight: var(--bwz-weight-semibold);
}
.bwz-admin__item-note {
  display: block;
  color: var(--bwz-fg-muted);
  font-size: var(--bwz-text-sm);
}
/* Where you already are, marked by a rule and the background rather than by
 * colour alone -- the same call `.bwz-nav__link[aria-current]` makes. */
.bwz-admin__item[aria-current="page"] {
  background: var(--bwz-surface-2);
  box-shadow: inset 2px 0 0 0 var(--bwz-accent);
}

/* A group of items under a label (869f2a694, Dan 2026-09-16: "Create a new
 * admin area menu item, between Runners and Django admin, 'Clients'").
 *
 * A LABEL and an indent, not a nested <details>: core/tests.py reads this panel
 * by splitting on the first `</details>`, so a submenu here would truncate the
 * list of links three tests assert on and they would go on passing on the
 * shorter list. The template says the same thing at more length. */
.bwz-admin__group {
  margin-top: var(--bwz-space-4);
  padding: 0 var(--bwz-space-3);
  color: var(--bwz-fg-muted);
  font-size: var(--bwz-text-xs);
  font-weight: var(--bwz-weight-semibold);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
/* The indent is what says "these two belong to the label above". Padding
 * rather than a margin, so the hover background still spans the item. */
.bwz-admin__item--grouped { padding-inline-start: var(--bwz-space-5); }
/* The label is not an item, so the item-to-item margin rule above does not
 * reach the first link under it. */
.bwz-admin__group + .bwz-admin__item { margin-top: var(--bwz-space-1); }

.bwz-footer {
  flex-shrink: 0;
  margin-top: var(--bwz-space-7);
  border-top: var(--bwz-border) solid var(--bwz-rule);
  background: var(--bwz-surface-2);
  padding-block: var(--bwz-space-6);
  font-size: var(--bwz-text-sm);
  color: var(--bwz-fg-muted);
}
.bwz-footer__grid {
  display: grid;
  gap: var(--bwz-space-5);
  grid-template-columns: repeat(auto-fit, minmax(min(14rem, 100%), 1fr));
}
.bwz-footer__heading {
  font-size: var(--bwz-text-xs);
  font-weight: var(--bwz-weight-semibold);
  letter-spacing: var(--bwz-tracking-wide);
  text-transform: uppercase;
  color: var(--bwz-fg-subtle);
  margin-bottom: var(--bwz-space-2);
}
.bwz-footer__legal {
  font-size: var(--bwz-text-xs);
  color: var(--bwz-fg-subtle);
}
/* The rule above the legal line separates it from the rest of the footer, so
 * it must not appear when the legal line IS the rest of the footer -- which is
 * the case on every page until there is something to link to. */
.bwz-footer__legal:not(:first-child) {
  margin-top: var(--bwz-space-5);
  padding-top: var(--bwz-space-4);
  border-top: var(--bwz-border) solid var(--bwz-rule);
}
.bwz-footer__version { font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------------------------
 * 12. Forms
 * ------------------------------------------------------------------------- */

.bwz-form { max-width: var(--bwz-measure); }
.bwz-form > * + * { margin-top: var(--bwz-space-5); }

.bwz-fieldset { border: 0; padding: 0; margin: 0; }
.bwz-fieldset__legend {
  padding: 0;
  font-size: var(--bwz-text-lg);
  font-weight: var(--bwz-weight-semibold);
  margin-bottom: var(--bwz-space-3);
}

.bwz-field { display: block; }
.bwz-field > * + * { margin-top: var(--bwz-space-2); }

.bwz-label {
  display: block;
  font-weight: var(--bwz-weight-medium);
  font-size: var(--bwz-text-sm);
}
.bwz-label__optional { color: var(--bwz-fg-subtle); font-weight: var(--bwz-weight-normal); }
.bwz-label__required { color: var(--bwz-danger); }

.bwz-input,
.bwz-textarea,
.bwz-select {
  display: block;
  width: 100%;
  min-height: 44px;
  padding: var(--bwz-space-2) var(--bwz-space-3);
  background: var(--bwz-surface);
  color: var(--bwz-fg);
  border: var(--bwz-border) solid var(--bwz-field-border);
  border-radius: var(--bwz-radius-md);
  /* 16px minimum, or iOS Safari zooms the whole page on focus. */
  font-size: var(--bwz-text-base);
  transition: border-color var(--bwz-duration) var(--bwz-ease);
}
.bwz-textarea { min-height: 7rem; resize: vertical; line-height: var(--bwz-leading-normal); }

.bwz-select {
  appearance: none;
  padding-right: var(--bwz-space-7);
  /* Chevron drawn in the border colour so it flips with the theme. */
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 1.15rem) 55%, calc(100% - 0.8rem) 55%;
  background-size: 0.36rem 0.36rem, 0.36rem 0.36rem;
  background-repeat: no-repeat;
}

.bwz-input:hover,
.bwz-textarea:hover,
.bwz-select:hover { border-color: var(--bwz-rule-strong); }

.bwz-input::placeholder,
.bwz-textarea::placeholder { color: var(--bwz-fg-subtle); opacity: 1; }

.bwz-input[disabled],
.bwz-textarea[disabled],
.bwz-select[disabled] {
  background: var(--bwz-surface-2);
  color: var(--bwz-fg-subtle);
  cursor: not-allowed;
}

/* Error state is driven by aria-invalid, so the styling and the accessibility
 * information cannot get out of step. */
.bwz-input[aria-invalid="true"],
.bwz-textarea[aria-invalid="true"],
.bwz-select[aria-invalid="true"] {
  border-color: var(--bwz-danger);
  border-width: var(--bwz-border-thick);
}

.bwz-help { font-size: var(--bwz-text-sm); color: var(--bwz-fg-muted); }
.bwz-error {
  display: flex;
  gap: var(--bwz-space-2);
  font-size: var(--bwz-text-sm);
  font-weight: var(--bwz-weight-medium);
  color: var(--bwz-danger);
}
/* A glyph as well as the colour: never colour alone. */
.bwz-error::before { content: "\26A0"; }

.bwz-check {
  display: flex;
  align-items: flex-start;
  gap: var(--bwz-space-3);
  cursor: pointer;
}
.bwz-check + .bwz-check { margin-top: var(--bwz-space-3); }
.bwz-check__input {
  flex: none;
  width: 1.25rem;
  height: 1.25rem;
  margin: 0.2rem 0 0;
  accent-color: var(--bwz-accent);
}
.bwz-check__label { font-size: var(--bwz-text-base); }

/* The honeypot wrapper. Off-canvas rather than display:none, because a bot that
 * skips hidden fields is exactly the bot this is meant to catch. It is also
 * aria-hidden and tabindex=-1 in the markup: three separate ways to keep a
 * person out of a field whose only correct value is empty. */
.bwz-hp {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* ---------------------------------------------------------------------------
 * 13. Tables
 * ------------------------------------------------------------------------- */

/* A table wider than 320px scrolls inside this, not the page. tabindex="0" on
 * the wrapper in the markup makes that scroll reachable from the keyboard. */
.bwz-table-wrap {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  border: var(--bwz-border) solid var(--bwz-rule);
  border-radius: var(--bwz-radius-lg);
}
.bwz-table-wrap:focus-visible { outline-offset: 0; }

.bwz-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--bwz-text-sm);
  text-align: left;
}
.bwz-table caption {
  padding: var(--bwz-space-3) var(--bwz-space-4);
  text-align: left;
  font-size: var(--bwz-text-sm);
  color: var(--bwz-fg-muted);
}
.bwz-table th,
.bwz-table td {
  padding: var(--bwz-space-3) var(--bwz-space-4);
  border-bottom: var(--bwz-border) solid var(--bwz-rule);
  vertical-align: top;
}
.bwz-table thead th {
  background: var(--bwz-surface-2);
  font-weight: var(--bwz-weight-semibold);
  font-size: var(--bwz-text-xs);
  letter-spacing: var(--bwz-tracking-wide);
  text-transform: uppercase;
  color: var(--bwz-fg-muted);
  white-space: nowrap;
}
.bwz-table tbody tr:last-child td { border-bottom: 0; }
.bwz-table--striped tbody tr:nth-child(even) { background: var(--bwz-surface-2); }
.bwz-table--hover tbody tr:hover { background: var(--bwz-accent-surface); }
.bwz-table--compact th,
.bwz-table--compact td { padding: var(--bwz-space-2) var(--bwz-space-3); }
.bwz-table__num { text-align: right; font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------------------------
 * 14. Badges, status dots and callouts
 * ------------------------------------------------------------------------- */

.bwz-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--bwz-space-2);
  padding: 0.15rem var(--bwz-space-3);
  border-radius: var(--bwz-radius-full);
  background: var(--bwz-surface-3);
  color: var(--bwz-fg-muted);
  font-size: var(--bwz-text-xs);
  font-weight: var(--bwz-weight-semibold);
  letter-spacing: 0.02em;
  white-space: nowrap;
}
.bwz-badge--accent { background: var(--bwz-accent-surface); color: var(--bwz-accent); }
.bwz-badge--ok { background: var(--bwz-ok-surface); color: var(--bwz-ok); }
.bwz-badge--warn { background: var(--bwz-warn-surface); color: var(--bwz-warn); }
.bwz-badge--danger { background: var(--bwz-danger-surface); color: var(--bwz-danger); }

/* The traffic light. The dot is decorative; the word next to it carries the
 * meaning, because colour alone is not an accessible signal. */
.bwz-dot {
  width: 0.625rem;
  height: 0.625rem;
  border-radius: var(--bwz-radius-full);
  background: currentColor;
  flex: none;
}

.bwz-callout {
  display: flex;
  gap: var(--bwz-space-3);
  padding: var(--bwz-space-4) var(--bwz-space-5);
  border-radius: var(--bwz-radius-md);
  border-left: var(--bwz-space-1) solid var(--bwz-rule-strong);
  background: var(--bwz-surface-2);
  color: var(--bwz-fg);
}
.bwz-callout__body > * + * { margin-top: var(--bwz-space-2); }
.bwz-callout__title { font-weight: var(--bwz-weight-semibold); }
.bwz-callout--accent { border-left-color: var(--bwz-accent); background: var(--bwz-accent-surface); }
.bwz-callout--ok { border-left-color: var(--bwz-ok); background: var(--bwz-ok-surface); }
.bwz-callout--warn { border-left-color: var(--bwz-warn); background: var(--bwz-warn-surface); }
.bwz-callout--danger { border-left-color: var(--bwz-danger); background: var(--bwz-danger-surface); }

.bwz-divider {
  height: var(--bwz-border);
  border: 0;
  background: var(--bwz-rule);
  margin-block: var(--bwz-space-6);
}

/* ---------------------------------------------------------------------------
 * 15. Theme toggle
 * ------------------------------------------------------------------------- */

.bwz-theme-toggle {
  display: inline-flex;
  align-items: center;
  gap: var(--bwz-space-2);
  min-height: 36px;
  padding: var(--bwz-space-1) var(--bwz-space-3);
  border: var(--bwz-border) solid var(--bwz-rule-strong);
  border-radius: var(--bwz-radius-full);
  background: var(--bwz-surface);
  color: var(--bwz-fg-muted);
  font-size: var(--bwz-text-sm);
  font-weight: var(--bwz-weight-medium);
  cursor: pointer;
}
.bwz-theme-toggle:hover { color: var(--bwz-fg); border-color: var(--bwz-accent); }
/* Hidden until the script that drives it has run — a toggle that does nothing
 * is worse than no toggle. */
.bwz-theme-toggle[hidden] { display: none; }

/* ---------------------------------------------------------------------------
 * 16. Styleguide-only furniture. Prefixed bwz-sg- so it is obvious that none
 *     of this is part of the design system the site and dashboard consume.
 * ------------------------------------------------------------------------- */

.bwz-sg-banner {
  background: var(--bwz-warn-surface);
  color: var(--bwz-warn);
  border-bottom: var(--bwz-border) solid var(--bwz-warn);
  padding-block: var(--bwz-space-2);
  font-size: var(--bwz-text-sm);
  font-weight: var(--bwz-weight-semibold);
}

.bwz-sg-toc {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bwz-space-2) var(--bwz-space-4);
  padding: 0;
  margin: 0;
  list-style: none;
  font-size: var(--bwz-text-sm);
}

.bwz-sg-section { scroll-margin-top: var(--bwz-space-5); }
.bwz-sg-section + .bwz-sg-section { margin-top: var(--bwz-space-8); }
.bwz-sg-section__head {
  border-bottom: var(--bwz-border-thick) solid var(--bwz-rule);
  padding-bottom: var(--bwz-space-2);
  margin-bottom: var(--bwz-space-5);
}

.bwz-sg-demo {
  padding: var(--bwz-space-5);
  border: var(--bwz-border) dashed var(--bwz-rule-strong);
  border-radius: var(--bwz-radius-lg);
  background: var(--bwz-surface);
}
.bwz-sg-demo + .bwz-sg-demo { margin-top: var(--bwz-space-4); }
.bwz-sg-demo__label {
  font-size: var(--bwz-text-xs);
  letter-spacing: var(--bwz-tracking-wide);
  text-transform: uppercase;
  color: var(--bwz-fg-subtle);
  margin-bottom: var(--bwz-space-3);
}

.bwz-sg-swatch {
  display: flex;
  align-items: center;
  gap: var(--bwz-space-3);
  padding: var(--bwz-space-2);
  border: var(--bwz-border) solid var(--bwz-rule);
  border-radius: var(--bwz-radius-md);
  background: var(--bwz-surface);
}
.bwz-sg-swatch__chip {
  width: 2.75rem;
  height: 2.75rem;
  flex: none;
  border-radius: var(--bwz-radius-md);
  border: var(--bwz-border) solid var(--bwz-rule-strong);
}
.bwz-sg-swatch__name { font-weight: var(--bwz-weight-semibold); font-size: var(--bwz-text-sm); }
.bwz-sg-swatch__hex {
  font-family: var(--bwz-font-mono);
  font-size: var(--bwz-text-xs);
  color: var(--bwz-fg-muted);
  text-transform: uppercase;
}

.bwz-sg-scale { display: flex; align-items: baseline; gap: var(--bwz-space-4); flex-wrap: wrap; }
.bwz-sg-scale__token {
  font-family: var(--bwz-font-mono);
  font-size: var(--bwz-text-xs);
  color: var(--bwz-fg-subtle);
  min-width: 9rem;
}
.bwz-sg-space__bar {
  height: 1rem;
  background: var(--bwz-accent);
  border-radius: var(--bwz-radius-sm);
}
.bwz-sg-dont { color: var(--bwz-danger); font-weight: var(--bwz-weight-semibold); }
.bwz-sg-do { color: var(--bwz-ok); font-weight: var(--bwz-weight-semibold); }

/* A darkened photograph strip, to show the one place the white mark is used
 * over an image. No real photograph is shipped for this. */
.bwz-sg-photo {
  display: grid;
  place-items: center;
  min-height: 9rem;
  border-radius: var(--bwz-radius-lg);
  background:
    linear-gradient(rgba(15, 22, 38, 0.62), rgba(15, 22, 38, 0.62)),
    repeating-linear-gradient(45deg, #5b6b86 0 14px, #74849e 14px 28px);
}

/* ---------------------------------------------------------------------------
 * 17. Dashboard
 *
 * Layout only. Every colour here is a role token that section 2 already
 * defines -- there is no new hex in this section and there must never be one,
 * because the traffic light on the dashboard has to be the same green as the
 * badge on the styleguide or the two stop meaning the same thing.
 *
 * The status itself is painted by .bwz-badge--ok / --warn / --danger and
 * .bwz-dot, which existed before this page did. What is added is the tile's
 * status edge and two small typographic helpers, and nothing else: a dashboard
 * that grows its own component set is a second design system.
 * ------------------------------------------------------------------------- */

/* Tighter than .bwz-grid: a status tile carries a label, a badge and a
 * sentence, so it wants to be narrower than a marketing card. auto-fit with a
 * min() floor, so 320px falls out of it rather than needing a breakpoint. */
.bwz-dash-grid {
  /* The floor is a TOKEN since 869fcjex3, so that §17e can widen the two grids
   * that draw cards with a grey band without widening the systems and checks
   * grids, and so that the "ten cards in a row" cap there is derived from the
   * card's own width rather than typed beside it. 15rem is what it was. */
  --bwz-dash-card: 15rem;
  display: grid;
  gap: var(--bwz-space-4);
  grid-template-columns: repeat(auto-fit, minmax(min(var(--bwz-dash-card), 100%), 1fr));
}

/* The traffic light again, as an edge on the card. Redundant with the badge on
 * purpose: the badge carries the word for anyone who cannot rely on the
 * colour, and the edge is what makes a grid of twenty tiles scannable. */
.bwz-tile { border-left: var(--bwz-space-1) solid var(--bwz-rule-strong); }
.bwz-tile--ok { border-left-color: var(--bwz-ok); }
.bwz-tile--warn { border-left-color: var(--bwz-warn); }
.bwz-tile--danger { border-left-color: var(--bwz-danger); }
.bwz-tile--neutral { border-left-color: var(--bwz-rule-strong); }

/* Ages, intervals and counts. Tabular figures so a column of them lines up. */
.bwz-dash-meta {
  font-size: var(--bwz-text-xs);
  color: var(--bwz-fg-subtle);
  font-variant-numeric: tabular-nums;
}

/* Name/value pairs in a check's detail. The first column has a min() floor so
 * it collapses rather than pushing the page sideways at 320px, and the second
 * is minmax(0, 1fr) so a long URL wraps inside its cell instead of widening
 * the grid. No media query: the same rule as .bwz-grid. */
.bwz-dl {
  display: grid;
  grid-template-columns: minmax(min(9rem, 45%), auto) minmax(0, 1fr);
  gap: var(--bwz-space-1) var(--bwz-space-4);
  margin: 0;
}
.bwz-dl dt {
  color: var(--bwz-fg-muted);
  font-size: var(--bwz-text-sm);
}
.bwz-dl dd {
  margin: 0;
  font-size: var(--bwz-text-sm);
  overflow-wrap: anywhere;
}

/* ---------------------------------------------------------------------------
 * 17a. Dashboard graphics (869f03448)
 *
 * Dan's brief: "Across the whole management site, strongly prefer graphics over
 * text so for example a CPU or disc percentage should be displayed with graphs
 * or illustrations and not text, although text should usually be overlay, for
 * example a status bar showing 34% used."
 *
 * [CRITICAL] Section 17 says a dashboard that grows its own component set is a
 * second design system, and that is still true. What is added here is three
 * components and no fourth, every colour is a role token section 2 already
 * defines, and there is no new hex — because on this dashboard a colour MEANS
 * "this check is failing", and a graphic that invented a green would be a
 * monitoring defect rather than a styling choice.
 *
 * [CRITICAL] None of these rules decides a colour or a length. Both come from
 * the template, which gets them from ops/models.py:CheckState.meter() and
 * ops/rollup.py:tally_bar(). There is deliberately no `.bwz-meter--over-80`
 * here: ops/registry.py owns what counts as too much disk, and a bar that
 * graded itself would be a second authority disagreeing with the first one on
 * the same tile.
 *
 * Everything holds at 320px with no media query, which is the same bar the rest
 * of this file is held to.
 * ------------------------------------------------------------------------- */

/* A measured percentage, drawn, with its own number on top of it. The track has
 * a fixed height rather than a line-height so the bar is the same size whether
 * or not the overlaid figure wraps — which it cannot, hence the nowrap. */
.bwz-meter { width: 100%; }
.bwz-meter__track {
  position: relative;
  display: flex;
  align-items: center;
  min-height: 1.5rem;
  border-radius: var(--bwz-radius-sm);
  background: var(--bwz-surface-3);
  overflow: hidden;
}
.bwz-meter__fill {
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  /* Neutral is the floor, not a colour of its own: a fill with no status is a
   * fill that should not have been drawn, and ops/models.py:CheckState.meter
   * returns None for every such reading. This is here so a template bug shows
   * up as a grey bar rather than an invisible one. */
  background: var(--bwz-rule-strong);
}
.bwz-meter__fill--ok { background: var(--bwz-ok); }
.bwz-meter__fill--warn { background: var(--bwz-warn); }
.bwz-meter__fill--danger { background: var(--bwz-danger); }
.bwz-meter__fill--neutral { background: var(--bwz-rule-strong); }

/* The figure, over the bar. It sits above the fill in the stacking order and
 * takes the page's own foreground colour, never a colour chosen to contrast
 * with the fill: the fill is a status colour and the pairs in core/design.py are
 * what guarantee a readable combination in both themes. Inset from the start
 * edge so a bar at 0% still has its number beside it rather than off it. */
.bwz-meter__value {
  position: relative;
  z-index: 1;
  padding-inline: var(--bwz-space-2);
  font-size: var(--bwz-text-xs);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  color: var(--bwz-fg);
}

/* The mix of statuses in one card, as one strip. flex-grow carries the
 * proportion, so the segments always fill the row exactly and no width has to
 * be rounded — which is what stops a 91-check strip ending 0.3px short. */
.bwz-tallybar {
  display: flex;
  gap: 1px;
  height: var(--bwz-space-2);
  border-radius: var(--bwz-radius-sm);
  overflow: hidden;
}
.bwz-tallybar__seg { flex-basis: 0; min-width: 2px; background: var(--bwz-rule-strong); }
.bwz-tallybar__seg--ok { background: var(--bwz-ok); }
.bwz-tallybar__seg--warn { background: var(--bwz-warn); }
.bwz-tallybar__seg--danger { background: var(--bwz-danger); }
.bwz-tallybar__seg--neutral { background: var(--bwz-rule-strong); }

/* The few key readings on a product card. auto-fit with a min() floor for the
 * same reason .bwz-dash-grid uses one: 320px falls out of it rather than
 * needing a breakpoint, and a card with two metrics and a card with four lay
 * out on the same rule. */
.bwz-metrics {
  display: grid;
  gap: var(--bwz-space-3);
  /* [CRITICAL] EXACTLY TWO COLUMNS, AND IT IS NO LONGER auto-fit (869fcjex3,
   * Dan 2026-10-06: "Ensure that the the cards are always exactly 2 columns
   * wide at the top (e.g. Response time and certificate, or response time and
   * its own health check)" and "Ensure that all sections line up with their
   * corresponding section on all the other cards").
   *
   * 25.9.0 answered the FIRST half of this ask with a narrower auto-fit floor
   * (6rem instead of 7rem) and it was measured to work -- and it was still
   * wrong, because auto-fit asks the CONTAINER how many columns fit. MEASURED
   * at 25.10.0 in Chromium, same page, four viewports: the same card drew 2
   * columns at 1680px, 3 at 820px and 4 for BlueWorkz at every width. A
   * "always exactly 2" that depends on the window is not the thing he asked
   * for, and the row of readings moving under him as he resized is half of
   * what the screenshot on that ticket shows.
   *
   * `minmax(0, 1fr)` and never `1fr` alone: a grid track's automatic minimum is
   * its content, so a long figure in one column would widen that column past
   * its half and push the other one off the card -- which is the OTHER half of
   * what the screenshot shows. The zero floor is what makes the two columns
   * genuinely equal and keeps every reading inside the card. */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  margin: 0;
  padding: 0;
  list-style: none;
}
.bwz-metric {
  padding-inline-start: var(--bwz-space-3);
  border-inline-start: var(--bwz-border) solid var(--bwz-rule-strong);
  /* The figure beside it can be long and has no spaces to break on. Without
   * this the item's automatic minimum is that string and the track it is in
   * grows past its half of the card, which is the "information going missing
   * off the side" in 869fcjex3's screenshot. */
  min-width: 0;
}
/* [CRITICAL] ONE BAR PER LINE (869fcjex3, Dan 2026-10-06: "display the memory,
 * disc, CPU bars in one column format, i.e. bar per line").
 *
 * The class is put on by ops/_metrics.html in the SAME condition that draws the
 * bar, so the two can never disagree -- there is no CSS selector for "contains a
 * meter" and a second condition in Python would be a second place to be wrong.
 *
 * ⚠️ So a metric whose bar is NOT drawn -- stale, grey, never polled -- goes back
 * to half a row, and that is correct rather than a bug: ops/models.py's rule is
 * that a stale reading gets no bar at all, and a full-width row with no bar in it
 * is a reading-shaped space where there is no reading. The rule follows the bar
 * because the rule is ABOUT the bar. */
.bwz-metric--bar { grid-column: 1 / -1; }
.bwz-metric--ok { border-inline-start-color: var(--bwz-ok); }
.bwz-metric--warn { border-inline-start-color: var(--bwz-warn); }
.bwz-metric--danger { border-inline-start-color: var(--bwz-danger); }
.bwz-metric--neutral { border-inline-start-color: var(--bwz-rule-strong); }
/* [CRITICAL] THE LABEL AND THE STATUS WORD SHARE A LINE, and it is MEASURED
 * HEIGHT rather than taste (869fcjex3, Dan 2026-10-06: "This should enable the
 * whole card height to be reduced significantly across all the cards").
 *
 * Dropping the long bottom sentences gave two lines back per card; putting each
 * bar on a line of its own -- which he asked for in the same comment -- took
 * more than that away again, because a card with three bars went from one and a
 * half rows of readings to three. **Measured in Chromium at 1680px: 681px
 * before this ticket, 788px with both of those changes in.** So the height had
 * to come from somewhere else, and this is where it was: every reading drew its
 * name, then its number or bar, then its status word, on three stacked lines.
 * The name and the status word are both one short phrase and there is a whole
 * column of space between them.
 *
 * The DOM order is name, reading, status -- which is the order they are read in
 * and the order a screen reader gets -- so the status word is pulled up onto the
 * first line with `order` rather than by moving it in the template. The reading
 * takes a row of its own underneath.
 *
 * ⚠️ An OPENED pill still takes a row to itself: `.bwz-info[open]`'s own rule in
 * §22 is `flex: 1 0 100%` and this is a flex row, so a reading whose note is
 * open pushes the rest down instead of covering it. That is the same mechanism
 * .bwz-noteline uses and the reason this is flex rather than grid. */
.bwz-metric {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  column-gap: var(--bwz-space-2);
  row-gap: var(--bwz-space-1);
}
.bwz-metric > :first-child { order: 0; flex: 1 1 auto; min-width: 0; }
/* `0 1 auto` and `min-width: 0`, both MEASURED: with `0 0 auto` the status word
 * could not shrink, and Venue View's "Not configured" pill reached past the edge
 * of its card in Chromium and Firefox at every viewport. It shrinks and wraps
 * inside its own pill instead, which §17g's badge rule is what allows. */
.bwz-metric > .bwz-dash-meta { order: 1; flex: 0 1 auto; min-width: 0; }
/* ⚠️ `1 0 100%`, and a line that lets the BAR share its row with the label was
 * built, measured and REVERTED: `flex: 1 1 8rem` on the bar changed the card
 * height by 0px at 1680px in Chromium, so it bought a cramped bar for nothing.
 * The reading takes its own row, which is also what Dan's words say. */
.bwz-metric__bar,
.bwz-metric__figure { order: 2; flex: 1 0 100%; min-width: 0; }
.bwz-metric__label {
  display: block;
  font-size: var(--bwz-text-xs);
  color: var(--bwz-fg-muted);
}
.bwz-metric__figure {
  margin: 0;
  font-size: var(--bwz-text-lg);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}
/* A metric with no reading. Same size as a figure so the row does not jump when
 * a credential arrives, and muted so it does not read as a number. */
.bwz-metric__figure--none {
  font-size: var(--bwz-text-sm);
  font-weight: 500;
  color: var(--bwz-fg-muted);
}

/* An expandable section. <details>, not a script: a blocked script must never
 * be able to hide something on this page, which is the same rule the header
 * navigation follows in reverse (section 15). */
.bwz-disclosure {
  border: var(--bwz-border) solid var(--bwz-rule);
  border-radius: var(--bwz-radius-lg);
  background: var(--bwz-surface);
}
.bwz-disclosure__summary {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--bwz-space-2) var(--bwz-space-4);
  padding: var(--bwz-space-4) var(--bwz-space-5);
  cursor: pointer;
}
/* No :focus-visible rule here: section 6's global one already rings every
 * focusable element with the accent, and a second ring on one element is a
 * second thing to keep in step with the contrast pairs in core/design.py. */
.bwz-disclosure__body {
  padding: 0 var(--bwz-space-5) var(--bwz-space-5);
}

/* ---------------------------------------------------------------------------
 * 17b. The product card's grey footer, lined up across a row (869f2a694)
 *
 * Dan, 2026-09-15: "Make the bottom section of the product cards uniform
 * height so the grey parts line up across all cards."
 *
 * MEASURED, not guessed. Six product cards at 1680px are one row of cards of
 * EQUAL height whose footers were 75px, 111px, 115px and 151px tall. The band
 * is bottom-anchored (margin-top: auto, section 10), so a taller footer starts
 * HIGHER up its card, and four grey bands began at four different heights on
 * one row. The height varies because the contents vary: a link, sometimes an
 * Admin button, sometimes one or two shell windows, each wrapping onto a line
 * of its own in a card this narrow.
 *
 * [CRITICAL] The fix is SUBGRID, and deliberately NOT a height somebody typed.
 * A card is two rows of the products grid -- body, then footer -- so the footer
 * track is sized by the tallest footer on that row and every card's band is
 * exactly that. Nothing here is declared in pixels, so a seventh product, a
 * second shell button or a longer label re-aligns the row instead of
 * overflowing a constant, and the rule cannot go stale the way a number would.
 *
 * ⚠️ It cannot line cards up across TWO rows of the grid: each row sizes its
 * own tracks, so a card left alone on a second row still sizes its own band.
 * That is the honest limit of doing this in CSS, and the alternative -- a fixed
 * height -- buys the second row by making every band wrong the first time the
 * contents change.
 *
 * Scoped to .bwz-dash-grid--aligned. .bwz-dash-grid is also the systems grid
 * and the checks grid, whose tiles have no footer at all: a shared component
 * quietly changing shape under every other page is what CLAUDE.md section 6
 * calls a monitoring defect rather than a design change.
 *
 * @supports, because the fallback must be the OLD layout rather than half of
 * the new one -- display: grid with grid-row: span 2 and no subgrid would put a
 * card's body and its footer in two different implicit rows.
 * ------------------------------------------------------------------------- */

@supports (grid-template-rows: subgrid) {
  .bwz-dash-grid--aligned {
    /* The gap between the two tracks of ONE card is the same row-gap as the
     * gap between two rows of cards -- grid has one value for both -- and a
     * strip of card background between a card's body and its grey footer is
     * worse than what this rule is fixing. So the row gap moves onto the card
     * as a margin, where it separates cards and nothing else. */
    row-gap: 0;
  }
  .bwz-dash-grid--aligned > .bwz-card {
    grid-row: span 2;
    display: grid;
    grid-template-rows: subgrid;
    margin-bottom: var(--bwz-space-4);
  }
  /* The footer track is already the bottom of the card and a grid item
   * stretches into its track, which is what makes the GREY fill the band
   * rather than the card's own background showing through under a short one.
   * margin-top: auto belongs to the flex layout this replaces. */
  .bwz-dash-grid--aligned > .bwz-card > .bwz-card__footer { margin-top: 0; }
}

/* ---------------------------------------------------------------------------
 * 17c. The product card's action band, drill-down link and site icon
 *      (869f2a694, Dan 2026-09-16)
 *
 * [CRITICAL] A SEPARATE SECTION FROM 17b ON PURPOSE. ops/tests.py's
 * test_the_product_cards_line_their_grey_footers_up_and_do_it_with_subgrid
 * reads §17b as a block and asserts two things about every line in it: that no
 * rule declares a height, and that every selector is scoped to
 * .bwz-dash-grid--aligned. Both are right for the subgrid and wrong for what is
 * below, which is a 2rem favicon and a flex row that has to behave the same on
 * a product's own page where there is no aligned grid at all. Merging the two
 * sections makes that test either fail or have to be softened, and softening it
 * is how the typed band height comes back.
 *
 * Three rules, from one comment of Dan's:
 *
 *   "please move the ''Asset giant in detail' link to the bottom of the white
 *    section above the grey section. Also, reduce the width of the PowerShell
 *    buttons, so that free buttons, including the admin link button, should fit
 *    in a single (grey), one-row box."
 *
 *   "Duplicate the favicon site images to the top of the cards, this time not
 *    in buttons, literally just the favicons, on the same row as the card title
 *    but aligned right. Make them approx 2x higher than the favicons appear in
 *    the admin buttons (to which: make no change). Link the favicons to the
 *    front user-facing pages for each"
 *
 * The one-row band needed BOTH halves: moving the link out, and narrowing the
 * buttons (§24). Either alone leaves two rows.
 * ------------------------------------------------------------------------- */

/* The band, as ONE wrapping row of buttons (869f2a694, Dan 2026-09-16: "reduce
 * the width of the PowerShell buttons, so that free buttons, including the
 * admin link button, should fit in a single (grey), one-row box").
 *
 * Two things had to change together and neither works alone. The drill-down
 * link moved into the white half above (.bwz-card__drilldown), which is the
 * other half of the same instruction; and the band became a flex row, because
 * the shell buttons arrive inside .bwz-pwsh-group -- a block-level flex
 * container -- which took a line of its own under the Admin button at every
 * width. Narrowing the buttons on their own would have produced a narrower
 * second row, not one row.
 *
 * NOT scoped to --aligned: the same band is drawn on a product's own page,
 * where the cards are not a subgrid, and a row of buttons that behaved
 * differently in two places would be the defect this include exists to stop.
 * It is scoped to the MODIFIER, so .bwz-card__footer everywhere else in the
 * project is untouched.
 *
 * align-items: center rather than baseline: an Admin button and a picture of a
 * terminal have no shared baseline worth aligning to.
 */
.bwz-card__footer--actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bwz-space-2);
}
/* The last four pixels, and they were MEASURED before they were taken. At
 * 820px the products grid is three columns of 247px, which leaves a 194px
 * band, and the Admin button plus two shell windows plus their gaps came to
 * 194.4px -- so the band wrapped on a fraction of a pixel in Chromium and
 * Firefox while WebKit rounded the other way and did not. Halving the gap
 * between the button's favicon and its word gives 4px of headroom.
 *
 * ⚠️ SCOPED TO THE BAND. .bwz-btn--faviconned is also the Admin button on a
 * product's own page, where there is room and no reason to tighten it, and the
 * FAVICON ITSELF is untouched -- Dan's instruction of 2026-09-16 about the
 * admin buttons' icons was "to which: make no change". */
.bwz-card__footer--actions .bwz-btn--faviconned { gap: var(--bwz-space-1); }

/* The drill-down link, last in the white half. margin-top: auto pushes it to
 * the bottom of the body, so a card with three metrics and a card with six put
 * it in the same place relative to their grey band. .bwz-stack's own rule sets
 * the top margin; this only says which way the slack goes. */
.bwz-card__drilldown {
  margin-top: auto;
  padding-top: var(--bwz-space-2);
}
/* .bwz-stack sets margin-top on every child after the first, which would win
 * over the auto above. Restated at the same specificity the stack uses. */
.bwz-stack > .bwz-card__drilldown { margin-top: auto; }

/* [CRITICAL] …and NEITHER of those two rules did anything at all until this one
 * was written (869f2a694, Dan 2026-09-18: "the 'asset giant in detail' links
 * should be vertically aligned to the bottom of the white box which they now
 * correctly occupy, so that all of them line up just above the grey boxes").
 *
 * MEASURED BEFORE IT WAS WRITTEN, and measured again after. At 1680px the six
 * links ended at six different y values in Chromium, Firefox and WebKit alike;
 * the drill reports it per row of the grid.
 *
 * The reason is one line of CSS that was never there: `.bwz-card__body` is a
 * BLOCK box and `.bwz-stack` spaces its children with margins rather than with
 * flex, and `margin-top: auto` in block layout computes to ZERO. Nothing was
 * pushing anything anywhere. The body has to be a flex column before "give me
 * the slack above" is a sentence the engine can act on -- and the body already
 * stretches to the full height of its row, because §17b's subgrid makes it a
 * grid item in a track sized by the tallest body on the row. That is what turns
 * "bottom of my own card" into "the same line across the row", which is the
 * half of the ask a card on its own could never satisfy.
 *
 * :has(), so the rule pairs itself with the element it exists for instead of
 * with a class somebody has to remember to add to the next card. An engine
 * without :has() drops the whole rule and gets exactly the 14.12.0 layout --
 * the same shape of fallback §17b takes with @supports, and for the same
 * reason: half of this is worse than none of it.
 *
 * NOT scoped to --aligned. The white half of a card is the white half of a card
 * wherever it is drawn, and a link that sat at the bottom on the roll-up and
 * half way up on a product's own page would be the drift this file keeps
 * being fixed for. */
.bwz-card__body:has(> .bwz-card__drilldown) {
  display: flex;
  flex-direction: column;
}

/* The product's own favicon, top-right of the card's title row (869f2a694,
 * Dan 2026-09-16: "on the same row as the card title but aligned right. Make
 * them approx 2x higher than the favicons appear in the admin buttons (to
 * which: make no change)").
 *
 * 2rem against .bwz-btn__favicon's 1rem, which is the "approx 2x" literally and
 * is left as a separate rule so that changing one cannot change the other --
 * "to which: make no change" is an instruction about the button.
 *
 * [CRITICAL] NO STATUS COLOUR, no tile class and no border that could read as
 * one. Everything else on this card is a measurement and colour here means a
 * check is failing; a declared URL is not a reading. */
.bwz-card__titlerow {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--bwz-space-3);
}
.bwz-card__titlerow > :first-child { min-width: 0; }
.bwz-card__siteicon {
  flex: none;
  display: inline-flex;
  line-height: 0;
  border-radius: var(--bwz-radius-sm);
  text-decoration: none;
  transition: opacity var(--bwz-duration) var(--bwz-ease);
}
.bwz-card__siteicon:hover,
.bwz-card__siteicon:focus-visible { opacity: 0.75; }
.bwz-card__siteicon img {
  width: 2rem;
  height: 2rem;
  border-radius: 3px;
}
@media (prefers-reduced-motion: reduce) {
  .bwz-card__siteicon { transition: none; }
}

/* ---------------------------------------------------------------------------
 * 17d. What a COLLAPSED section is holding, on its own closed row
 *      (869f2a694, Dan 2026-09-18)
 *
 * "I do appreciate this rule, however I do want to see that the sections are
 *  all collapsed when I come to look at the page. I think the way to resolve
 *  this is to put on the collapsed card state, aligned to the right, the pills
 *  which show on the hidden cards."
 *
 * [CRITICAL] THIS IS THE PRICE OF THE REMEMBERED COLLAPSE, PAID RATHER THAN
 * ARGUED AWAY. bwz-disclosure.js may remember that a reader closed a section,
 * and the rule it was bought under is that a blocked script must never be able
 * to hide anything. A closed section that says nothing about its contents makes
 * that rule technically true and practically false. So the closed row now
 * carries the colours, and collapsing the page costs the detail rather than the
 * reading.
 *
 * Aligned right with an auto inline-start margin rather than with
 * space-between: the summary is a WRAPPING flex row of three other things, and
 * space-between would spread those three apart at every width instead of
 * pushing one group to the end.
 *
 * ⚠️ display: none here is on the OPEN state only, and it hides nothing: what
 * it drops is a DUPLICATE of pills that are on screen a few pixels below it.
 * With no stylesheet at all both are shown, which is the right way round. No
 * rule in this file may ever hide the contents of a section.
 * ------------------------------------------------------------------------- */

.bwz-disclosure__pills {
  margin-inline-start: auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bwz-space-2);
}
.bwz-disclosure[open] > .bwz-disclosure__summary > .bwz-disclosure__pills { display: none; }

/* ---------------------------------------------------------------------------
 * 17e. TEN CARDS IN A ROW IS A PAGE WIDTH, AND THE SECTION BORDER GOES WITH IT
 *      (869fcjex3, Dan 2026-10-06; CORRECTED by 869fczpww, Dan 2026-10-08)
 *
 *   "allow the row of products cards to hold up to 10 cards in one horizontal
 *    row if the screen width allows it. To be clear, do not change the existing
 *    width of the cards, just put more on one row if the screen is wide enough"
 *                                                                -- 869fcjex3
 *
 *   "the products card now allows up to 10 cards per row (great) but the card
 *    border background doe NOT - so it looks wrong. Please fix. Also the other
 *    sections stop at 5 rows (e.g. Environments) - make sure they all also can
 *    have 10 in a row, if screen width allows. All content on the page should
 *    eb correctly formatted so it all lines up left with all the other content"
 *                                                                -- 869fczpww
 *
 * [CRITICAL] THE FIRST ANSWER WIDENED ONE GRID, AND A GRID WAS THE WRONG
 * ELEMENT. `.bwz-dash-grid--spread` gave the products grid a width of its own --
 * `max(100%, min(ten-card-cap, 100vw - 4 * gutter))` -- plus a negative right
 * margin to break out of the page container. It did break out of the container.
 * It also broke out of the SECTION CARD, because `details#products` is the
 * bordered white box and the grid is inside it: the cards marched out of their
 * own border into the page's white space, which is what Dan is looking at.
 *
 * MEASURED off his own screenshot (3839px wide, 2026-10-08): every section's
 * border ran 957 -> 2882 and the products row ran 957 -> 3360. **One left edge,
 * and 478px of cards outside the box.**
 *
 * And it left every OTHER section at the container, which is five of these cards
 * and no more -- *"the other sections stop at 5 rows"*, exactly and arithmetically.
 *
 * [CRITICAL] SO THE WIDTH BELONGS TO THE PAGE AND NOT TO ONE GRID. The roll-up
 * asks for a container as wide as ten cards; every section card, every grid
 * inside one, the heading above them and the tables below then share one width
 * and one left edge, which is the whole of his third sentence. **The breakout is
 * DELETED rather than moved**: no negative margin, no `100vw` arithmetic, and
 * therefore no scrollbar to subtract -- `width: 100%` of the page is already the
 * viewport minus the scrollbar in every engine, which is what that `- 4 * gutter`
 * fudge existed to approximate.
 *
 * ⚠️ IT IS DERIVED FROM THE CARD, which is this section's own rule unchanged:
 * change `--bwz-dash-card-wide` and the page width follows. A typed `3074px`
 * would be wrong the first time a card changed width.
 *
 * ⚠️ THE SUM IS THE ROW PLUS THE BOX AROUND IT, and leaving any term out costs
 * the tenth card -- which is this very fault, one level further in. See
 * `--bwz-container-dash` in §3.
 *
 * ⚠️ NOTHING CHANGES BELOW THE CAP. It is a `max-width` on a `width: 100%` box,
 * so on any screen narrower than the sum this is the page it always was: no
 * breakpoint and no media query, the shape `.bwz-dash-grid` itself uses.
 *
 * ⚠️ THE HEADER IS NOT WIDENED AND THAT IS DELIBERATE. `.bwz-header__inner` is
 * `.bwz-container` (96rem) and is shared with the marketing site, so widening it
 * would restyle every public page to fix a dashboard. The two already differed
 * before this change (112rem against 96rem) and they differ by more now; it is
 * reported on the ticket rather than fixed by a session on its own authority.
 * ------------------------------------------------------------------------- */

/* The card's own width, on the grids that draw cards with a grey band. The
 * token moved to §3 in 869fczpww because the page container is derived from it
 * now; the MEASUREMENT that chose the number stays here, with the rule.
 *
 * 15rem -> 16rem was "very slightly wider" (869fcjex3, first round). **18rem is
 * the second round and it is MEASURED rather than chosen**: with §17g holding
 * the width still, the readings grid is exactly two columns and a column of a
 * 16rem card is ~104px, which is narrower than the words "Not configured" beside
 * a dot. Measured in Chromium at 1680px, one page, three candidate widths:
 * **16rem -> a 738px card** with those pills wrapped onto two lines, **18rem ->
 * 684px**, **20rem -> 684px**. 18rem is the narrowest width at which nothing
 * wraps, which is Dan's own test for it -- "wide enough to display that top row
 * without cutting off any of it" -- and 20rem buys no height for the cards per
 * row it costs. */
.bwz-dash-grid--aligned { --bwz-dash-card: var(--bwz-dash-card-wide); }

/* ---------------------------------------------------------------------------
 * 17g. A CARD WHOSE WIDTH DOES NOT MOVE, and sections that line up
 *      (869fcjex3, Dan 2026-10-06 -- his revision of his own earlier ask)
 *
 *   "Ensure that the card width stays fixed, wide enough to display that top
 *    row without cutting off any of it, and the card with does not change when
 *    the screen width is changed. The only change will be to move the cards 1
 *    by 1 into a narrower grid pattern as the screen gets narrower. The card
 *    width itself should never change (unless the screen is only one card wide
 *    and is narrowed, in that circumstance reduced the card width to ensure the
 *    full card fits on the full screen width)"
 *
 *   "Ensure that all sections line up with their corresponding section on all
 *    the other cards."
 *
 *   "Make sure to avoid what is shown in the screenshot below, IE card width
 *    being compressed when the screen width is smaller and some of the
 *    information going missing off the side."
 *
 * [CRITICAL] THE CULPRIT WAS THE `1fr`, AND IT WAS MEASURED BEFORE IT WAS
 * REMOVED. `repeat(auto-fit, minmax(min(16rem, 100%), 1fr))` fits as many
 * columns as it can and then STRETCHES them to share whatever is left, so the
 * card width is `container / columns` and moves every time the column count
 * changes. Measured in Chromium on the live page at 25.10.0, one card, five
 * viewports: 307px at 1680, **259px at 1440**, 340px at 1100, 378px at 820 and
 * 348px at 380. It got NARROWER as the window got wider, which is the sentence
 * Dan's screenshot is a picture of.
 *
 * `repeat(auto-fit, min(var(--bwz-dash-card), 100%))` is a FIXED track instead:
 * every card is exactly one card wide at every viewport, the only thing that
 * changes is how many fit, and `min(…, 100%)` is the one exception he named --
 * when the container is narrower than one card the track becomes the container,
 * so the whole card still fits the screen. No breakpoint and no media query,
 * which is the shape .bwz-dash-grid has always used.
 *
 * ⚠️ `justify-content: center` because fixed tracks no longer fill the row: the
 * remainder has to go somewhere, and a row of cards hugging the left of a 2700px
 * breakout with 400px of white beside it reads as a layout that failed.
 *
 * ⚠️ IT IS ON --aligned, which is the three grids that draw CARDS, and never on
 * .bwz-dash-grid -- that is also the systems, checks and runners grids, whose
 * tiles carry a label, a badge and a sentence and are meant to fill their row.
 * ------------------------------------------------------------------------- */
.bwz-dash-grid--aligned {
  grid-template-columns: repeat(auto-fit, min(var(--bwz-dash-card), 100%));
  /* [CRITICAL] `start` AND NOT `center` (869fcjex3, Dan 2026-10-06: "Ensure
   * that all sections are aligned left, so the asset giant card lines up with
   * the backups and restore card, which lines up with the development and
   * staging card in environments").
   *
   * Fixed tracks no longer fill their row, so the remainder has to go
   * somewhere, and `center` was chosen when the products row was the only grid
   * being looked at -- a row of cards hugging the left of a wide breakout with
   * white beside it "reads as a layout that failed". Measured against the whole
   * page instead, centring is what made it fail: three grids with different
   * card counts centre to three different left edges, so no card in one section
   * lines up with any card in another, and neither lines up with the headings
   * above them.
   *
   * `start` is the only value at which the first card of every grid starts at
   * the page's own left edge, whatever each grid happens to hold. The leftover
   * space goes to the right of the last card, where it is slack rather than an
   * offset. ../blueworkz-tests/drills/product_card_footer_drill.py measures the
   * three sections' first-card left edges against each other. */
  justify-content: start;
}

/* [CRITICAL] THE BOTTOM BLOCK IS BOTTOM-ANCHORED, which is how "all sections
 * line up" is kept without a subgrid of its own.
 *
 * Every section above the headline is one line or a block that starts at the
 * same place on every card -- title, pill, tally bar, then the readings -- so
 * those line up by construction. The only variable is the NUMBER of readings,
 * and everything under it used to float up by however many a card happened to
 * have: five on Asset Giant, two on Venue View.
 *
 * §17b already makes every card body on a row one height, so pushing the slack
 * ABOVE the headline puts the headline and the drill-down link at the same y on
 * every card in the row. That is the same mechanism the drill-down has used
 * since 14.12.0 and it is applied one element earlier.
 *
 * ⚠️ TWO `margin-top: auto` siblings SPLIT the free space between them, which
 * would leave the headline floating in the middle of the gap. So the drill-down's
 * own auto is overridden back to an ordinary gap INSIDE this grid, at the
 * specificity .bwz-stack's rule uses plus one class. Outside an aligned grid --
 * a product's own page -- nothing here applies and the drill-down keeps its auto.
 *
 * ⚠️ A SUBGRID PER SECTION WAS CONSIDERED AND REJECTED, and the reason is
 * mechanical rather than aesthetic: _tally_bar.html renders NOTHING when a card
 * has no segments, so the number of children of .bwz-card__body is not a
 * constant, and `grid-row: span <n>` with a variable child count silently puts a
 * card's sections in the wrong tracks. A rule that is wrong the first time a
 * card has no readings is not the rule to hold an alignment with. */
.bwz-dash-grid--aligned .bwz-card__headline { margin-top: auto; }
.bwz-dash-grid--aligned .bwz-stack > .bwz-card__drilldown { margin-top: var(--bwz-space-2); }
/* [CRITICAL] …AND THE LINK UNDER IT IS ONE ROW TOO, or the line above it moves.
 * MEASURED: with the link free to wrap, "Forever Colours Hospice in detail" took
 * two lines on a 16rem card and pushed that card's bottom line 26px higher than
 * its neighbour's -- in all three engines, at every viewport where the two
 * shared a row. Everything under a bottom-anchored element inherits that
 * element's height, so "all sections line up" needs the LAST one to be a
 * constant number of rows rather than a constant height.
 *
 * ⚠️ IT IS §17f's CLIP, AND CLIPPING IS NOT TRUNCATING. The whole label is still
 * in the markup -- selectable, searchable, read out in full by a screen reader,
 * and the link still goes where it went. What changes is how much of an intact
 * string is painted. Cutting the text in Python would delete words, which is the
 * rule rules/runner.md states in capitals.
 *
 * ⚠️ It is also SELF-CORRECTING rather than fitted to today's estate: on a wider
 * card nothing clips at all, and the day a product gets a longer name this moves
 * no other card. A `min-height` reserving a second line would have been a typed
 * constant that is wrong the first time the contents change -- §17b's own rule.
 *
 * ⚠️ Scoped to the aligned grids. On a product's own page the link has a whole
 * page's width and there is nothing to line it up with. */
.bwz-dash-grid--aligned .bwz-card__drilldown {
  min-width: 0;
  overflow: hidden;
}
.bwz-dash-grid--aligned .bwz-card__drilldown > a {
  display: block;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* [CRITICAL] NOTHING INSIDE A CARD MAY REACH PAST ITS EDGE (the second half of
 * the screenshot: "some of the information going missing off the side").
 *
 * A flex or grid item's automatic minimum size is its CONTENT, so one long
 * unbreakable string -- a check label with a URL in it, a figure, an em-dashed
 * sentence inside an opened pill -- makes its own box wider than the card and
 * the card wider than its track. `min-width: 0` is what stops that, and
 * `overflow-wrap: anywhere` is what makes the string wrap once it has to.
 *
 * It is scoped to the aligned grids. The same markup on a product's own page has
 * a whole page's width and nothing to overflow. */
.bwz-dash-grid--aligned .bwz-card { min-width: 0; }
.bwz-dash-grid--aligned .bwz-card__body { min-width: 0; }
.bwz-dash-grid--aligned .bwz-cluster { min-width: 0; }
.bwz-dash-grid--aligned .bwz-info { min-width: 0; max-width: 100%; }
.bwz-dash-grid--aligned .bwz-info__panel { max-width: 100%; }
.bwz-dash-grid--aligned .bwz-pillnote__item { min-width: 0; overflow-wrap: anywhere; }
/* [CRITICAL] A PILL IS A FIXED PHRASE IN A BOX THAT IS NOW HALF A CARD WIDE, and
 * MEASURED, that is what overflowed first. `.bwz-badge` is `white-space: nowrap`
 * everywhere in the project -- right for a badge in a page-width row -- and
 * "Not configured" beside a dot and a disclosure arrow is wider than one column
 * of the readings grid. Venue View's ticket-runner reading drew it in Chromium
 * and Firefox at every viewport, the <summary> and its arrow reaching past the
 * card's own edge.
 *
 * ⚠️ IT IS WRAPPED, NEVER SHORTENED. The word on a traffic light is the half of
 * it that does not rely on colour (CLAUDE.md §6), so clipping it is the one
 * thing that must not happen here; two lines inside the pill is the cost and it
 * is the right one. Scoped to the aligned grids, so a badge anywhere else in the
 * project keeps the single line it was designed for. */
.bwz-dash-grid--aligned .bwz-info > summary { max-width: 100%; flex-wrap: wrap; }
.bwz-dash-grid--aligned .bwz-badge { max-width: 100%; white-space: normal; }

/* ---------------------------------------------------------------------------
 * 17h. One traffic light per system that has a backup policy
 *      (869fcjex3, Dan 2026-10-06)
 *
 *   "I want clear traffic lights style pills that show each system and whether
 *    it is OK, with OK being the backups running, and the restore test (if set
 *    up) running successfully on schedule."
 *
 * A list, one system per row: the name, its own pill, and the one line that
 * says where the evidence came from and whether a restore test is part of the
 * light. The provenance line is NOT optional styling -- ops/rollup.py's
 * BackupPolicyView renders it at every colour, because three identical green
 * pills in a row would flatten CLAUDE.md §1's measured/attested marks.
 *
 * ⚠️ NO COLOUR IS DECLARED HERE. The light is _status.html's badge, which is the
 * same green as every other light on this page; the row's own left edge takes
 * the role token the card's tile edge already uses. A second grading vocabulary
 * on this page is what CLAUDE.md §6 calls a monitoring defect.
 * ------------------------------------------------------------------------- */
.bwz-syslights {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--bwz-space-2);
}
/* FLEX and not grid, and the reason is the opened note. .bwz-info[open]'s own
 * rule in §22 is `flex: 1 0 100%` -- it takes the row it is on -- and that only
 * fires if the pill is a direct flex child of this row. In a two-column grid the
 * panel would be trapped in the narrow column and drawn one word per line. So
 * the pill is included straight into this row with no wrapper, exactly as
 * .bwz-noteline does it. */
.bwz-syslights__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bwz-space-1) var(--bwz-space-2);
  padding-inline-start: var(--bwz-space-3);
  border-inline-start: 3px solid var(--bwz-rule-strong);
  min-width: 0;
}
.bwz-syslights__row--ok { border-inline-start-color: var(--bwz-ok); }
.bwz-syslights__row--warn { border-inline-start-color: var(--bwz-warn); }
.bwz-syslights__row--danger { border-inline-start-color: var(--bwz-danger); }
.bwz-syslights__row--neutral { border-inline-start-color: var(--bwz-rule-strong); }
.bwz-syslights__name {
  flex: 1 1 auto;
  font-weight: var(--bwz-weight-semibold);
  min-width: 0;
  overflow-wrap: anywhere;
}
/* The provenance, under both, taking the whole row. It is prose about where the
 * evidence came from, so it belongs beside the light rather than behind a
 * marker somebody has to open -- which is exactly what Dan called insufficient
 * about the note this replaced. */
.bwz-syslights__note { flex: 1 0 100%; min-width: 0; }

/* ---------------------------------------------------------------------------
 * 17f. The product card's headline, clipped to ONE ROW (869fcjex3, 2026-10-06)
 *
 *   "in the products cards, never have more than one row of text at the bottom"
 *
 * [CRITICAL] CLIPPING IS NOT TRUNCATING, AND THE DIFFERENCE IS WHY THIS IS CSS
 * AND NOT PYTHON. `ops/rollup.py:ProductView.headline_short` composes a short
 * sentence out of measured facts; this guarantees the ONE ROW for every card
 * including one whose worst check has no declared short name. What it does is
 * decide how much of an intact string is painted. Every word is still in the
 * markup -- selectable, searchable, read out in full by a screen reader, and
 * rendered again in the note and the hover title beside it. A `[:40]` in Python
 * would have deleted the words instead, which is the rule
 * rules/runner.md states in capitals.
 *
 * `min-width: 0` on the flex item, because a flex child's automatic minimum
 * size is its content and an overflow rule on it does nothing without this --
 * the line would simply widen the card. .bwz-noteline is the flex row (§22).
 * ------------------------------------------------------------------------- */
.bwz-card__headline {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* ---------------------------------------------------------------------------
 * 17i. A SECTION'S OWN LIGHT ON ITS OWN ROW, OPEN OR CLOSED (869fk156w,
 *      Dan 2026-10-19)
 *
 *   "put each product such as asset giant, just granted, campaign planner call
 *    my etc into its own expandable section, collapsed by default. Put the pill
 *    such as OK or warning in the titles I see it even when the card is
 *    collapsed."
 *
 * Aligned to the end with an auto inline-start margin, which is §17d's own
 * answer and for §17d's own reason: the summary is a WRAPPING flex row of four
 * other things, and `space-between` would spread all four apart at every width
 * instead of pushing one to the end.
 *
 * [CRITICAL] AND IT IS A SEPARATE CLASS FROM `.bwz-disclosure__pills` PRECISELY
 * BECAUSE IT MUST NOT INHERIT THAT ONE'S HIDE-WHEN-OPEN RULE. §17d hides the
 * tally on the open state and says in terms why that hides nothing: "what it
 * drops is a DUPLICATE of pills that are on screen a few pixels below it". That
 * argument does not reach this element. A `.bwz-disclosure__pills` tally is a
 * COUNT of the pills inside the section; this is the section's OWN rolled-up
 * light, and it is drawn nowhere else on the page -- so hiding it on the open
 * state would take a reading off the page rather than a duplicate of one, and
 * Dan asked to see it. **There is no `[open]` rule here and adding one would
 * delete a measurement.**
 *
 * ⚠️ IT INTRODUCES NO COLOUR. The pill inside it is `_status.html`'s plain call
 * shape drawn from `rollup.EnvironmentRow.badge_class` and
 * `EnvironmentGroup.badge_class` -- the same `badge_class_for()` every other
 * light on this dashboard uses -- so there is no new token pair for
 * `core/design.py`'s contrast sweep to grade and no second definition of what
 * colour a product is. This rule is four lines of layout.
 * ------------------------------------------------------------------------- */

.bwz-disclosure__light {
  margin-inline-start: auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bwz-space-2);
}

/* ---------------------------------------------------------------------------
 * 20. The discovery questionnaire
 *
 * A private, link-only page (content/discovery_views.py), so none of this is
 * reachable from the marketing site and none of it is reused by it. It is here
 * rather than in a second stylesheet because there is one stylesheet, and
 * because every rule below is built out of the tokens in section 2 — a page
 * somebody answers at night has to work in both themes like everything else.
 *
 * Everything holds at 320px with no media query. The section tabs scroll
 * inside themselves rather than widening the page, which is the same rule
 * .bwz-table-wrap follows.
 * ------------------------------------------------------------------------- */

.bwz-quest { max-width: 52rem; }
.bwz-quest > * + * { margin-top: var(--bwz-space-4); }

/* The section tabs. Submit buttons, not links: clicking one saves the section
 * being left rather than discarding it, so they have to be part of the form. */
.bwz-quest__steps {
  display: flex;
  gap: var(--bwz-space-1);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  border-bottom: var(--bwz-border) solid var(--bwz-rule);
  scrollbar-width: thin;
  /* [CRITICAL] `overflow-x: auto` alone is not enough here, and the reason is
   * worth writing down because it looks like a bug in the test rather than in
   * the page. `.bwz-body` sets `overflow-x: hidden`, and because <html> is
   * `visible` that value is *propagated to the viewport* -- which leaves the
   * body itself computing as `visible`. The fourteen tabs then contribute their
   * full 2.7 metres to the document's scrollable overflow, so
   * `documentElement.scrollWidth` reads 2570 in a 768px viewport even though
   * `window.scrollX` cannot move off zero. `test_the_page_does_not_scroll_
   * sideways` measures exactly that number, correctly, and would fail.
   * `contain: paint` ends the propagation at this box: nothing changes visually
   * and the tabs still scroll. Measured in Chromium on 2026-09-04; `max-width`
   * and `width: 100%` both make no difference, and clipping an ancestor does
   * not help either. */
  contain: paint;
}
.bwz-quest__step {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: var(--bwz-space-2);
  min-height: 44px;
  padding: var(--bwz-space-2) var(--bwz-space-3);
  border: 0;
  border-bottom: var(--bwz-border-thick) solid transparent;
  background: none;
  color: var(--bwz-fg-muted);
  font: inherit;
  font-size: var(--bwz-text-sm);
  white-space: nowrap;
  cursor: pointer;
}
.bwz-quest__step:hover { color: var(--bwz-fg); }
/* aria-current, not a class: the state a screen reader is told and the state a
 * sighted reader sees are then the same fact. */
.bwz-quest__step[aria-current="step"] {
  color: var(--bwz-accent);
  border-bottom-color: var(--bwz-accent);
  font-weight: var(--bwz-weight-semibold);
}

/* The ring carries a shape as well as a colour: never colour alone. */
.bwz-quest__ring {
  flex: none;
  display: grid;
  place-items: center;
  width: 1.1rem;
  height: 1.1rem;
  border: var(--bwz-border-thick) solid var(--bwz-rule-strong);
  border-radius: var(--bwz-radius-full);
  font-size: 0.6rem;
  line-height: 1;
  color: transparent;
}
.bwz-quest__ring[data-done="part"] {
  border-color: var(--bwz-warn);
  background: var(--bwz-warn-surface);
}
.bwz-quest__ring[data-done="all"] {
  border-color: var(--bwz-ok);
  background: var(--bwz-ok);
  color: var(--bwz-bg);
}

.bwz-quest__bar {
  height: 4px;
  border-radius: var(--bwz-radius-full);
  background: var(--bwz-surface-3);
  overflow: hidden;
}
.bwz-quest__bar > span {
  display: block;
  height: 100%;
  background: var(--bwz-accent);
  transition: width var(--bwz-duration) var(--bwz-ease);
}

.bwz-quest__meta {
  display: flex;
  align-items: center;
  gap: var(--bwz-space-3);
  margin: 0;
  font-size: var(--bwz-text-sm);
  color: var(--bwz-fg-muted);
  font-variant-numeric: tabular-nums;
}
.bwz-quest__saved {
  padding: 0.1rem var(--bwz-space-3);
  border-radius: var(--bwz-radius-full);
  background: var(--bwz-ok-surface);
  color: var(--bwz-ok);
  font-size: var(--bwz-text-xs);
  font-weight: var(--bwz-weight-semibold);
}

.bwz-quest__head { margin-bottom: var(--bwz-space-5); }
.bwz-quest__eyebrow {
  margin: 0;
  font-family: var(--bwz-font-mono);
  font-size: var(--bwz-text-xs);
  letter-spacing: var(--bwz-tracking-wide);
  text-transform: uppercase;
  color: var(--bwz-fg-subtle);
}
.bwz-quest__title {
  margin: var(--bwz-space-1) 0 0;
  font-size: var(--bwz-text-xl);
  letter-spacing: var(--bwz-tracking-tight);
}
.bwz-quest__blurb {
  margin: var(--bwz-space-2) 0 0;
  max-width: var(--bwz-measure);
  color: var(--bwz-fg-muted);
}

.bwz-quest__form > * + * { margin-top: var(--bwz-space-4); }

/* One question. A fieldset, because a choice question is a group of inputs and
 * its legend is the question — which is what a screen reader announces.
 *
 * [CRITICAL] THE QUESTION IS ITS OWN BOX, AND THE `float` IS WHAT MAKES THAT
 * POSSIBLE AT ALL (869fct82k, Dan 2026-10-08: *"Question … is awkwardly
 * positioned on top of the border of the answers … Put the questions in their
 * own box."*).
 *
 * The HTML rendering spec lifts a <legend> INTO the fieldset's top border and
 * cuts the border away behind it. On a one-line question that reads as a
 * deliberate caption; on one long enough to WRAP, the second line falls inside
 * the box with the border running through the gap above it, which is what Dan
 * photographed. The spec's own escape hatch is the one used here: a legend
 * whose `float` is not `none` is NOT the "rendered legend" and is laid out as
 * an ordinary block child. `display: block` is NOT that escape hatch and is
 * ignored by the engines — measured in all three by
 * ../blueworkz-tests/drills/discovery_question_box_drill.py, which reads the
 * legend's own rect against the fieldset's border edge and fails on the build
 * Dan was looking at.
 *
 * So the fieldset keeps the frame and holds NO padding of its own, the legend
 * is a full-bleed header band with its own edge, and `.bwz-quest__body` is the
 * answers. Two boxes, which is what was asked for. */
/* `.bwz-quest__block` — a repeatable block: a group of questions the client may
 * answer more than once. 869fjpa3g, Dan's Q1(a)/Q2(a)/Q3(a) of 2026-10-19.
 *
 * No new colour and no new token. A row is bordered so that "which answers
 * belong to venue 2" is a question the eye can answer, and the border is
 * `--bwz-rule` — the same neutral `.bwz-quest__q` wears — because on a
 * questionnaire a graded colour would be the first thing a client read as a
 * verdict on their own answer. Nothing here is `display: none`, so a blocked
 * stylesheet leaves every control reachable and every row readable in order. */
.bwz-quest__block {
  display: grid;
  gap: var(--bwz-space-4);
  padding: var(--bwz-space-4);
  border: var(--bwz-border) solid var(--bwz-rule);
  border-radius: var(--bwz-radius-lg);
  background: var(--bwz-surface-2);
}
.bwz-quest__blocktitle {
  margin: 0;
  font-size: var(--bwz-text-lg);
  line-height: 1.3;
}
.bwz-quest__blockempty { margin: 0; }
.bwz-quest__row {
  display: grid;
  gap: var(--bwz-space-3);
  padding: var(--bwz-space-4);
  border: var(--bwz-border) solid var(--bwz-rule);
  border-radius: var(--bwz-radius-lg);
  background: var(--bwz-surface);
}
.bwz-quest__rowhead {
  margin: 0;
  font-weight: 600;
  color: var(--bwz-fg-muted);
}
.bwz-quest__rowfoot { margin: 0; }

.bwz-quest__q {
  margin: 0;
  padding: 0;
  border: var(--bwz-border) solid var(--bwz-rule);
  border-radius: var(--bwz-radius-lg);
  background: var(--bwz-surface);
}
.bwz-quest__label {
  float: left;
  width: 100%;
  margin: 0;
  padding: var(--bwz-space-3) var(--bwz-space-4);
  border-bottom: var(--bwz-border) solid var(--bwz-rule);
  border-start-start-radius: calc(var(--bwz-radius-lg) - var(--bwz-border));
  border-start-end-radius: calc(var(--bwz-radius-lg) - var(--bwz-border));
  background: var(--bwz-surface-2);
  font-weight: var(--bwz-weight-semibold);
  font-size: var(--bwz-text-md);
  line-height: var(--bwz-leading-snug);
}
/* `clear` is not decoration: it is the other half of the float above, and
 * without it the first answer is laid out beside the band rather than under
 * it. */
.bwz-quest__body {
  clear: both;
  padding: var(--bwz-space-4);
}
.bwz-quest__body > * + * { margin-top: var(--bwz-space-3); }
.bwz-quest__opts > .bwz-check + .bwz-check { margin-top: var(--bwz-space-2); }

.bwz-quest__other > * + * { margin-top: var(--bwz-space-2); }

/* 869fjpa3d. A `number` question's field and its declared unit, on one line.
 *
 * The unit is the QUESTION's own word -- Dan's Q3(a): declared by whoever
 * writes the question, never typed by the client -- so it is a label beside
 * the control rather than a second input.
 *
 * `flex-wrap` is what keeps it honest at 320px: the field has a floor of
 * 8rem, so on a narrow phone the unit drops to its own line rather than
 * squeezing a numeric field down to nothing or scrolling the page sideways.
 * No colour is introduced -- `--bwz-fg-muted` is the token `.bwz-help`
 * already uses on this surface, so the WCAG sweep in core/design.py gains no
 * pair to check. */
.bwz-quest__num {
  display: flex;
  align-items: center;
  gap: var(--bwz-space-2);
  flex-wrap: wrap;
}
.bwz-quest__num > .bwz-input { flex: 0 1 14rem; min-width: 8rem; }
.bwz-quest__unit {
  font-size: var(--bwz-text-sm);
  color: var(--bwz-fg-muted);
}

.bwz-quest__files {
  margin: 0;
  padding: 0;
  list-style: none;
}
.bwz-quest__files li {
  display: flex;
  justify-content: space-between;
  gap: var(--bwz-space-3);
  padding: var(--bwz-space-2) 0;
  border-bottom: var(--bwz-border) solid var(--bwz-rule);
  font-size: var(--bwz-text-sm);
  overflow-wrap: anywhere;
}
.bwz-quest__size {
  flex: none;
  color: var(--bwz-fg-subtle);
  font-size: var(--bwz-text-sm);
  font-variant-numeric: tabular-nums;
}
.bwz-quest__upload > * + * { margin-top: var(--bwz-space-2); }

.bwz-quest__nav {
  padding-top: var(--bwz-space-4);
  border-top: var(--bwz-border) solid var(--bwz-rule);
}
.bwz-quest__nav > * + * { margin-top: var(--bwz-space-3); }
.bwz-quest__count {
  margin: 0;
  font-size: var(--bwz-text-sm);
  color: var(--bwz-fg-muted);
  font-variant-numeric: tabular-nums;
}
.bwz-quest__foot { max-width: var(--bwz-measure); }

.bwz-quest__summary li {
  display: flex;
  justify-content: space-between;
  gap: var(--bwz-space-3);
  padding: var(--bwz-space-2) 0;
  border-bottom: var(--bwz-border) solid var(--bwz-rule);
}

/* ---------------------------------------------------------------------------
 * 21. The headless runners page (869f03368)
 *
 * One full-width row per runner, about two lines tall closed: a disclosure
 * triangle, the label, the status pill, ten words of state, and the one-line
 * "new ticket" box. Everything else is behind the triangle.
 *
 * <details> and <summary>, never a script. The header navigation is the one
 * place on this site where JavaScript is allowed to hide something, and it is
 * allowed there because it also *creates* the thing it hides (section 15). Here
 * a blocked script must leave every runner readable, and with native
 * disclosure it does — the rows simply all open.
 *
 * Layout only, as section 17: every colour below is a role token from section 2
 * and the status edge is .bwz-tile--*, which the dashboard already owns. A page
 * that grows its own greens is a page whose green means something else.
 * ------------------------------------------------------------------------- */

.bwz-runner {
  border: var(--bwz-border) solid var(--bwz-rule);
  border-radius: var(--bwz-radius-lg);
  background: var(--bwz-surface);
  /* [CRITICAL] ONE rule for the whole card, because the card is full of strings
   * this repo did not write and cannot shorten: ticket titles from the board,
   * env key names, absolute paths, pause reasons. `overflow-wrap` inherits, so
   * every descendant gets it from here.
   *
   * `anywhere`, never `break-word`. Only `anywhere` reduces a box's MIN-CONTENT,
   * and min-content is what a flex item uses as its automatic minimum — with
   * `break-word` the text wraps and the row still refuses to be narrower than
   * `BWZ_RUNNER_AGENT_SECRET_VENUE_VIEW`, so the page scrolls sideways instead.
   *
   * Measured on the live board at 320px on 2026-09-11, three engines, with
   * every disclosure open: 56px of horizontal scroll from three independent
   * offenders — a ticket title, the dead-control reason, and the env key in the
   * "declared, not enrolled" callout. All three predate 12.2.0; no test had
   * reached this page at 320 until then. */
  overflow-wrap: anywhere;
}
.bwz-runner + .bwz-runner { margin-top: var(--bwz-space-3); }

/* Row one. `align-items: center` rather than baseline: the pill and the
 * triangle are boxes, not text, and baseline puts them half a line high. */
.bwz-runner__summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bwz-space-2) var(--bwz-space-3);
  padding: var(--bwz-space-3) var(--bwz-space-4);
  cursor: pointer;
}
/* The default marker is a different glyph in every engine and cannot be
 * rotated. Ours is one span, drawn with a border, so it is identical in all
 * four browsers and takes currentColor in both themes. */
.bwz-runner__summary::-webkit-details-marker { display: none; }
.bwz-runner__summary { list-style: none; }
.bwz-runner__arrow {
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(-45deg);
  transition: transform 150ms ease;
}
[open] > .bwz-runner__summary .bwz-runner__arrow { transform: rotate(45deg); }

/* The ClickUp button on the title row (869f29wfz, Dan 2026-09-15). `margin-left:
 * auto` rather than relying on the headline to push it: the headline is
 * `flex: 1 1 12rem` and normally eats the slack, but a card whose headline is
 * short and whose row has wrapped would otherwise leave the button mid-row.
 * `flex: none` so a two-word label never shrinks to "Click…".
 *
 * It is the one control inside a <summary> on this page. Nothing here stops the
 * card toggling -- the DOM does, by choosing the LINK as the activation target
 * -- and no CSS could, which is why the reasoning is in the template beside the
 * markup and not in this comment. */
.bwz-runner__clickup { flex: none; }

/* A bookmark to ANOTHER list on the same board (869fbxz9x, Dan 2026-10-05).
 * Same chrome, same `flex: none`, DELIBERATELY A DIFFERENT CLASS: the one above
 * is the card's single queue link and a test asserts there is exactly one of
 * it. These are navigation to a list this estate may open and may not write
 * on, and the open card says which is which. */
.bwz-runner__listlink { flex: none; }

/* The title row's right-hand cluster (869f29wfz, Dan 2026-09-18: "Move the
 * shell button icon to the top row so it is next to the click up button").
 *
 * `margin-left: auto` moved OFF the ClickUp button and onto the cluster, for
 * the reason the button carried it in the first place: the headline is
 * `flex: 1 1 12rem` and normally eats the slack, but a card with a short
 * headline, or a row that has wrapped, would leave the cluster mid-row. On the
 * cluster it also survives a card that declares no queue — Venue View — where
 * the shell buttons would otherwise be the ones needing it.
 *
 * It wraps, because at 320px "ClickUp" plus two 40px windows does not fit
 * beside a runner's name, and dropping to the next line is the correct failure. */
.bwz-runner__titletools {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bwz-space-2);
  margin-left: auto;
}
/* A plain wrapper round the shared include, so the group is a flex item of the
 * cluster and nothing about ops/_shell_buttons.html changes between the two
 * pages that use it. */
.bwz-runner__shell { display: inline-flex; align-items: center; }

/* The note beside them. Dan asked for the words to go — "so it shows only the
 * 'i' in a circle" — so the padding tightens to a circle rather than a pill,
 * and the accessible name is an aria-label in the markup. It is a DIRECT child
 * of the summary, which is what lets .bwz-info[open]'s `flex: 1 0 100%` give
 * the open panel the whole row instead of the right-hand cluster's width.
 *
 * [CRITICAL] It is __sshnote and NOT __shellnote. Three tests count
 * `bwz-runner__shell` with `str.count` to prove there is exactly ONE shell row
 * per card, and a class that CONTAINS that string counts as a second one — the
 * same substring trap the runner's own RUNNER-CLAIM / RUNNER-HANDOVER markers
 * are written against. Do not "tidy" the name. */
.bwz-runner__sshnote > summary { padding: 0.1rem; border-radius: 50%; }

/* The reason a card has NO shell button, on the second row where a sentence
 * fits. Right-aligned with the model line it sits above. */
.bwz-runner__noshell { margin: 0; text-align: right; }
@media (prefers-reduced-motion: reduce) {
  .bwz-runner__arrow { transition: none; }
}

.bwz-runner__label {
  font-weight: 600;
  font-size: var(--bwz-text-md);
}
/* Ten words, one line where there is room. `min-width: 0` is what lets it
 * shrink inside the flex row instead of pushing the pill off at 320px. */
.bwz-runner__headline {
  flex: 1 1 12rem;
  min-width: 0;
  margin: 0;
  color: var(--bwz-fg-muted);
  font-size: var(--bwz-text-sm);
}

/* Row two: the title box and its button, side by side above 30rem and stacked
 * below it. No media query — the wrap does it. */
.bwz-runner__new {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--bwz-space-2) var(--bwz-space-3);
  padding: 0 var(--bwz-space-4) var(--bwz-space-3);
}
.bwz-runner__new .bwz-field {
  flex: 1 1 16rem;
  min-width: 0;
  margin: 0;
}
/* ...except the complexity select, which is a word and not a sentence
 * (869f29wfz). Without this it inherits `flex: 1 1 16rem` from the rule above
 * and takes half the row from the title box, which is the control the row is
 * for. It still SHRINKS -- `1` as the flex-shrink -- so the two share what is
 * left at 320px rather than one of them forcing a horizontal scroll. */
.bwz-runner__new .bwz-runner__newcomplexity { flex: 0 1 9rem; }
/* The expanded quick-add's second row: the description on the left, the two
 * selects on the right, the submit buttons below (869f1qw3v -- Dan: "put it on
 * the same row as the description field, on the right, just before the create
 * button"). No media query: the wrap does it, exactly as .bwz-runner__new does,
 * so below ~34rem the selects drop underneath and nothing is clipped. */
.bwz-runner__addrow {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--bwz-space-2) var(--bwz-space-3);
}
.bwz-runner__addrow > .bwz-field {
  flex: 1 1 18rem;
  min-width: 0;
  margin: 0;
}
.bwz-runner__addcol {
  flex: 0 1 14rem;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--bwz-space-2);
}
.bwz-runner__addcol > .bwz-field { margin: 0; }
.bwz-runner__body {
  padding: 0 var(--bwz-space-4) var(--bwz-space-4);
  border-top: var(--bwz-border) solid var(--bwz-rule);
}
.bwz-runner__body > * { margin-top: var(--bwz-space-4); }

/* A nested disclosure — "the other fourteen", "recent runs", "recent ticks".
 * Quieter than the row itself so that two levels of triangle do not read as
 * two levels of importance. */
.bwz-runner__more {
  border: var(--bwz-border) solid var(--bwz-rule);
  border-radius: var(--bwz-radius-md);
}
.bwz-runner__more > summary {
  display: flex;
  align-items: center;
  gap: var(--bwz-space-2);
  padding: var(--bwz-space-2) var(--bwz-space-3);
  cursor: pointer;
  font-size: var(--bwz-text-sm);
  color: var(--bwz-fg-muted);
  list-style: none;
}
.bwz-runner__more > summary::-webkit-details-marker { display: none; }
.bwz-runner__more > div {
  padding: 0 var(--bwz-space-3) var(--bwz-space-3);
}

/* The ticket list. Tabular so a column of ids lines up, and the id is allowed
 * to wrap rather than widening the page at 320px. */
.bwz-runner__ticket {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bwz-space-1) var(--bwz-space-2);
  align-items: baseline;
}
/* `min-width` does NOT inherit, so every flex row inside a card that holds a
 * string from outside needs it spelled out. The wrapping half comes from
 * .bwz-runner above. */
.bwz-runner__ticket > *,
.bwz-runner__controls > * {
  min-width: 0;
}
.bwz-runner__ticket-when {
  font-size: var(--bwz-text-sm);
  color: var(--bwz-fg-muted);
  font-variant-numeric: tabular-nums;
}

/* The one region of a card that may name an environment key. It is a wrapper
 * with a job rather than a look: 869f061h5 went red because an assertion about
 * env keys was scoped to a whole runner card, whose queue listing carries
 * ticket titles this repo did not write. */
.bwz-runner__requires > * + * { margin-top: var(--bwz-space-3); }

/* Row two of the closed card: the controls and the new-ticket box, side by
 * side. Dan's ask of 2026-09-11 — "move the stop / go buttons in the collapsed
 * view (so I see them all on page load)" — so STOP and GO are OUTSIDE the
 * <details>, not duplicated inside it. Wraps rather than scrolls at 320px. */
.bwz-runner__row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--bwz-space-2) var(--bwz-space-3);
  padding: 0 var(--bwz-space-4) var(--bwz-space-3);
}
.bwz-runner__row .bwz-runner__new {
  flex: 1 1 18rem;
  padding: 0;
}
.bwz-runner__controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bwz-space-2);
  /* It is itself a flex item. Without this its automatic minimum is its own
   * max-content and it cannot shrink inside .bwz-runner__row. */
  min-width: 0;
}
/* No `white-space: nowrap`: this carries "Asked to: STOPPED — you pressed this
 * a moment ago", and an unwrappable phrase of that length is a 320px floor
 * broken by a sentence that only appears after somebody presses a button. */
.bwz-runner__asked {
  font-size: var(--bwz-text-sm);
  color: var(--bwz-fg-muted);
}

/* The model line, above the new-ticket box on the closed row (869f0mvrp).
 *
 * A column so that "Default: …" sits above the Create button rather than
 * beside it, which is where Dan asked for it. It is a LABEL and not a control,
 * so it takes the muted treatment the rest of the row's helper text takes.
 *
 * `overflow-wrap: anywhere`, never `break-word`: this line carries a model id
 * the account chose, and an unbreakable token of unknown length is the 320px
 * floor broken by a string this repo did not write. Section 12.12.17.3.1 of the
 * encyclopaedia is three defects deep on exactly that. */
.bwz-runner__newcol {
  display: flex;
  flex-direction: column;
  gap: var(--bwz-space-1);
  min-width: 0;
}
.bwz-runner__row .bwz-runner__newcol { flex: 1 1 18rem; }
.bwz-runner__row .bwz-runner__newcol .bwz-runner__new { flex: 0 0 auto; padding: 0; }
/* The shell buttons were on this row from 14.10.0 until 2026-09-18, when Dan
 * moved them up to the title row (869f29wfz). What is left here is the line a
 * card with NO alias declared shows instead — see .bwz-runner__noshell above. */

.bwz-runner__default {
  margin: 0;
  text-align: right;
  font-size: var(--bwz-text-sm);
  color: var(--bwz-fg-muted);
  overflow-wrap: anywhere;
}

/* ---------------------------------------------------------------------------
 * 21b. The Claude usage panel (869f0mvrp, 2026-09-12)
 *
 * One tile, one row per limit window the ACCOUNT has reported. There is no
 * colour of its own anywhere in here: the tile takes .bwz-tile--<light> and
 * each window takes a status badge, both graded by ops/registry.py and
 * ops/probes/runner.py. A panel about spending is exactly the place somebody
 * would reach for a "nearly full" amber of its own, and that would be a second
 * grading authority disagreeing with the first one on the same page.
 *
 * The bar is ops/templates/ops/_meter.html, unchanged and unextended — there is
 * no .bwz-meter--over-80 and there must never be one.
 * ------------------------------------------------------------------------ */
.bwz-usage { padding: var(--bwz-space-4); }
.bwz-usage > * + * { margin-top: var(--bwz-space-3); }
.bwz-usage__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bwz-space-2) var(--bwz-space-3);
  min-width: 0;
}
.bwz-usage__summary {
  margin: 0;
  flex: 1 1 16rem;
  min-width: 0;
  overflow-wrap: anywhere;
}

/* THE COLLAPSE (869f0mvrp, Dan 2026-09-13). The closed row carries the
 * measurement -- a bar per window, the figure inside it and the countdown -- so
 * every window is a flex ROW inside the <summary> rather than a stacked list
 * item. The <details> itself takes no border of its own: it is inside
 * .bwz-tile, and a second box round the same content would read as a second
 * card.
 *
 * The arrow is section 23's generic `.bwz-arrow`, inherited whole from
 * `details[open] > summary .bwz-arrow`, which is why there is no rotation rule
 * here and must not be one.
 *
 * ⚠️ `.bwz-usage__window` sets `flex: 1 1 18rem`, so two windows sit side by
 * side on a wide screen and stack at 320 without a media query. Do not give the
 * bar a fixed width: .bwz-meter is `width: 100%` by design and takes whatever
 * the row has left. */
.bwz-usage__disclosure > .bwz-disclosure__summary {
  padding: 0;
  align-items: center;
}
.bwz-usage__windows { margin: 0; }
.bwz-usage__window {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bwz-space-2);
  min-width: 0;
  flex: 1 1 18rem;
}
.bwz-usage__window .bwz-meter { flex: 1 1 7rem; min-width: 7rem; }
.bwz-usage__body {
  padding-top: var(--bwz-space-3);
  margin-top: var(--bwz-space-3);
  border-top: var(--bwz-border) solid var(--bwz-rule);
}
.bwz-usage__body > * + * { margin-top: var(--bwz-space-2); }
.bwz-usage__sources { margin: 0; }
.bwz-usage__source {
  font-size: var(--bwz-text-sm);
  color: var(--bwz-fg-muted);
  overflow-wrap: anywhere;
}
.bwz-usage__source + .bwz-usage__source { margin-top: var(--bwz-space-1); }
.bwz-usage__label { font-weight: 600; overflow-wrap: anywhere; }
.bwz-usage__resets {
  font-size: var(--bwz-text-sm);
  color: var(--bwz-fg-muted);
  font-variant-numeric: tabular-nums;
}
.bwz-usage__phrase { margin: 0; overflow-wrap: anywhere; }
.bwz-usage__foot { overflow-wrap: anywhere; }

/* ---------------------------------------------------------------------------
 * 22. The clickable note (869f03368, 2026-09-11)
 *
 * Dan: "move all the information in to clickable tooltips, so it is all still
 * there at my fingertips, but does not take up space on the page unless
 * clicked."
 *
 * [CRITICAL] It is <details>, not a script, and not a :hover tooltip.
 *   - a script: section 21's rule. A blocked script must leave every runner
 *     readable, and with native disclosure it does — every note simply opens.
 *   - :hover: there is no hover on a phone, and "at my fingertips" is literal.
 *     A hover tooltip is a control that does not exist on half the devices the
 *     design system is required to hold at (CLAUDE.md section 6).
 *
 * [CRITICAL] The panel is IN FLOW. It pushes what is below it down when it is
 * open and occupies nothing when it is closed, which is exactly what Dan asked
 * for — and it is deliberately not the overlay this shipped as for one hour on
 * 2026-09-11.
 *
 * The overlay was measured and rejected, in three engines, by
 * `test_an_open_clickable_note_never_pushes_the_runners_page_sideways`: an
 * absolutely positioned panel covers the control it explains, and Playwright
 * reported it exactly — *"<div class=bwz-info__panel> subtree intercepts
 * pointer events"*. Both ways out of that are worse than pushing content down
 * on a page carrying STOP buttons:
 *   - leave it intercepting, and the form a note describes cannot be used while
 *     the note explaining it is open;
 *   - add `pointer-events: none`, and a click on the visible panel lands on
 *     whatever is underneath it — which on this page can be STOP.
 * A note is prose. It never covers a control.
 *
 * [CRITICAL] ONE SECTION OVERLAYS PROSE ON A CONTROL AND IT IS §28d, SINCE
 * 2026-10-18, ON DAN'S OWN INSTRUCTION. This section is UNTOUCHED and still
 * governs the runner cards, the answer-sheet notes, the trends blurb and every
 * other surface — it is named here rather than left to be discovered, because
 * the copy nobody edits is the one that is believed. The cheat sheet's hover
 * takes the `pointer-events: none` route this paragraph refuses, and what makes
 * that safe THERE is measured and page-specific: there is no STOP on that page,
 * and the panel exists only while the pointer is on its own control, so a press
 * can be neither blocked nor misrouted. §28d states it in full. A run must not
 * read that exception as reaching this section.
 *
 * It cannot push the page sideways either: the panel's max-width is a measure,
 * a flex item's automatic minimum is its min-content (one word here), and every
 * row it sits in wraps. There is no width to tune.
 * ------------------------------------------------------------------------- */

/* A heading or a line of text with its note alongside it. A wrapper rather
 * than nesting the <details> inside the <p> or the <h3>: `details` is flow
 * content and both of those take phrasing content only, so a parser closes the
 * paragraph before it and the markup a person reads stops matching the DOM the
 * browser built. */
.bwz-noteline {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--bwz-space-1) var(--bwz-space-2);
}
.bwz-noteline > p { margin: 0; }

.bwz-info {
  display: inline-block;
  vertical-align: baseline;
}
.bwz-info > summary {
  display: inline-flex;
  align-items: center;
  gap: var(--bwz-space-1);
  padding: 0.1rem var(--bwz-space-2);
  border: var(--bwz-border) solid var(--bwz-rule);
  border-radius: var(--bwz-radius-full);
  cursor: pointer;
  font-size: var(--bwz-text-sm);
  line-height: 1.4;
  color: var(--bwz-fg-muted);
  list-style: none;
}
.bwz-info > summary::-webkit-details-marker { display: none; }
.bwz-info > summary:hover,
.bwz-info > summary:focus-visible { color: var(--bwz-fg); }
.bwz-info[open] > summary { color: var(--bwz-fg); }

/* The mark itself. A circled "i" drawn in CSS rather than an icon file: it has
 * to take currentColor in both themes, and core/design.py owns the palette. */
.bwz-info__mark {
  flex: none;
  width: 1.05em;
  height: 1.05em;
  border: var(--bwz-border) solid currentColor;
  border-radius: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: start;
  font-size: 0.85em;
  font-style: italic;
  font-weight: 700;
}

.bwz-info__panel {
  margin-top: var(--bwz-space-2);
  max-width: var(--bwz-measure);
  /* A note outside a runner card -- the page footer -- does not inherit the
   * card's wrapping rule, and its prose names env keys and absolute paths. */
  overflow-wrap: anywhere;
  padding: var(--bwz-space-3);
  border: var(--bwz-border) solid var(--bwz-rule);
  border-radius: var(--bwz-radius-md);
  background: var(--bwz-surface-2);
  box-shadow: var(--bwz-shadow-md);
  font-size: var(--bwz-text-sm);
  color: var(--bwz-fg-muted);
}
.bwz-info__panel > * + * { margin-top: var(--bwz-space-2); }

/* Open, it takes the row it is on: a paragraph beside a status pill reads as a
 * caption of the pill. Closed, it is a marker the size of a word. */
.bwz-info[open] {
  display: block;
  flex: 1 0 100%;
  min-width: 0;
}

/* A note whose HANDLE is a button rather than a marker (869f7b3b9, 21.2.0).
 *
 * Dan asked for a *button* labelled "Answer sheet - auto login" that sits in
 * the row of buttons on an answer-sheet card. The mechanics wanted are exactly
 * section 22's -- a native <details>, in flow, never a popover, taking the whole
 * row when it is open -- and only the handle's appearance differs, so this is a
 * modifier rather than a second component.
 *
 * `.bwz-info > summary` is (0,2,0) and would beat `.bwz-btn` (0,1,0) on every
 * property the two disagree about, so putting both class lists on one element
 * does not work and is not what this is. The summary carries `.bwz-info__mark`
 * nowhere: a button says what it does in words.
 *
 * The numbers are .bwz-btn--sm's own, read from section 6 rather than guessed,
 * so the handle lines up with the real buttons either side of it. */
.bwz-info--btn > summary {
  min-height: 36px;
  padding: var(--bwz-space-1) var(--bwz-space-3);
  border-width: var(--bwz-border-thick);
  border-radius: var(--bwz-radius-md);
  color: var(--bwz-fg-muted);
  font-weight: var(--bwz-weight-semibold);
}
.bwz-info--btn > summary:hover { background: var(--bwz-surface-2); color: var(--bwz-fg); }

/* The link itself, and the one line that says what happened to it. Both live
 * inside the panel, so a blocked script leaves the link on screen to be
 * selected -- which is the whole fallback. `anywhere` because a token has no
 * spaces in it and a URL that overflows its card is a URL nobody can read. */
.bwz-copy__value {
  display: block;
  padding: var(--bwz-space-2);
  border: var(--bwz-border) solid var(--bwz-rule);
  border-radius: var(--bwz-radius-sm);
  background: var(--bwz-surface);
  font-family: var(--bwz-font-mono);
  font-size: var(--bwz-text-sm);
  overflow-wrap: anywhere;
}
.bwz-copy__status:empty { display: none; }

/* ---------------------------------------------------------------------------
 * 23. Arrows, and the pill that explains itself (869f03448, 2026-09-11)
 *
 * Two of Dan's asks on the dashboard, and they share a component:
 *
 *   "Show arrows on the collapsible cards (to indicate they are collapsible /
 *    expandable)"
 *
 *   "If any pill on the page is not 'Ok', i.e. it is 'Failing' or 'Warning'
 *    then, on hover or click, pop out a small box with more info and a link to
 *    see more detailed info."
 *
 * [CRITICAL] Click, not hover. There is no hover on a phone and 320px is a hard
 * target (CLAUDE.md section 6), so "pop out on hover" is a control that does not
 * exist on half the devices this design system is required to hold at. Every
 * disclosure here is <details>: section 21's rule, because a blocked script must
 * never be able to hide something on this page.
 *
 * [CRITICAL] The panel is IN FLOW and inherits section 22's .bwz-info__panel
 * wholesale, which is where the measurement lives: an absolutely positioned
 * panel covers the control it explains, and on these pages that control can be
 * STOP. A note is prose. Prose never covers a control.
 *
 * ONE arrow, drawn once. There were already three copies of these eight lines
 * -- .bwz-account__arrow, .bwz-runner__arrow and the theme toggle's -- each with
 * its own `[open] > .x__summary` rotation selector because each was written for
 * one component. `.bwz-arrow` is the generic one: the rotation selector matches
 * any <details> whose summary holds it, so the next disclosure needs no CSS at
 * all. The two existing copies are deliberately left alone -- they work, and
 * restyling the runners page is not this ticket.
 * ------------------------------------------------------------------------- */
.bwz-arrow {
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(-45deg);
  transition: transform var(--bwz-duration) var(--bwz-ease);
}
details[open] > summary .bwz-arrow { transform: rotate(45deg); }
@media (prefers-reduced-motion: reduce) {
  .bwz-arrow { transition: none; }
}

/* The expandable sections on the roll-up. The arrow is the only change: the
 * summary was already a flex row, so it takes its place without a reflow. */
.bwz-disclosure__summary::marker,
.bwz-disclosure__summary::-webkit-details-marker { display: none; }
.bwz-disclosure__summary { list-style: none; }

/* ---- the pill that explains itself -------------------------------------- */

/* [CRITICAL] The BADGE is the pill, and the summary adds no second outline.
 * Section 22's .bwz-info > summary draws its own bordered capsule, which is
 * right for a note hanging off a heading and wrong here: a capsule around a
 * capsule reads as two controls, and the inner one is the traffic light — the
 * one element on this page whose shape must not become decorative. */
.bwz-info--pill > summary {
  border: 0;
  padding: 0;
  border-radius: 0;
  gap: var(--bwz-space-1);
  /* The badge carries its own colour from ops/models.py:BADGE_CLASS. Section
   * 22's summary sets `color: var(--bwz-fg-muted)`, which would repaint the
   * status word and make a failing pill argue with its own dot. */
  color: inherit;
}
.bwz-info--pill > summary:hover .bwz-info__arrow,
.bwz-info--pill > summary:focus-visible .bwz-info__arrow { color: var(--bwz-fg); }
.bwz-info__arrow {
  flex: none;
  width: 0.4rem;
  height: 0.4rem;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(-45deg);
  transition: transform var(--bwz-duration) var(--bwz-ease);
  color: var(--bwz-fg-muted);
}
details[open] > summary .bwz-info__arrow { transform: rotate(45deg); }
@media (prefers-reduced-motion: reduce) {
  .bwz-info__arrow { transition: none; }
}

/* What the box lists: every not-OK check behind this pill, each one its own
 * link. The left edge carries the check's OWN status colour — ops/rollup.py
 * hands it over as a role token, and there is no threshold anywhere in this
 * stylesheet that could disagree with ops/registry.py about it. */
.bwz-pillnote {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--bwz-space-2);
}
.bwz-pillnote__item {
  padding-inline-start: var(--bwz-space-3);
  border-inline-start: 3px solid var(--bwz-rule-strong);
}
.bwz-pillnote__item--ok { border-inline-start-color: var(--bwz-ok); }
.bwz-pillnote__item--warn { border-inline-start-color: var(--bwz-warn); }
.bwz-pillnote__item--danger { border-inline-start-color: var(--bwz-danger); }
.bwz-pillnote__item--neutral { border-inline-start-color: var(--bwz-rule-strong); }
/* The reading goes under the name rather than beside it. At 320px a check label
 * and its summary on one line is the one place this card has ever overflowed. */
.bwz-pillnote__item > .bwz-subtle { display: block; }

/* ---------------------------------------------------------------------------
 * 24. The shell button — a tiny PowerShell window (869f03448, Dan 2026-09-14)
 *
 * "I'd like these ssh links to be buttons, with the buttons looking like tiny
 * powershell windows … and just 'SSH' for blueworkz /kestrel. They should
 * render approx the same width as a 'Warning' pill currently does."
 *
 * So it is a picture of a window: a title bar with PowerShell's blue mark, and
 * a black screen with one word on it in monospace. The word is Dan's ("Prod",
 * "Dev", "SSH") and is aria-hidden — ops/_shell_buttons.html carries the
 * accessible name, because "Prod" names no machine and two of these sit side
 * by side on Asset Giant.
 *
 * [CRITICAL] It carries NO STATUS COLOUR and it must never acquire one. Every
 * other coloured thing on this dashboard is a measurement. A declared alias is
 * not one, and the vendor chrome above is the one palette here that cannot be
 * read as a traffic light. Do not add a .bwz-pwsh--ok, and do not make it
 * follow the theme.
 *
 * The width WAS matched to a "Warning" pill -- 6rem, Dan's own 2026-09-14 ask
 * that they "render approx the same width as a 'Warning' pill currently does".
 *
 * [CRITICAL] THAT INSTRUCTION WAS SUPERSEDED ON 2026-09-16 (869f2a694): "reduce
 * the width of the PowerShell buttons, so that free buttons, including the
 * admin link button, should fit in a single (grey), one-row box." The two asks
 * are in direct conflict and the later one wins; decision log 2026-09-16. Do
 * not "restore" 6rem from the older comment on the older ticket.
 *
 * 2.5rem is 40px, and every number in the sum below was MEASURED in Chromium
 * on the real page rather than chosen (drills/product_card_footer_drill.py,
 * 2026-09-16). A product card at 1680px is 253px wide and its grey band has
 * 1.5rem of padding each side, leaving 200px. The widest band on the page is
 * Asset Giant's, which holds the Admin button (102px, a 16px favicon and the
 * word) and TWO of these:
 *
 *     102 + 8 (band gap) + 40 + 4 (group gap) + 40  =  194  <=  200
 *
 * At 6rem that sum was 102 + 8 + 96 + 8 + 96 = 310 and the band was two rows;
 * at 3.25rem it was 222 and still two. Six pixels of headroom is not much, and
 * it is stated rather than hidden: the grid's own floor is a 15rem column, so a
 * viewport that lands a column at exactly 240px leaves 192px and the group
 * wraps. That is the graceful failure and it is the right one -- a band that
 * wraps is readable, and the alternative is a button that clips its own word.
 *
 * It is still a WIDTH rather than a min-width, so "Prod" and "Dev" are the same
 * size next to each other, and the title bar keeps its ellipsis -- "PowerShell"
 * does not fit at this size and is not meant to: the window is a picture, and
 * the accessible name is the sentence in the markup.
 * ------------------------------------------------------------------------- */

.bwz-pwsh-group {
  display: flex;
  flex-wrap: wrap;
  /* --bwz-space-1 since 869f2a694: Asset Giant is the only card with two of
   * these, and 4px rather than 8px between them is 4px of the 6px that made
   * its band one row. */
  gap: var(--bwz-space-1);
  /* A row of bookmarks, not a row of controls: it sits under the label that
   * introduces it, and wrapping is how two buttons behave at 320px. */
}

.bwz-pwsh {
  display: inline-flex;
  flex-direction: column;
  flex: none;
  width: 2.5rem;
  border-radius: var(--bwz-radius-sm);
  border: var(--bwz-border) solid var(--bwz-pwsh-chrome);
  overflow: hidden;
  background: var(--bwz-pwsh-screen);
  text-decoration: none;
  /* A real touch target at 320px is 44px; this is 42px of window plus the
   * border, and it is a bookmark rather than STOP, so the shortfall is
   * deliberate and bounded. Widening it would stop it looking like a window. */
  transition: border-color var(--bwz-duration) var(--bwz-ease),
              box-shadow var(--bwz-duration) var(--bwz-ease);
}

.bwz-pwsh:hover,
.bwz-pwsh:focus-visible {
  border-color: var(--bwz-pwsh-icon);
  box-shadow: 0 0 0 2px var(--bwz-accent-surface);
}

/* The title bar. Windows Terminal's tab strip, at a twelfth of the size. */
.bwz-pwsh__bar {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.15rem 0.3rem;
  background: var(--bwz-pwsh-chrome);
  color: var(--bwz-pwsh-chrome-fg);
  font-size: 0.5rem;
  line-height: 1;
  letter-spacing: 0.01em;
}

/* PowerShell's mark: a blue square with a chevron and an underscore in it.
 * Drawn rather than shipped as an image — it is two characters. */
.bwz-pwsh__icon {
  flex: none;
  width: 0.6rem;
  height: 0.6rem;
  border-radius: 1px;
  background: var(--bwz-pwsh-icon);
  position: relative;
}
.bwz-pwsh__icon::before {
  content: ">_";
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: start;
  color: var(--bwz-pwsh-chrome-fg);
  font-family: var(--bwz-font-mono);
  font-size: 0.4rem;
  line-height: 1;
}

.bwz-pwsh__title {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  /* Smaller since the window narrowed (869f2a694). It ellipsises at this width
   * whatever the size, and this is the size at which the ellipsis looks like a
   * tab strip rather than like a truncation. */
  font-size: 0.42rem;
}

/* The screen. One word, PowerShell's own pale yellow, and no cursor — Dan
 * asked for the reference images "without the cursor showing". */
.bwz-pwsh__screen {
  display: flex;
  align-items: center;
  /* 0.25rem of inline padding rather than 0.4rem since the window narrowed:
   * "Prod" is ~29px of monospace at --bwz-text-xs and the screen is 32px wide
   * inside its border. */
  padding: 0.25rem 0.25rem 0.3rem;
  background: var(--bwz-pwsh-screen);
  color: var(--bwz-pwsh-screen-fg);
  font-family: var(--bwz-font-mono);
  /* --bwz-text-xs since the window narrowed to 3.25rem (869f2a694). "Prod" at
   * --bwz-text-sm is 34px of glyph in 33px of screen, and a clipped word on a
   * button that is a PICTURE of a terminal reads as a rendering bug. */
  font-size: var(--bwz-text-xs);
  font-weight: var(--bwz-weight-semibold);
  line-height: 1.1;
  overflow: hidden;
  white-space: nowrap;
}

@media (prefers-reduced-motion: reduce) {
  .bwz-pwsh { transition: none; }
}

/* The Admin button's favicon (869f03448, Dan 2026-09-14: "Buttons should be
 * normal buttons but include their targets' favicons as well as 'Admin'").
 *
 * A plain button plus an image, which is why there is no new button variant
 * here: it is .bwz-btn--secondary --sm with one modifier that stops the icon
 * being stretched by the flex row it sits in. The bytes are inlined by
 * ops/favicons.py; the size is fixed here so that a favicon somebody replaces
 * with a 512px one cannot resize the button. */
.bwz-btn--faviconned { gap: var(--bwz-space-2); }
.bwz-btn__favicon {
  flex: none;
  width: 1rem;
  height: 1rem;
  border-radius: 2px;
  object-fit: contain;
  /* Always on paper, in BOTH themes, and this one is measured rather than
   * tidy: SR-CRM's favicon is a dark navy square and in dark mode it very
   * nearly vanishes into the button it is sitting in. These are four other
   * organisations' marks, drawn for a browser tab strip, so none of them can
   * be assumed to work on a dark ground — and the brand rule that applies to
   * our own mark ("never recolour it") applies harder to somebody else's.
   * Invisible in light mode, where the surface is already paper. */
  background: var(--bwz-brand-paper);
}

/* ===========================================================================
 * §25. The Snapshot control (869f34k2p, Dan 2026-09-16)
 * ===========================================================================
 *
 * The first CONTROL ever to sit on a product card — everything else in that
 * band is navigation — and the only one on the dashboard that spends money at a
 * vendor. Two rules shape every line below and both are already written down.
 *
 * [CRITICAL] IT IS A <details> AND NEVER A POPOVER. "A tooltip Dan asks for is
 * a <details> note — in flow, never :hover, never a popover"
 * (rules/ops-dashboard.md), and "prose never covers a control"
 * (rules/runner.md §12.12.17). Dan's word on the ticket was "pop up";
 * an overlaying panel on this page either blocks the control under it or passes
 * the click onto it, and what is under a product card is the rest of a
 * monitoring page. So there is NO position: absolute, NO position: fixed, no
 * transform and no z-index anywhere in this section, deliberately.
 *
 * [CRITICAL] NO STATUS COLOUR. Colour on this dashboard means a check is
 * failing. A snapshot is not a reading, so this wears the same neutral chrome
 * .bwz-btn--secondary does and is never given a tile class, a pill or a meter.
 * The one exception is the "running" flag, which is drawn in the SUBTLE ink and
 * not in an amber — it is a fact about an action somebody took, not a warning.
 */
.bwz-snapshot {
  /* Closed, it is one more button in the wrapping row. OPEN, it takes a whole
   * row of the band — which is what lets the confirmation open IN FLOW without
   * the panel sitting beside the Admin button in a 90px column. The band is a
   * flex container (§17b), so this is flex-basis and not a width. */
  flex: 0 1 auto;
  min-width: 0;
}
.bwz-snapshot[open] { flex: 1 0 100%; }

.bwz-snapshot__summary {
  display: inline-flex;
  align-items: center;
  gap: var(--bwz-space-1);
  padding: var(--bwz-space-1) var(--bwz-space-3);
  /* [CRITICAL] THE ACCENT BLUE, ON EVERY CONTROL IN THIS FAMILY, AND THIS IS
   * THE ONE DEFINITION OF IT (869fczpww, Dan 2026-10-08: *"Also make the
   * Snapshot buttons that appear across the site blue as well."*).
   *
   * `25.22.0` painted the raise-a-ticket control accent in §27 and left the
   * snapshot button in neutral chrome, so one <details>-with-a-button-summary
   * was blue and its twin was not. The colour is declared HERE, on the class
   * both of them wear, rather than a second time in §27: `.bwz-btn--primary`'s
   * own tokens, so there is no second definition of "the blue a control wears"
   * for the two to drift apart on.
   *
   * [CRITICAL] IT IS STILL "NO STATUS COLOUR". The colours that GRADE something
   * on this dashboard are ok / warn / danger; accent is this project's ACTION
   * colour and every primary button already wears it. It says "press me", never
   * "this reading is bad", and ops/tests.py asserts no graded token reaches
   * either section.
   *
   * ⚠️ THE OLD DECLARATION PAINTED NO BORDER AT ALL, measured by reading it:
   * `border: 1px solid var(--bwz-border)` put the WIDTH token (`--bwz-border`
   * is `1px`) in the COLOUR slot, so the shorthand was invalid and every engine
   * dropped the whole of it. A button with no border and a near-white face is
   * most of why Dan read this control as "hard to read" in the first place.
   *
   * ⚠️ The pair is an EXISTING documented one, so core/tests.py's WCAG sweep
   * gains nothing new that could fail. */
  border: var(--bwz-border) solid var(--bwz-accent);
  border-radius: var(--bwz-radius-sm);
  background: var(--bwz-accent);
  color: var(--bwz-accent-fg);
  font-size: var(--bwz-text-sm);
  font-weight: 600;
  line-height: 1.4;
  cursor: pointer;
  /* The native marker is removed in both spellings: WebKit still wants the
   * pseudo-element, everything else honours list-style. Removing it is what
   * makes the summary read as the button Dan asked for; the open state is
   * carried by the panel appearing, which is the thing a reader is looking at
   * anyway. */
  list-style: none;
}
.bwz-snapshot__summary::-webkit-details-marker { display: none; }
.bwz-snapshot__summary:hover {
  background: var(--bwz-accent-hover);
  border-color: var(--bwz-accent-hover);
}
.bwz-snapshot__summary:active {
  background: var(--bwz-accent-active);
  border-color: var(--bwz-accent-active);
}
.bwz-snapshot__summary:focus-visible {
  outline: 2px solid var(--bwz-focus);
  outline-offset: 2px;
}

/* "running" beside the word, when DigitalOcean has not yet said the last
 * snapshot finished. Subtle ink rather than a warning colour, for the reason in
 * the section header: an action in progress is not a check that is failing. */
.bwz-snapshot__flag {
  font-weight: 400;
  font-size: var(--bwz-text-xs);
  /* [CRITICAL] IT FOLLOWED THE SUMMARY ONTO THE ACCENT (869fczpww, 2026-10-08).
   * It was `--bwz-ink-subtle`, a muted ink chosen for a near-white button and
   * close to unreadable on the accent -- the legibility complaint arriving by a
   * third door. `--bwz-accent-fg` is the documented partner of the background it
   * sits on now, so "running" is as readable as "Snapshot" beside it. Still a
   * weight and a size and NEVER a warning colour: an action in progress is not a
   * check that is failing. */
  color: var(--bwz-accent-fg);
}

.bwz-snapshot__panel {
  /* flex-basis: 100% above makes the DETAILS full width; this keeps the panel
   * itself readable rather than letting a sentence run the width of three
   * product cards on a wide screen. */
  max-width: var(--bwz-measure);
  margin-top: var(--bwz-space-2);
  font-size: var(--bwz-text-sm);
}

/* "Completed - 12:40", underneath the control, until it is dismissed or the
 * page is reloaded (869f34k2p, 22.4.0, Dan 2026-10-03: "it is easy to miss when
 * it completes - can you put a dismissable pill underneath that says
 * 'Completed - <time in hh:mm format>', which persists until page refresh or it
 * is dismissed?").
 *
 * [CRITICAL] THESE THREE RULES ARE WHAT MAKE THE SCRIPT'S OWN RULE TRUE.
 * core/static/core/js/bwz-snapshot.js says in capitals that it may write TEXT
 * into the elements the server marked and may NOT open, close, hide, reveal,
 * move or remove anything. So nothing reveals this pill: it is hidden while its
 * word is empty and shown while it is not, and the script only ever writes the
 * word — the answer from DigitalOcean on the way in, and an empty string when
 * somebody presses Dismiss. Appearing and vanishing are decided here.
 *
 * [CRITICAL] `display: none` IS THE DEFAULT DELIBERATELY, AND THE REVEAL IS THE
 * ONE RULE. An engine without :has() support therefore shows nothing at all,
 * which is the page this estate shipped in 22.2.0 — a degradation. Written the
 * other way round, the same engine would show a permanently visible empty pill
 * with a Dismiss button on it, on every card with a snapshot running. :has() is
 * in every browser in this project's declared target set (current and previous
 * major of Chrome, Edge, Safari and Firefox) and the pill is measured live in
 * all three engines by ../blueworkz-tests/drills/snapshot_live_update_drill.py;
 * the default is what covers the engine nobody tested.
 *
 * [CRITICAL] NO STATUS COLOUR, like everything else in this section. Colour on
 * this dashboard means a check is failing, and a snapshot that has just
 * finished is not a check. The pill wears the same neutral chrome the rest of
 * the band does, and WHICH KIND of finished it was is carried by the word —
 * "Completed" or "Errored", DigitalOcean's own, from
 * DropletSnapshot.completion_pill. A word, never a colour.
 */
.bwz-snapshot__done { display: none; }
.bwz-snapshot__done:has(.bwz-snapshot__done-word:not(:empty)) {
  display: flex;
  align-items: center;
  gap: var(--bwz-space-2);
  /* A row of its own in the product card's band, which is what "underneath"
   * means there. The band is a flex container (§17b) that wraps, so this is
   * flex-basis and not a width — the same mechanism .bwz-snapshot[open] uses.
   * On a product's own page the parent is block flow and this is ignored. */
  flex: 1 0 100%;
  margin-top: var(--bwz-space-2);
  padding: var(--bwz-space-1) var(--bwz-space-2);
  border: 1px solid var(--bwz-border);
  border-radius: var(--bwz-radius-sm);
  background: var(--bwz-surface-2);
  font-size: var(--bwz-text-sm);
}
.bwz-snapshot__done-word {
  font-weight: 600;
  color: var(--bwz-fg);
}
.bwz-snapshot__done-dismiss {
  /* A real <button>, not a label over a hidden checkbox: a dismiss is a press,
   * it has to be reachable from the keyboard, and the thing it presses on is a
   * notice rather than a state anybody stores. */
  margin-left: auto;
  padding: 0 var(--bwz-space-1);
  border: 1px solid var(--bwz-border);
  border-radius: var(--bwz-radius-sm);
  background: var(--bwz-surface);
  color: var(--bwz-fg-subtle);
  font: inherit;
  font-size: var(--bwz-text-xs);
  line-height: 1.6;
  cursor: pointer;
}
.bwz-snapshot__done-dismiss:hover { background: var(--bwz-surface-2); }
.bwz-snapshot__done-dismiss:focus-visible {
  outline: 2px solid var(--bwz-focus);
  outline-offset: 2px;
}

/* ---------------------------------------------------------------------------
 * §26. The trend charts (869f58teh, Dan 2026-09-21)
 *
 * [CRITICAL] There is no status colour in this section and that is the rule,
 * not an omission. `ops_resourcesample` stores no status, so a graded line
 * would be the page applying today's thresholds to an old reading. Colour on
 * this dashboard means "this check is failing"; a trend line is history, and
 * history is drawn in ink.
 *
 * The capacity line is distinguished by a DASH as well as by being a second
 * colour, for the same reason every tile carries the status word as well as the
 * dot: colour is never the only signal.
 *
 * Every coordinate arrives projected from ops/trends.py. Nothing here sizes the
 * plot: the viewBox does, so the chart is responsive down to 320px without a
 * media query and without a script.
 * ------------------------------------------------------------------------ */

.bwz-chart {
  max-width: 100%;
  overflow: hidden;
}

.bwz-chart__svg {
  display: block;
  width: 100%;
  height: auto;
}

/* [CRITICAL] --bwz-rule, never --bwz-border. `--bwz-border` is a WIDTH (1px);
 * it shipped here as a stroke on 2026-09-21 and `stroke: 1px` is invalid at
 * computed-value time, so every labelled rule on every trend chart was drawn
 * with no stroke at all -- an axis with numbers beside it and nothing to read
 * them against. Dan spotted the symptom, not the cause, on 2026-09-22 when he
 * asked for "subtle horizontal lines to make them more readable": they were
 * already asked for, and they were already invisible.
 * core/tests.py::test_no_colour_property_in_the_stylesheet_names_a_length_token
 * is what stops the next one. */
.bwz-chart__grid {
  stroke: var(--bwz-rule);
  stroke-width: 1;
}

/* The unlabelled rules halfway between the labelled ones. Dan, 2026-09-22:
 * "subtle horizontal lines to make them more readable". Subtle is the whole
 * specification -- a minor rule as strong as a major one is a second axis, and
 * an axis nobody can read past is worse than no help at all. */
.bwz-chart__grid--minor {
  stroke: var(--bwz-rule);
  opacity: 0.45;
  stroke-dasharray: 2 6;
}

.bwz-chart__axis {
  fill: var(--bwz-fg-muted);
  font-size: 15px;
}

/* The min-max band. Deliberately faint: it is context for the mean line, and a
 * band as strong as the line it sits behind reads as two measurements. */
.bwz-chart__band {
  fill: var(--bwz-fg-muted);
  opacity: 0.16;
  stroke: none;
}

.bwz-chart__line {
  stroke: var(--bwz-fg);
  stroke-width: 2.5;
  stroke-linejoin: round;
  stroke-linecap: round;
}

/* ONE INK PER RESOURCE (869f58teh, Dan 2026-09-22). The band takes the same
 * hue as its line at a low opacity, because the band IS that line's spread --
 * the question Dan asked, "some graphs show a black line and grey shading
 * behind, why is this", is easier to answer once the two are visibly the same
 * measurement. The default above still stands for a kind nobody has declared:
 * a new resource draws in ink rather than not drawing at all.
 *
 * [CRITICAL] These say WHICH resource. They never say how it is doing, and
 * nothing in this block may ever be var(--bwz-ok/-warn/-danger). */
.bwz-chart__line--cpu { stroke: var(--bwz-series-cpu); }
.bwz-chart__line--memory { stroke: var(--bwz-series-memory); }
.bwz-chart__line--disk { stroke: var(--bwz-series-disk); }
.bwz-chart__line--swap { stroke: var(--bwz-series-swap); }

.bwz-chart__band--cpu { fill: var(--bwz-series-cpu); opacity: 0.18; }
.bwz-chart__band--memory { fill: var(--bwz-series-memory); opacity: 0.18; }
.bwz-chart__band--disk { fill: var(--bwz-series-disk); opacity: 0.18; }
.bwz-chart__band--swap { fill: var(--bwz-series-swap); opacity: 0.18; }

/* The mouseover targets. Transparent rather than `fill: none`, because an
 * element with no fill is not a pointer target in any engine and the tooltip
 * would never appear. `pointer-events: all` is what makes a fully transparent
 * fill catch the pointer; it is the whole mechanism and there is no script
 * behind it. */
.bwz-chart__hotspot {
  fill: transparent;
  pointer-events: all;
}

.bwz-chart__capacity {
  stroke: var(--bwz-fg-muted);
  stroke-width: 2;
  stroke-dasharray: 8 6;
  stroke-linecap: round;
}

/* The key beside the chart. An inline swatch rather than a legend inside the
 * SVG: the words belong in the page's text layer, where they are selectable,
 * translatable and readable by a screen reader that skips the graphic. */
.bwz-chart__key {
  display: inline-block;
  width: 1.5rem;
  height: 0;
  vertical-align: middle;
  border-top-width: 3px;
  border-top-style: solid;
}
.bwz-chart__key--line { border-top-color: var(--bwz-fg); }
.bwz-chart__key--capacity {
  border-top-style: dashed;
  border-top-color: var(--bwz-fg-muted);
}

/* The band's swatch is a filled block rather than a rule, because the band is
 * an area. Height and the border together, so the two swatches in one sentence
 * sit on the same baseline. */
.bwz-chart__key--band {
  height: 0.7rem;
  border-top: 0;
  background: var(--bwz-fg-muted);
  opacity: 0.3;
}

.bwz-chart__key--line.bwz-chart__key--cpu { border-top-color: var(--bwz-series-cpu); }
.bwz-chart__key--line.bwz-chart__key--memory { border-top-color: var(--bwz-series-memory); }
.bwz-chart__key--line.bwz-chart__key--disk { border-top-color: var(--bwz-series-disk); }
.bwz-chart__key--line.bwz-chart__key--swap { border-top-color: var(--bwz-series-swap); }

.bwz-chart__key--band.bwz-chart__key--cpu { background: var(--bwz-series-cpu); }
.bwz-chart__key--band.bwz-chart__key--memory { background: var(--bwz-series-memory); }
.bwz-chart__key--band.bwz-chart__key--disk { background: var(--bwz-series-disk); }
.bwz-chart__key--band.bwz-chart__key--swap { background: var(--bwz-series-swap); }

/* One series on the stacked droplet page. A hairline above each block after
 * the first, so three plots read as three sections rather than as one very
 * tall chart. Nothing here sizes a plot: the viewBox still does. */
.bwz-trend-stack__item + .bwz-trend-stack__item {
  border-top: 1px solid var(--bwz-rule);
  padding-top: var(--bwz-space-5);
}

/* ===========================================================================
 * 25. The questionnaire overview and the answer sheet (869f5cv7w, 16.2.0)
 *
 * Two small components, both on `/dashboard/questionnaires/`. Everything else
 * that area draws is borrowed: the tab strip and the overall bar are §20's
 * (`.bwz-quest__steps`, `.bwz-quest__bar`), the section strip is §17a's
 * (`.bwz-tallybar`), and the cards, tables, forms and buttons are the base
 * system's. That reuse is the point rather than economy -- the tabs on the
 * answer sheet are the same tabs, with the same completion rings, as the ones
 * the client is looking at on the questionnaire itself.
 *
 * NO HEX IS NAMED HERE. Every colour is a role token from §2, so both themes
 * and the WCAG AA sweep in core/design.py apply to it unchanged.
 * ======================================================================== */

/* `.bwz-qgraph` -- one small bar per section, the graph that answers "where
 * have they stopped?". A three-column grid rather than a flex row per section:
 * the labels then line up down the page and the bars all start at the same x,
 * which is the entire reason a reader can compare them at a glance.
 *
 * It collapses to a single column below 40em. A 14-section set at 320px would
 * otherwise give each bar about 60px, and a bar that narrow is a decoration
 * rather than a measurement. */
.bwz-qgraph {
  display: grid;
  grid-template-columns: minmax(8rem, 14rem) 1fr auto;
  align-items: center;
  gap: var(--bwz-space-2) var(--bwz-space-3);
  margin: 0;
}
.bwz-qgraph > p { margin: 0; }
.bwz-qgraph__label { font-size: var(--bwz-text-sm); }
.bwz-qgraph__count {
  font-size: var(--bwz-text-sm);
  color: var(--bwz-fg-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.bwz-qgraph__track {
  height: 8px;
  border-radius: var(--bwz-radius-full);
  background: var(--bwz-surface-3);
  overflow: hidden;
}
.bwz-qgraph__fill { display: block; height: 100%; background: var(--bwz-rule-strong); }
/* The three states are the ones content/discovery.py:progress already names,
 * so the bar, the tab's ring and the strip above cannot disagree. A zero-width
 * fill is still drawn -- a "not started" section keeps its empty track, which
 * is what makes the column of tracks readable as a column. */
.bwz-qgraph__fill--none { background: var(--bwz-rule-strong); }
.bwz-qgraph__fill--part { background: var(--bwz-warn); }
.bwz-qgraph__fill--all { background: var(--bwz-ok); }

@media (max-width: 40em) {
  .bwz-qgraph { grid-template-columns: 1fr; gap: var(--bwz-space-1); }
  .bwz-qgraph__count { margin-bottom: var(--bwz-space-3); }
}

/* `.bwz-qa` -- one question and its answer, as prose.
 *
 * Deliberately NOT the questionnaire's own question card: a disabled form
 * control reads as "you may not tick this", not as "they did not tick this",
 * and this page is a transcript rather than a form somebody is being stopped
 * from using. The rule on the left edge is what separates one question from
 * the next without drawing fourteen boxes down a long page. */
.bwz-qa {
  padding-left: var(--bwz-space-4);
  border-left: var(--bwz-border-thick) solid var(--bwz-rule);
}
.bwz-qa + .bwz-qa { margin-top: var(--bwz-space-4); }
.bwz-qa__question {
  margin: 0;
  font-weight: var(--bwz-weight-semibold);
}
.bwz-qa__answer { margin: var(--bwz-space-2) 0 0; }
/* A blank answer is muted and italic rather than hidden: "nobody has answered
 * this" is one of the two facts this page exists to carry, and a page that
 * dropped unanswered questions would read as a finished questionnaire. */
.bwz-qa--blank { border-left-color: var(--bwz-rule-strong); }
/* 869fjpa3g. One row of a repeatable block on the transcript. Indented rather
 * than boxed: this page is prose Dan reads, and a second frame inside
 * `.bwz-qa`'s own left rule reads as a card in a card. */
.bwz-qa__row { margin-top: var(--bwz-space-4); padding-left: var(--bwz-space-4); border-left: var(--bwz-border) solid var(--bwz-rule); }
.bwz-qa__rowhead { margin: 0 0 var(--bwz-space-2); font-weight: 600; color: var(--bwz-fg-muted); }
.bwz-qa__answer--blank { color: var(--bwz-fg-muted); font-style: italic; }

/* ---------------------------------------------------------------------------
 * The portal-visibility switch (869f5cv7w, 17.4.0)
 *
 * [CRITICAL] It is a SUBMIT BUTTON drawn as a tick box, and the reason is not
 * style. Dan asked for the separate Save button to go -- "ticking (or
 * unticking) the box IS the save" -- and an <input type="checkbox"> cannot
 * save itself: only a script could submit its form on change, and the rule on
 * this dashboard is that a blocked script never costs anybody a control. So
 * the thing that looks like a box is the thing that is pressed, once.
 *
 * `role="switch"` and `aria-checked` are in the markup, so the state a screen
 * reader is told and the state a sighted reader sees are the same fact -- the
 * same rule `aria-current="step"` follows on the tab strip.
 *
 * NO HEX IS NAMED HERE. Both colours are role tokens from §2, so the theme
 * sweep and the WCAG AA pairs in core/design.py apply to it unchanged.
 * ------------------------------------------------------------------------ */
.bwz-switch {
  display: inline-flex;
  align-items: center;
  gap: var(--bwz-space-3);
  padding: var(--bwz-space-2) var(--bwz-space-3);
  margin: 0;
  border: var(--bwz-border) solid var(--bwz-rule-strong);
  border-radius: var(--bwz-radius-sm);
  background: var(--bwz-surface);
  color: var(--bwz-fg);
  font: inherit;
  font-size: var(--bwz-text-base);
  text-align: left;
  cursor: pointer;
}
.bwz-switch:hover { border-color: var(--bwz-accent); }
/* The box itself. A fixed square so that "on" and "off" do not move the label
 * sideways -- a control that shifts under the pointer is one somebody presses
 * twice. */
.bwz-switch__box {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: start;
  width: 1.25rem;
  height: 1.25rem;
  border: var(--bwz-border-thick) solid var(--bwz-rule-strong);
  border-radius: var(--bwz-radius-sm);
  font-size: var(--bwz-text-sm);
  line-height: 1;
}
/* On: the tick AND the colour, never the colour alone -- the same rule
 * .bwz-error::before follows. */
.bwz-switch--on .bwz-switch__box {
  border-color: var(--bwz-ok);
  color: var(--bwz-ok);
}
.bwz-switch__label { font-weight: var(--bwz-weight-medium); }

/* The SLIDER form of the same control (869f4mvv1, 18.1.0 — Dan 2026-09-22)
 *
 * Dan asked for "an on / off slider" beside the dashboard heading. It is the
 * same submit button as the tick-box above, with a track and a knob instead of
 * a box, for the same reason and with the same accessibility markup: a blocked
 * script must never cost anybody a control here, so the thing that looks like a
 * slider is the thing that is pressed.
 *
 * [CRITICAL] THE KNOB MOVES. The colour is the second signal and never the
 * only one — the same rule .bwz-switch--on .bwz-switch__box follows, and the
 * reason is that on this dashboard colour means "this check is failing", so a
 * control whose only difference is green would be reading in the page's own
 * vocabulary. NO HEX IS NAMED: both colours are role tokens from §2.
 */
.bwz-switch__track {
  flex: none;
  position: relative;
  display: inline-block;
  width: 2.25rem;
  height: 1.25rem;
  border: var(--bwz-border-thick) solid var(--bwz-rule-strong);
  border-radius: 999px;
  background: var(--bwz-surface-2);
  transition: background-color 120ms ease-out, border-color 120ms ease-out;
}
.bwz-switch__knob {
  position: absolute;
  top: 0.125rem;
  left: 0.125rem;
  width: 0.75rem;
  height: 0.75rem;
  border-radius: 50%;
  background: var(--bwz-fg-muted);
  transition: left 120ms ease-out, background-color 120ms ease-out;
}
.bwz-switch--on .bwz-switch__track {
  border-color: var(--bwz-ok);
  background: var(--bwz-ok-surface);
}
.bwz-switch--on .bwz-switch__knob {
  left: 1.125rem;
  background: var(--bwz-ok);
}
/* Somebody who has asked for less motion still gets the position change; they
 * simply get it immediately. */
@media (prefers-reduced-motion: reduce) {
  .bwz-switch__track,
  .bwz-switch__knob { transition: none; }
}

/* ===========================================================================
 * 26. One answer sheet, collapsed (869fbe2vq, 23.1.0 — Dan 2026-10-03)
 *
 * Dan: *"Make each answer sheet in the Answer sheets card itself collapsible,
 * all collapsed by default. So when I open the answer sheet card, I see list
 * of all answer sheets, each collapsed"*, and in the same comment *"Put the 3
 * new buttons in a single row"*.
 *
 * It is 20.0.0's rule for this page's SECTIONS, one level further in, so it is
 * drawn as what it is: a card whose header is the handle. The body keeps every
 * class it had -- `.bwz-stack`, `.bwz-cluster`, `.bwz-quest__bar`, `.bwz-info`
 * -- so the only thing this section adds is the frame and the handle.
 *
 * [CRITICAL] THE ARROW IS §23's GENERIC `.bwz-arrow` and there is no rotation
 * rule here: `details[open] > summary .bwz-arrow` already matches this, which
 * is exactly what that component was drawn for.
 *
 * NO HEX IS NAMED HERE. Every colour is a role token from §2, so both themes
 * and the WCAG AA sweep in core/design.py apply to it unchanged.
 * ======================================================================== */

.bwz-sheet {
  border: var(--bwz-border) solid var(--bwz-rule);
  border-radius: var(--bwz-radius-md);
  background: var(--bwz-surface);
}

/* The handle. `align-items: flex-start` because the head wraps on a narrow
 * card and an arrow centred against three wrapped lines points at nothing. */
.bwz-sheet__summary {
  display: flex;
  align-items: flex-start;
  gap: var(--bwz-space-3);
  padding: var(--bwz-space-3);
  cursor: pointer;
  list-style: none;
}
.bwz-sheet__summary::marker,
.bwz-sheet__summary::-webkit-details-marker { display: none; }
.bwz-sheet__summary:hover { background: var(--bwz-surface-2); }
.bwz-sheet__summary:focus-visible { outline: var(--bwz-border-thick) solid var(--bwz-accent); }
/* The arrow is drawn on the summary's own line height, not on the block of
 * wrapped badges beside it. */
.bwz-sheet__summary > .bwz-arrow { margin-top: 0.45rem; }

/* What the closed row says it holds. `min-width: 0` so a long client label
 * wraps inside the flex item instead of pushing the arrow off the card. */
.bwz-sheet__head {
  flex: 1 1 auto;
  min-width: 0;
  gap: var(--bwz-space-2);
}

.bwz-sheet__body {
  padding: var(--bwz-space-4);
  border-top: var(--bwz-border) solid var(--bwz-rule);
}

/* The three switches, in one row (869fbe2vq). A row rather than a stack
 * because Dan asked for one, and it is affordable now that the labels are
 * constants: three fixed-width words instead of three sentences that changed
 * length with the state.
 *
 * `align-items: flex-start` so a note that has been opened -- §22 gives it
 * `flex: 1 0 100%`, so it takes the whole row -- does not stretch the switches
 * beside it to its own height. */
.bwz-switchset { align-items: flex-start; }
.bwz-switchset > form { margin: 0; }

/* [CRITICAL] THE NOTES ARE A SECOND ROW, AND THAT IS A MEASUREMENT.
 *
 * Dan asked for the three BUTTONS in a single row. With their three notes in
 * the same row, drills/questionnaire_sheet_card_drill.py measured it wrapping
 * at 1100px in Chromium and Firefox and not in WebKit -- the single row he
 * asked for, broken on two engines out of three, on a page neither test suite
 * can see. Split, the switches hold one row down to about 700px.
 *
 * Tighter gaps and a smaller type scale than the row above, because this row
 * is a caption of that one and should not read as a second set of controls. */
/* [CRITICAL] THAT MARGIN WAS NEGATIVE FOR THREE RELEASES AND IT PUT THE NOTES
 * UNDER THE BUTTONS (869fct82k, Dan 2026-10-08: *"the show as completed,
 * editing allowed, show on client dashboard check boxes have pills behind
 * them. Please fix"*).
 *
 * `23.1.0` wanted this row tighter than `.bwz-stack`'s own 1rem and wrote
 * `calc(var(--bwz-space-3) * -1)` for it — reasoning as though a negative
 * margin here would be ADDED to that gap. It is not: `.bwz-stack > * + *` and
 * `.bwz-switchset--notes` have the same specificity and this rule is later in
 * the file, so it REPLACES the gap rather than reducing it, and the notes were
 * pulled 0.75rem up THROUGH the switches above them.
 *
 * Nothing measured it. `questionnaire_sheet_card_drill.py` asked whether an
 * OPENED note's panel covers a control (§22's rule) and never whether the
 * CLOSED row does, so 147/147 was green over the whole of it. It asks now. */
.bwz-switchset--notes {
  gap: var(--bwz-space-2);
  margin-top: var(--bwz-space-2);
}


/* ===========================================================================
 * 27. Raise a ticket about one failing check (869fczpww, Dan 2026-10-07)
 *
 * Dan: *"a very easy way to raise a ClickUp ticket for any particular failing
 * status indicator, which is probably a create ticket button for each line on
 * the details page of any particular product, droplets, service et cetera"*.
 *
 * [CRITICAL] IT DELIBERATELY WEARS §25's CHROME AND THIS SECTION IS ALL THAT IS
 * ADDED. ops/templates/ops/_check_ticket.html uses `.bwz-snapshot__summary` and
 * `.bwz-snapshot__panel` unchanged, because it is the same KIND of control — a
 * <details> whose summary is a neutral button and whose panel is a confirmation
 * in flow — and a second copy of §25's sixty lines would be two places for the
 * one sentence about an act that cannot be undone to drift apart. That is the
 * same call ops/_snapshot_button.html made when 869fc6ewc widened it to take a
 * panel rather than copying the file.
 *
 * [CRITICAL] SO WHY A CLASS OF ITS OWN AT ALL? Two reasons, and neither is
 * styling. It is never in the product card's flex band — it is in a card BODY and
 * in page flow — so `.bwz-snapshot`'s `flex` pair means nothing here and the one
 * thing it does need is a block of its own. And the markup has to stay tellable
 * apart from a snapshot's: ops/tests.py counts `class="bwz-snapshot"` on the
 * environments page to assert one snapshot control per declared droplet, and a
 * ticket button that matched that string would break a measurement that has
 * nothing to do with it.
 *
 * [CRITICAL] NO STATUS COLOUR, like §25 and for the same reason: colour on this
 * dashboard means a check is failing, and the tile behind this control and the
 * persistence badge beside it are the only things on the row that grade anything.
 * A ticket is not a reading. There is no position: absolute, no z-index and no
 * :hover anywhere in this section, deliberately — prose never covers a control.
 */
.bwz-check-ticket {
  display: block;
  margin-top: var(--bwz-space-2);
}

/* [CRITICAL] THE SUMMARY IS THE ACCENT BLUE, AND IT IS THE SAME BLUE AS THE
 * `Create` BUTTON ON /dashboard/runners/ RATHER THAN A NEW ONE (Dan 2026-10-07,
 * second round on 869fczpww: *"the button is hard to read, could you please
 * format it with a blue background so it looks like the create button on the
 * runners page"*).
 *
 * It is `.bwz-btn--primary`'s own three declarations, token for token, so there
 * is no second definition of "the blue a control wears" to drift. The hover and
 * active states are re-declared HERE because §25's
 * `.bwz-snapshot__summary:hover` paints `--bwz-surface-2` and this section comes
 * after it; without them the button would go grey under the pointer, which is
 * the legibility complaint arriving by a different door.
 *
 * [CRITICAL] THIS DOES NOT PUT A STATUS COLOUR ON A CONTROL. The colours that
 * grade something on this dashboard are ok / warn / danger; accent is the ACTION
 * colour every primary button in this project already wears, and it says "press
 * me", never "this check is failing". The persistence badge beside it still
 * carries no modifier at all and the tile behind it is still the only thing on
 * the row that grades the reading.
 *
 * It takes `--sm`'s geometry and not the full button's: the control sits in a
 * table cell and in a card body, where a 44px block per not-green row would
 * outweigh the reading it is about. The touch target is still 36px, which is
 * what every other compact button on these pages is.
 *
 * NO `position:`, NO `z-index` and NO status token anywhere in this block --
 * ops/tests.py asserts the first two of the whole section. */
.bwz-check-ticket__summary {
  min-height: 36px;
  border-radius: var(--bwz-radius-md);
  border-color: var(--bwz-accent);
  background: var(--bwz-accent);
  color: var(--bwz-accent-fg);
}
/* [CRITICAL] THE HOVER AND ACTIVE OVERRIDES ARE GONE BECAUSE THE THING THEY
 * CORRECTED IS GONE (869fczpww, Dan 2026-10-08). They existed because §25's
 * `.bwz-snapshot__summary:hover` painted `--bwz-surface-2` and would have
 * greyed this button under the pointer; §25 now paints the whole family accent,
 * hover and active included, so re-declaring them here would be a second copy
 * of a colour that has one definition. The three declarations above stay
 * because ops/tests.py pins THIS control to the runners-page pair by name --
 * they resolve to the same tokens §25 sets, so there is nothing to drift. */

/* [CRITICAL] THE CONFIRMATION GETS A FLOOR WIDTH INSIDE A TABLE CELL, AND THAT
 * NUMBER IS MEASURED (869fczpww, 2026-10-07). Putting the control beside the pill
 * put it in the "Not green for" COLUMN, and a column is as wide as its content:
 * drills/check_detail_controls_drill.py measured the open panel at 342px wide at
 * 1440px, 251px at 1100px and 112-169px from 820px down -- a ribbon of prose
 * 1,124 to 1,257px TALL in all three engines. Every assertion in that drill
 * passed, because they ask about overflow and about flow and neither is the
 * fault: the document never scrolled sideways and the panel never covered
 * anything. It was simply unreadable, and the one sentence in it that matters is
 * the one saying a ticket on a foreign board cannot be deleted.
 *
 * `.bwz-table-wrap` has `tabindex="0"` and scrolls inside its own box, so a wider
 * cell costs horizontal scrolling WITHIN the table -- which is what that wrapper
 * exists for and what the fifth column already does at 320px -- and never a
 * sideways-scrolling document. That is the trade, taken deliberately.
 *
 * 17rem rather than a percentage: a percentage of a 122px cell is 122px, and the
 * cell is the thing being escaped. It is 272px, just inside the ~288px a 320px
 * viewport leaves the wrapper, so the panel fits the visible box once scrolled to
 * rather than needing a second scroll of its own. */
.bwz-table .bwz-check-ticket > .bwz-snapshot__panel {
  min-width: 17rem;
}

/* ---------------------------------------------------------------------------
 * [CRITICAL] ON THE PILL'S OWN LINE, WHICH REVERSES THIS TICKET'S OWN RULE
 * (869fczpww, Dan 2026-10-08: *"Put the "Raise a ticket" buttons on the same
 * line as the pills, make the reading column slightly narrower to accomodate
 * them"*).
 *
 * `25.22.0` wrote, in capitals, that *"next to the pill" is the same cell,
 * directly beneath, and never the same line* -- reasoned from a <details>
 * cramped into a narrow table column being the one thing a confirmation may not
 * be, which is a real measurement (112-169px wide, 1,257px tall) and is NOT
 * being denied. **It is overturned by the person it was written for, and the
 * cost is stated rather than the reasoning deleted.**
 *
 * [CRITICAL] WHAT PAYS FOR IT IS §25's OWN TRICK, not a smaller panel. A closed
 * control is one more item on the badge's line; an OPEN one takes `flex: 1 0
 * 100%` and becomes a full row of its own underneath -- exactly what
 * `.bwz-snapshot[open]` does in the product card's band, for exactly this
 * reason. So the confirmation still opens IN FLOW, still at full width, still
 * never covering a control, and the 17rem floor above still applies inside a
 * table cell. The badge wraps to its own line below the button on a narrow
 * screen, which is `flex-wrap` doing what 320px needs.
 *
 * ⚠️ `align-items: flex-start`, not `center`: the badge is a sentence that
 * wraps and the open panel is tall, and centring one against the other puts the
 * pill in the middle of a column of prose.
 * ------------------------------------------------------------------------- */
.bwz-check-ticket-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--bwz-space-2);
}
.bwz-check-ticket-row > .bwz-check-ticket {
  margin-top: 0;
  /* The BUTTON does not shrink: it is a fixed phrase and squeezing it is how a
   * control becomes two lines of its own. The badge gives way instead. */
  flex: 0 0 auto;
  min-width: 0;
}

/* [CRITICAL] THE BADGE GETS A CEILING, AND THE MISSING CEILING -- NOT THE GROW --
 * WAS THE FAULT (869fczpww, Dan 2026-10-09: *"the never green no green reading
 * ever recorded pill is really long. The pill should only be the length of the
 * text within it"*, and *"put the text from the pill over 2 lines so the total
 * height of the pill is the same as the button, but the width it requires is
 * much less"*).
 *
 * 26.3.0 declared `flex: 1 1 6rem` with NO `max-width`. The basis is measured and
 * is what packs the line cheaply enough for the button to sit beside the badge --
 * flexbox breaks a line on each item's HYPOTHETICAL size, so 96 + 8 + 133 = 237px
 * decides where the button wraps, and a basis of the whole sentence is why the
 * first version of that wrapped at every width. What nobody measured is what the
 * badge does with the room LEFT OVER, and `flex-grow: 1` with no ceiling gives it
 * all of it: on a check's own page the row is the page, so 26.3.0's wider
 * container turned a slightly roomy badge into a grey lozenge about 1,150px wide
 * with its sentence at the far left. **It is a regression of the PAGE WIDENING
 * showing up through an unbounded flex item**, which is why the same declaration
 * looked fine at 112rem for a release.
 *
 * ⚠️ THE GROW IS KEPT DELIBERATELY AND REMOVING IT IS THE OBVIOUS WRONG FIX.
 * `flex: 0 1 auto; width: max-content` hugs the text exactly -- and MEASURED
 * 2026-10-09 it put the badge's hypothetical size at 176px, so the line needed
 * 317px and the button wrapped underneath at 1440px and 1680px on a product page.
 * Dan ticked **Q2(a)** -- *"Same line on anything desktop-sized"* -- on the round
 * before this one, so that fix ships a regression against a tick to obey a
 * sentence the ceiling already satisfies.
 *
 * ⚠️ 11rem IS MEASURED IN THREE ENGINES AND IS THE NARROWEST CAP THAT WORKS.
 * The arithmetic says it should be smaller: `.bwz-badge` is `--bwz-text-xs`
 * (12px) at `line-height: 1.35`, so two line boxes plus 0.15rem of vertical
 * padding is 37px against `.bwz-check-ticket__summary`'s `min-height: 36px`, and
 * 8rem leaves 104px of content for a 30-character sentence. **8rem was tried and
 * measured at THREE lines and 53px**, because a semibold 12px face with 0.02em of
 * letter-spacing runs nearer 8.7px a character than the 5px the earlier 234px
 * reading implies. Six real labels x seven caps x chromium/firefox/webkit:
 *
 *      8rem  3 lines, 53px      11rem  2 lines, 37px   <- every engine
 *      9rem  3 lines, 53px      12rem  2 lines, 37px
 *     10rem  3 lines in chromium and firefox, 2 in webkit
 *     14rem  webkit stops wrapping the short labels at all
 *
 * So the band that holds in all three is 11-13rem, and 11 is the narrowest of it.
 * The height is reached by making the badge two lines rather than by typing one,
 * which is Dan's *"the total height of the pill is the same as the button"*.
 *
 * ⚠️ `max-width: fit-content(11rem)` WAS TRIED AND IS NOT WHAT IT READS LIKE.
 * It would have been the whole answer -- "no wider than the text, and no wider
 * than 11rem" -- and MEASURED 2026-10-09 it computes to a flat `176px` in
 * chromium, firefox and webkit alike, stretching a 107px label to 176px exactly
 * as a plain `max-width` does. **It buys nothing here and the measurement is
 * written down so nobody reaches for it again.**
 *
 * ⚠️ SO A LABEL SHORTER THAN THE CAP WOULD STILL BE GROWN TO IT, AND THE REASON
 * THAT IS NOT A LIVE FAULT IS MEASURED RATHER THAN ARGUED: every label
 * `ops/rollup.py:Persistence.label` can produce measures 213-299px on one line --
 * the shortest being *"New - not green for 1 minute"* -- against a 176px ceiling,
 * so the ceiling binds on all of them and the badge is two lines every time. The
 * day a shorter wording appears,
 * `drills/check_detail_controls_drill.py`'s *"the pill is never wider than its own
 * text needs"* fails and names it. The invariant is guarded rather than assumed.
 *
 * ⚠️ `overflow-wrap: anywhere` stays: a long unbroken word in a 152px content box
 * reaches past it otherwise, which is the fault `.bwz-persist` was added to fix. */
.bwz-check-ticket-row > .bwz-persist {
  flex: 1 1 6rem;
  max-width: 11rem;
  min-width: 0;
  overflow-wrap: anywhere;
}
.bwz-check-ticket-row > .bwz-check-ticket[open] { flex: 1 0 100%; }

/* [CRITICAL] "MAKE THE READING COLUMN SLIGHTLY NARROWER TO ACCOMODATE THEM" IS
 * DAN'S OWN REMEDY AND IT IS WHAT MAKES THE LINE ABOVE AFFORDABLE. The Reading
 * cell holds a whole summary sentence and a table column is as wide as its
 * content, so on a wide page it took the room the fifth column now needs.
 *
 * `--bwz-measure` rather than a typed width: it is this project's own stated
 * ceiling for a line of prose -- *"~80 characters, the top of the readable
 * band"* (§3) -- so the column is capped by the reason it should be capped
 * rather than by a number that happened to look right today. Below that width
 * nothing changes at all, which is why "slightly" is honest on a narrow screen.
 *
 * ⚠️ `overflow-wrap: anywhere` with it, or a long unbroken value (a URL, a key
 * name) would push past the cap and the cap would read as decoration. */
.bwz-checks__reading {
  max-width: var(--bwz-measure);
  overflow-wrap: anywhere;
}

/* [CRITICAL] THE PERSISTENCE BADGE WRAPS, AND THAT IS A MEASURED FIX RATHER THAN
 * A PREFERENCE. `.bwz-badge` is `white-space: nowrap`, which is correct for the
 * one-word status pill it was drawn for and wrong for a sentence: at 380px
 * "Never green - no green reading ever recorded" measured 415px and made the
 * check page scroll SIDEWAYS in all three engines, against a 320px hard target
 * (CLAUDE.md §6). Measured by drills/check_detail_controls_drill.py on
 * 2026-10-07, which is the only thing that can see these pages.
 *
 * A plain second class and never a `--modifier`: the badge must go on carrying no
 * status spelling at all, and ops/tests.py asserts exactly that. */
.bwz-persist {
  white-space: normal;
  /* It is a sentence, so it gets a sentence's leading rather than a pill's. */
  line-height: 1.35;
  text-align: left;
}

/* ===========================================================================
 * 28. The cheat-sheet board of buttons (869fgd7uj, Dan 2026-10-14)
 *
 * Dan: *"a single page which has a button for all of these actions that I can
 * click ... designed so as to maximise the number of cheats available to me on
 * any one screen. I should say a button and perhaps 4 to 5 words of explanation
 * ... strong visual differentiation between ... different types of actions and
 * different estates ... I want the absolute bare minimum of information."*
 *
 * [CRITICAL] NOT ONE GRADED COLOUR IN THIS SECTION. On this dashboard
 * ok / warn / danger mean *this check is failing*, and 307 buttons wearing
 * status colours would turn the whole page into a reading it is not. The
 * differentiation Dan asked for is carried by three things that grade nothing:
 * the estate's own LOGO in its heading, the per-kind sub-headings, and the SHAPE
 * of the control -- an execute button wears §9's `.bwz-btn--primary` accent,
 * which is this project's ACTION colour and is an existing documented token
 * pair, and a copy button does not.
 *
 * [CRITICAL] THE DENSITY IS A WRAPPING FLEX ROW AND NOT A GRID, and that is
 * measured rather than chosen. A grid gives every button the width of the
 * longest one in its column, and these labels run from one word to five, so a
 * grid would have spent most of the screen on whitespace -- which is the
 * opposite of *"maximise the number of cheats on one screen"*. A wrapping row
 * packs each button to its own content.
 *
 * [CRITICAL] A CLOSED `<details>` IS THE BUTTON. There is no separate button
 * element: the `<summary>` IS the control, which is what lets `bwz-copy.js` stay
 * exactly as it is -- it wires `details[data-bwz-copy]` and copies on the
 * summary's click -- and what makes the no-script fallback correct rather than
 * merely acceptable. So `.bwz-cheat__face` has to look like a button and be a
 * summary, and the `list-style: none` plus the `::-webkit-details-marker` rule
 * are what remove the disclosure triangle in all three engines.
 * =========================================================================== */

.bwz-cheat-estate { margin-block-end: var(--bwz-space-4); }

.bwz-cheat-estate__summary {
  display: flex;
  align-items: center;
  gap: var(--bwz-space-2);
  cursor: pointer;
  font-weight: 600;
  padding: var(--bwz-space-2) 0;
}

/* The logo. `object-fit: contain` so a non-square mark is not stretched --
 * core/design.py's rule is that the mark is never stretched or distorted, and
 * these are other estates' marks, which makes it their rule as well as ours. */
.bwz-cheat-estate__icon {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  object-fit: contain;
  border-radius: 2px;
}

.bwz-cheat-estate__name { flex: 1 1 auto; }

.bwz-cheat-estate__count { flex: 0 0 auto; }

.bwz-cheat-estate__body { padding-block-start: var(--bwz-space-2); }

.bwz-cheat-group { margin-block-end: var(--bwz-space-3); }

/* ----------------------------------------------------------------------------
 * §28f  THE STANDARDISED EXPANDED CARD (869fmdb62, superdan 2026-10-25)
 *
 * "the expanded view for each card should be the same on all cards ... a very
 * instantly recognisable layout which applies to all of them, but also the
 * ability to use the existing, more complicated, cheats."
 *
 * Three blocks in one order on every card: Terminals, Quick actions, then one
 * dropdown holding every existing cheat. What makes the layout recognisable is
 * the HEADING SHAPE repeating, so there is one heading rule for both blocks
 * rather than one each.
 *
 * [CRITICAL] NOT ONE GRADED COLOUR IN THIS SECTION. On this dashboard ok / warn
 * / danger mean *this check is failing*, and none of these controls is a
 * reading. The separation is carried by a BORDER and the surface step §28's own
 * cards already use, and `--bwz-fg-muted` on `--bwz-surface-2` is
 * core/design.py's documented *"Table header label"* pair, which is what these
 * headings are. ⚠️ `--bwz-accent` is NOT used: on this page the accent means
 * *pressing this runs a command on the box*, and nothing in here does.
 * -------------------------------------------------------------------------- */

.bwz-cheat-std { margin-block-end: var(--bwz-space-3); }

/* The repeated heading. Bigger than §28's kind heading and in the same family:
 * a kind heading divides a list, where this one names a BLOCK of the standard
 * layout, and a reader scanning two cards has to be able to see that the blocks
 * are the same two. */
.bwz-cheat-std__head {
  display: flex;
  align-items: center;
  gap: var(--bwz-space-1);
  font-size: var(--bwz-text-sm);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--bwz-fg-muted);
  background: var(--bwz-surface-2);
  border-radius: var(--bwz-radius-sm);
  padding: var(--bwz-space-1) var(--bwz-space-2);
  margin: 0 0 var(--bwz-space-1);
  font-weight: 600;
}

.bwz-cheat-std__glyph { font-size: var(--bwz-text-sm); }

/* The tally. A collapsible or repeated section that hides a number says the
 * number (15.2.0), and here it also tells a reader at a glance that every card
 * offers the same twelve. */
.bwz-cheat-std__count {
  margin-inline-start: auto;
  font-variant-numeric: tabular-nums;
  color: var(--bwz-fg-subtle);
}

/* "buttons to open all the relevant terminals for that card, nicely laid out."
 * A wrapping row rather than §28's grid: a terminal button is sized by its own
 * label (`account - environment - service`) and the vendor chrome §24 gives it
 * is what makes it recognisable, so stretching six of them to a 15rem column
 * would read as six cards rather than six bookmarks. */
.bwz-cheat-terminals { padding-block-end: var(--bwz-space-1); }

.bwz-cheat-terminals .bwz-cheat-shells {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bwz-space-1);
}

/* THE DROPDOWN holding every existing cheat. It is a plain <details> and it
 * deliberately carries NEITHER `data-cheat-card` (it is not an estate card:
 * that attribute is what the collapse test enumerates) NOR `data-cheat-group`
 * (every one of those must ship open) NOR a `name=` (the accordion group is the
 * BUTTONS, and a dropdown in it would close under the reader's hand when a
 * button inside it opened). It does carry `data-bwz-remember`, so
 * bwz-disclosure.js puts back whatever Dan left it as; with that file blocked it
 * is closed, which is the state the server drew. */
.bwz-cheat-more {
  border: 1px solid var(--bwz-border);
  border-radius: var(--bwz-radius-md);
  background: var(--bwz-surface-2);
}

.bwz-cheat-more__head {
  display: flex;
  align-items: center;
  gap: var(--bwz-space-1);
  padding: var(--bwz-space-1) var(--bwz-space-2);
  cursor: pointer;
  font-size: var(--bwz-text-sm);
  font-weight: 600;
  color: var(--bwz-fg-muted);
}

.bwz-cheat-more__name { flex: 1 1 auto; }

.bwz-cheat-more__count { flex: 0 0 auto; }

.bwz-cheat-more__body {
  padding: var(--bwz-space-2);
  padding-block-start: 0;
}

/* Small, uppercase and quiet: it is a divider, not a thing to read. Dan asked
 * for the bare minimum of information, and a kind heading is the least that can
 * carry *"strong visual differentiation between different types of actions"*. */
.bwz-cheat-group__head {
  font-size: var(--bwz-text-xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--bwz-fg-subtle);
  margin: 0 0 var(--bwz-space-1);
  font-weight: 600;
}

/* [CRITICAL] A GRID OF MINI CARDS SINCE 869fjnggu (Dan, 2026-10-18), WHERE IT
 * WAS A WRAPPING FLEX ROW OF ONE-LINE BUTTONS. His words: "Perhaps a better way
 * to think of the buttons is more like mini cards so each one might have a title
 * and some smaller text on it I can still fit a lot of them on the screen at
 * once but glancing at one immediately tells me everything I need to know about
 * it."
 *
 * A wrapping flex row sizes every button to its own content, which is what made
 * the one-line face necessary and the label cut with it: a card as wide as a
 * 155-character label would have been the whole line. `auto-fill` with a floor
 * gives every card the same width, so a title can WRAP instead of being cut and
 * the cards still line up in columns — which is the half of "mini cards" that
 * one-per-content-width could never give.
 *
 * ⚠️ `align-items: start` AND NOT THE DEFAULT `stretch`, and that is measured
 * rather than cosmetic. A grid row is as tall as its tallest card either way,
 * and `stretch` paints every short card in that row to that height — so one
 * 155-character label would draw eleven giant empty boxes beside it at 3440px.
 * `start` leaves the same whitespace unpainted, which reads as a card wall
 * rather than as a fault.
 *
 * ⚠️ 15rem IS A MEASURED TRADE AND IT COSTS DENSITY. [M] 2026-10-18, chromium:
 * the old ragged row fitted 17 buttons on its fullest line at 3440px. Narrower
 * than this and long titles wrap to five or six lines; wider and fewer fit. The
 * number is on 869fjnggu as a question, because "how many fit on my screen" is a
 * judgement about Dan's screen and not one this box can take. */
.bwz-cheat-row {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  gap: var(--bwz-space-1);
  align-items: start;
}

/* Each button sits in the flow of the row while closed, and takes the whole row
 * while open -- the trick §25's snapshot control and the ticket control both
 * use, so that the panel opens IN FLOW and never covers the control beside it.
 * `prose never covers a control` is the rule, and with 307 of these on one page
 * an overlay would cover several.
 * ⚠️ That is about THIS panel — the one a press opens — and it is unchanged. The
 * HOVER table is a popover, deliberately, since 2026-10-18: §28d. */
/* ⚠️ The 2px border is TRANSPARENT here and coloured by §28c when the row
 * opens, so pressing a button changes a colour and never a size: these are in a
 * wrapping flex row, and a border that appeared on open would move the whole
 * line sideways under the reader's hand. */
.bwz-cheat {
  min-width: 0;
  border: 2px solid transparent;
  border-radius: var(--bwz-radius-md);
}
/* The OPEN row. ⚠️ [CRITICAL] IT NO LONGER TAKES THE WHOLE LINE AND ITS PANEL
 * NO LONGER OPENS IN FLOW, SINCE 869fmdb62 (Dan, 2026-10-25). Everything about
 * the geometry this rule used to carry has moved to §28g with the panel it was
 * about; what is left here is the colour change a press makes.
 *
 * [CRITICAL] THIS REVERSES "THE PANEL OPENS IN FLOW AND NEVER COVERS THE
 * BUTTONS BESIDE IT", WHICH WAS A RULE IN CAPITALS IN THIS FILE, AND IT IS DAN'S
 * OWN INSTRUCTION AFTER READING THE DEPLOYED PAGE: "when I hover over a cheat
 * and I get the pop up if I then click the cheat itself then a different
 * expanded view displays. The information in the expanded display is correct but
 * the form is not. When I click on the button, the already visible popup should
 * expand in place to display the full detail on the cards." A panel that opens
 * in flow CANNOT be the popup expanding in place -- it is a second box in a
 * second position, which is the fault he is describing. The cost and the bound
 * are stated in §28g and the superseding decision-log entry is written.
 *
 * ⚠️ WHAT THE OLD RULE WAS PROTECTING IS NOT LOST. `grid-column: 1 / -1` existed
 * because a panel opening inside a 15rem column is the 122px-wide, 1257px-tall
 * ribbon 25.22.0 paid for; a floating panel is sized by its own content and by
 * §28g's cap and is not in that column at all, so the fault that rule answered
 * cannot occur. `max-width: min(54rem, 100%)`'s measurement moves with it. */
.bwz-cheat[open] {
  border-color: var(--bwz-rule-strong);
}

.bwz-cheat__face {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.15rem;
  cursor: pointer;
  font-size: var(--bwz-text-xs);
  line-height: 1.3;
  padding: 0.35rem 0.5rem;
  border: var(--bwz-border) solid var(--bwz-rule);
  border-radius: var(--bwz-radius-sm);
  background: var(--bwz-surface-2);
  color: var(--bwz-fg);
  /* The disclosure triangle, removed in all three engines. */
  list-style: none;
}

.bwz-cheat__face::-webkit-details-marker { display: none; }

.bwz-cheat__face:hover { background: var(--bwz-surface-3); }

/* The execute buttons, and the only accent on the page. ACTION, never status. */
.bwz-cheat__face--run {
  background: var(--bwz-accent);
  color: var(--bwz-accent-fg);
  border-color: var(--bwz-accent);
}

.bwz-cheat__face--run:hover {
  background: var(--bwz-accent);
  color: var(--bwz-accent-fg);
}

/* The card's first line: the mark glyph, the title, the copy glyph. */
.bwz-cheat__head {
  display: flex;
  align-items: baseline;
  gap: 0.35em;
}

/* One glyph, and it must not be squeezed by the words beside it. */
.bwz-cheat__glyph { flex: 0 0 auto; }

/* [CRITICAL] THE TITLE, AND IT HAS NO CEILING OF ANY KIND — no `max-height`, no
 * line clamp, no `text-overflow`. 869fjnggu is the ticket that says so: "the
 * name of the cheat needs to be fully visible". It is a step LARGER than the
 * rest of the card (14px against 12px) so that a card reads as a title with
 * smaller text under it rather than as three equal lines, which is the shape
 * Dan described. `overflow-wrap: anywhere` is what lets a long unbroken token in
 * a label wrap inside a 15rem column instead of widening the grid.
 * ⚠️ Anything here that could hide a character is the defect this ticket was
 * raised about. `test_no_face_ever_cuts_its_title` and the drill's own
 * `no card hides any of its title` both read this back. */
.bwz-cheat__words {
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--bwz-text-sm);
  font-weight: 600;
  overflow-wrap: anywhere;
}

/* The declared description, where there is one. `--bwz-fg-muted` on
 * `--bwz-surface-2` is an EXISTING documented pair in core/design.py, checked at
 * 4.5 in both themes, so this adds nothing for the WCAG sweep to fail on. */
.bwz-cheat__note {
  color: var(--bwz-fg-muted);
  overflow-wrap: anywhere;
}

/* The command. Monospace, because it is a command and a reader needs to see it
 * is one; quiet, because the title is what the eye is scanning. Never clipped
 * either — a half-command is worse than a wrapped one. */
.bwz-cheat__cmd {
  font-family: var(--bwz-font-mono);
  color: var(--bwz-fg-muted);
  overflow-wrap: anywhere;
}

/* On an execute card the accent is the background, so the quiet ink would be
 * unreadable on it: both smaller lines take the accent's own foreground. The
 * pair is `.bwz-btn--primary`'s, token for token, and is already documented. */
.bwz-cheat__face--run .bwz-cheat__note,
.bwz-cheat__face--run .bwz-cheat__cmd { color: var(--bwz-accent-fg); }

.bwz-cheat__panel {
  padding: var(--bwz-space-2);
  border: var(--bwz-border) solid var(--bwz-rule);
  border-block-start: 0;
  border-radius: 0 0 var(--bwz-radius-sm) var(--bwz-radius-sm);
  background: var(--bwz-surface);
}

.bwz-cheat__label { font-weight: 600; margin: 0; }

/* THE OPENED CARD'S LABELLED FACTS (869fgd7uj, Dan 2026-10-20).
 *
 * [CRITICAL] THE HEIGHT COMES OUT OF THE PANEL BECAUSE THE FACTS FLOW INTO
 * COLUMNS, AND THAT IS THE WHOLE MECHANISM. His words: "Organize that info
 * better, in a more card-like format ... carry the detail, but make it fast,
 * easy and unambiguous to read, with a clear hierarchy, scannable labels and
 * nothing repeated that the card already says ... especially on a page with
 * hundreds of similar buttons."
 *
 * Until this round every one of those facts was a full-width muted <p>, so a
 * panel was five or six paragraphs tall however much width it had. They are
 * `auto-fill` grid items now: the detail is not cut, it is laid side by side.
 *
 * ⚠️ `minmax(15rem, 1fr)` IS THE SAME MEASURE `.bwz-cheat-row` USES for a
 * button, deliberately -- a fact column the width of a card is a width this page
 * has already been measured at across six viewports, and at 320px it collapses
 * to one column rather than overflowing. */
.bwz-cheat__facts {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  gap: 0.15rem var(--bwz-space-3);
  margin: 0;
  font-size: var(--bwz-text-xs);
  line-height: 1.4;
}

/* One key-and-value pair. It is a <div> inside the <dl> so that the pair is one
 * grid item and a key can never be laid out away from its own value -- which is
 * what a bare <dt>/<dd> sequence in a grid does. */
.bwz-cheat__fact {
  min-width: 0;
  display: flex;
  flex-direction: column;
}

/* The label. Dan's "scannable labels": every value on this panel has one now,
 * where three of them used to be unlabelled muted paragraphs a reader had to
 * recognise. `--bwz-fg-muted` on the panel's `--bwz-surface` is core/design.py's
 * own documented pair, and the key is the quiet half BECAUSE the value is what
 * is being read. */
.bwz-cheat__fact-key {
  font-weight: 600;
  color: var(--bwz-fg-muted);
  text-transform: uppercase;
  letter-spacing: 0.02em;
}

.bwz-cheat__fact-val {
  margin: 0;
  color: var(--bwz-fg);
  overflow-wrap: anywhere;
}

/* ------------------------------------------------------------------ §28e
 * A CARD THAT OPENS INTO SEVERAL BUTTONS (869fjnggu).
 *
 * [CRITICAL] DAN REJECTED 33.1.0's SHAPE IN WRITING, 2026-10-20: "Do not accept
 * this. This is materially worse than what we had before. What I was thinking is
 * that you would have a card for runner actions which was the same size
 * approximately as all the other buttons on the page, and then when you click
 * that then you get 4 buttons inside and expand it card view [...] laid out in a
 * really clear and simple way which is graphically stylized and appealing,
 * distinct from what it has emerged from in terms of the background so is easy
 * to see, does not give any extraneous detail with all explanations hidden
 * inside a tooltip with only an 'i' symbol showing."
 *
 * 33.1.0 drew the box as a bordered panel with every member on screen at once
 * and the DERIVED base command as its heading. What is here now is one closed
 * card, a declared title, a count, an `i`, and a panel with a background of its
 * own. Dan's Q2(a) 15rem is untouched: a family is still one grid cell.
 *
 * ⚠️ NOT ONE GRADED COLOUR, exactly as §28. The panel is
 * `--bwz-accent-surface` under `--bwz-fg`, an EXISTING documented pair in
 * core/design.py at 4.5 in both themes, described there as "Body text in an
 * accent callout" — which is what this is. It is the accent TINT and never the
 * SOLID `--bwz-accent` fill, which on this page means "pressing this runs a
 * command on the box": §28b's selected tab and §28d's hover title band both
 * make the same distinction for the same reason. Nothing here is
 * ok/warn/danger, and on this page those mean "this check is failing". */

/* The closed card. Deliberately the same chrome as `.bwz-cheat` so it reads as
 * one of the buttons beside it — "the same size approximately as all the other
 * buttons on the page" is his sentence, and a family that announced itself with
 * a different box would be a second visual language on one row. The 2px border
 * is transparent and coloured on open for §28c's reason: pressing a card
 * changes a colour and never a size. */
.bwz-cheat-family {
  min-width: 0;
  border: 2px solid transparent;
  border-radius: var(--bwz-radius-md);
}

/* [CRITICAL] THE OPEN CARD TAKES THE WHOLE ROW, AND THIS IS THE RULE 33.1.0 HAD
 * TO REACH FOR `:has()` TO GET. A member is a child of this element's panel and
 * not a grid item, so `.bwz-cheat[open]`'s own `grid-column` cannot reach it —
 * but this element IS a grid item, and it is `[open]` whenever any member is
 * visible at all, so the span comes from a plain attribute selector every engine
 * has always had. Without it a member's panel opens inside a 15rem column, which
 * is EXACTLY the measured fault 25.22.0 paid for on the ticket control: a panel
 * 122px wide and 1257px tall with every assertion about it green.
 *
 * ⚠️ The `:has()` selector is KEPT rather than deleted and it is now a BELT: a
 * member can only be open inside an open family, so it cannot fire on its own.
 * It is what holds the geometry if this card is ever drawn by something other
 * than a <details> again. */
.bwz-cheat-family[open],
.bwz-cheat-family:has(.bwz-cheat[open]) {
  grid-column: 1 / -1;
}

.bwz-cheat-family[open] {
  border-color: var(--bwz-rule-strong);
  box-shadow: var(--bwz-shadow-md);
}

/* The face. `.bwz-cheat__face`'s own box, token for token, because it is the
 * same kind of thing: a card on a row of cards. */
.bwz-cheat-family__face {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0.15rem;
  cursor: pointer;
  font-size: var(--bwz-text-xs);
  line-height: 1.3;
  padding: 0.35rem 0.5rem;
  border: var(--bwz-border) solid var(--bwz-rule);
  border-radius: var(--bwz-radius-sm);
  background: var(--bwz-surface-2);
  color: var(--bwz-fg);
  list-style: none;
}

.bwz-cheat-family__face::-webkit-details-marker { display: none; }

.bwz-cheat-family__face:hover { background: var(--bwz-surface-3); }

.bwz-cheat-family__head {
  display: flex;
  align-items: baseline;
  gap: 0.35em;
}

.bwz-cheat-family__glyph { flex: 0 0 auto; }

/* The declared title. The same step larger than the rest of the card that a
 * lone button's title is (14px over 12px), and with NO ceiling of any kind for
 * the reason `.bwz-cheat__words` has none: 869fjnggu is the ticket that says a
 * name must be fully visible. */
.bwz-cheat-family__name {
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--bwz-text-sm);
  font-weight: 600;
  overflow-wrap: anywhere;
}

/* The `i`. An affordance and not a control: it says the words are on the hover
 * and in the panel. Quiet, because it is the last thing the eye needs. */
.bwz-cheat-family__info,
.bwz-cheat__info {
  flex: 0 0 auto;
  color: var(--bwz-fg-muted);
}

/* What the closed card is hiding, which a collapsible section owes its closed
 * row (15.2.0). `--bwz-fg-muted` on `--bwz-surface-2` is the documented pair. */
.bwz-cheat-family__count { color: var(--bwz-fg-muted); }

/* [CRITICAL] THE PANEL IS THE "DISTINCT FROM WHAT IT HAS EMERGED FROM IN TERMS
 * OF THE BACKGROUND" HALF OF THE ASK, AND IT IS A DOCUMENTED PAIR RATHER THAN A
 * COLOUR PICKED FOR THIS PAGE. See the section note above. */
.bwz-cheat-family__panel {
  display: flex;
  flex-direction: column;
  gap: var(--bwz-space-1);
  padding: var(--bwz-space-2);
  border: var(--bwz-border) solid var(--bwz-rule);
  border-block-start: 0;
  border-radius: 0 0 var(--bwz-radius-sm) var(--bwz-radius-sm);
  background: var(--bwz-accent-surface);
  color: var(--bwz-fg);
}

/* The shared base, printed once, where the derived truth belongs. Monospace
 * because it is a command. `--bwz-fg` rather than the muted ink, because the
 * documented pair for `--bwz-accent-surface` is the full-strength one. */
.bwz-cheat-family__base {
  margin: 0;
  font-size: var(--bwz-text-xs);
  font-family: var(--bwz-font-mono);
  color: var(--bwz-fg);
  overflow-wrap: anywhere;
}

/* The buttons inside. A narrower floor than the board's 15rem because a member
 * carries one or two words instead of a whole title, which is what lets four of
 * them sit on one line inside a card that was one cell wide when closed. */
.bwz-cheat-family__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
  gap: var(--bwz-space-1);
  align-items: start;
}

/* A member's compact face. The same COLUMN every other card on this page is,
 * and it keeps two of the three lines: the head (mark glyph, one or two words,
 * ⧉, `i`, ssh icon) and the command's own flags. What came off it is the
 * DESCRIPTION line, which is the "extraneous detail ... hidden inside a tooltip
 * with only an 'i' symbol showing" half of the ask — it is still in the panel
 * and on the hover, which are the same panel and the same hover every other
 * control here has. A touch tighter than a lone card, because it is a button
 * inside a card rather than a card. */
.bwz-cheat__face--member { padding: 0.3rem 0.4rem; }


/* ONE class and no modifier. The first draft had `--ok` and `--danger`
 * variants; both resolved to the same ink, because a graded colour is exactly
 * what this page may not carry -- so two classes that cannot differ were two
 * names for one rule. The sentence itself says whether it worked. */
.bwz-cheat-notice { font-weight: 600; }

.bwz-cheat-output__text {
  display: block;
  white-space: pre-wrap;
  max-height: 24rem;
  overflow-y: auto;
}

/* ---------------------------------------------------------------------------
 * 28b. The cheat sheet's search box, environment tabs, collapsible cards,
 *      shell buttons and key (869fgd7uj, Dan 2026-10-14 22:33:49Z)
 *
 * [CRITICAL] STILL NOT ONE GRADED COLOUR. The SELECTED tab wears
 * `--bwz-accent-surface` with `--bwz-fg`, which is an EXISTING documented pair
 * (core/design.py: "Body text in an accent callout", checked at 4.5 in both
 * themes) -- so the WCAG sweep gains nothing to fail on. It is deliberately NOT
 * the solid `--bwz-accent` fill: on this page that fill means *press this and it
 * runs a command on the box*, and a selected tab is neither an action nor a
 * reading. Selection is carried by weight and by a border as well as by the
 * tint, so it survives a reader who cannot tell the two apart.
 *
 * [CRITICAL] THE FILTER IS `~` AND THE TIDYING IS `:has()`, AND THE SPLIT IS
 * THE WHOLE RISK DECISION. Hiding a row is load-bearing, so it uses the general
 * sibling combinator, which every engine has always had. Hiding a GROUP or a
 * CARD whose rows have all gone is cosmetic, so it uses `:has()` -- and an
 * engine without `:has()` loses a stray heading and not a command. There is no
 * script in any of it.
 *
 * [CRITICAL] THE SEARCH BOX IS OUT OF THE LAYOUT UNTIL ITS SCRIPT SAYS IT IS
 * THERE. `:root[data-bwz-cheatfilter="js"]` is set by
 * core/static/core/js/bwz-cheatfilter.js and by nothing else, which is
 * `bwz-nav.js`'s shape: a text box that filters nothing is worse than no text
 * box. Both halves are needed -- the `hidden` attribute is what keeps it out of
 * the accessibility tree, and this rule is what keeps it out of the layout if
 * some other agent strips the attribute.
 * ------------------------------------------------------------------------- */

.bwz-cheat-search { display: none; }

:root[data-bwz-cheatfilter="js"] .bwz-cheat-search:not([hidden]) {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--bwz-space-2);
}

.bwz-cheat-search__label { font-weight: 600; font-size: var(--bwz-text-xs); }

.bwz-cheat-search__box {
  flex: 1 1 16rem;
  max-width: var(--bwz-measure);
  padding: 0.35rem 0.5rem;
  border: var(--bwz-border) solid var(--bwz-rule);
  border-radius: var(--bwz-radius-sm);
  background: var(--bwz-surface);
  color: var(--bwz-fg);
  font: inherit;
}

.bwz-cheat-search__count { flex: 0 0 auto; margin: 0; }

/* Visually hidden and still focusable. The ring is drawn on the matching label
 * below, because a focus ring on a 1px box is a focus ring nobody can see. */
.bwz-cheat-env__radio {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.bwz-cheat-env {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bwz-space-1);
}

.bwz-cheat-env__ask {
  font-size: var(--bwz-text-xs);
  font-weight: 600;
  color: var(--bwz-fg-subtle);
  margin-inline-end: var(--bwz-space-1);
}

.bwz-cheat-env__tab {
  display: inline-flex;
  align-items: baseline;
  gap: 0.4em;
  cursor: pointer;
  font-size: var(--bwz-text-xs);
  padding: 0.3rem 0.6rem;
  border: var(--bwz-border) solid var(--bwz-rule);
  border-radius: var(--bwz-radius-sm);
  background: var(--bwz-surface-2);
  color: var(--bwz-fg);
}

.bwz-cheat-env__tab:hover { background: var(--bwz-surface-3); }

#cheat-env-all:checked ~ .bwz-cheat-env [for="cheat-env-all"],
#cheat-env-pwsh:checked ~ .bwz-cheat-env [for="cheat-env-pwsh"],
#cheat-env-shell:checked ~ .bwz-cheat-env [for="cheat-env-shell"] {
  background: var(--bwz-accent-surface);
  border-color: var(--bwz-accent);
  font-weight: 700;
}

#cheat-env-all:focus-visible ~ .bwz-cheat-env [for="cheat-env-all"],
#cheat-env-pwsh:focus-visible ~ .bwz-cheat-env [for="cheat-env-pwsh"],
#cheat-env-shell:focus-visible ~ .bwz-cheat-env [for="cheat-env-shell"] {
  outline: var(--bwz-border-thick) solid var(--bwz-accent);
  outline-offset: 2px;
}

/* The filter itself. Load-bearing, so `~` and nothing cleverer. */
#cheat-env-pwsh:checked ~ .bwz-cheat-board .bwz-cheat[data-cheat-env="shell"],
#cheat-env-shell:checked ~ .bwz-cheat-board .bwz-cheat[data-cheat-env="pwsh"] {
  display: none;
}

/* Cosmetic tidying only: a group or a card whose rows have all been filtered
 * away. An engine without `:has()` leaves an empty heading and loses nothing. */
#cheat-env-pwsh:checked ~ .bwz-cheat-board
  .bwz-cheat-group:not(:has(.bwz-cheat[data-cheat-env="pwsh"])),
#cheat-env-shell:checked ~ .bwz-cheat-board
  .bwz-cheat-group:not(:has(.bwz-cheat[data-cheat-env="shell"])),
#cheat-env-pwsh:checked ~ .bwz-cheat-board
  .bwz-cheat-estate:not(:has(.bwz-cheat[data-cheat-env="pwsh"])),
#cheat-env-shell:checked ~ .bwz-cheat-board
  .bwz-cheat-estate:not(:has(.bwz-cheat[data-cheat-env="shell"])) {
  display: none;
}

/* The same tidying for a FAMILY card, which is a fourth kind of thing the two
 * filters can empty (869fjnggu). Without it a tab leaves a bordered box with a
 * base command in it and no buttons under it — a card that reads as a fault.
 * ⚠️ TWO RULES AND NOT ONE, because the two filters empty a family by two
 * different mechanisms and neither can see the other's: the tabs are a
 * stylesheet and set `display: none`, which `:has()` cannot test, while
 * `bwz-cheatfilter.js` sets the `hidden` ATTRIBUTE, which it can. Hiding the
 * box here rather than in that file is also what keeps its own rule true: it may
 * hide "the cheat board's own rows, groups and estate cards", and a family is
 * none of the three. */
#cheat-env-pwsh:checked ~ .bwz-cheat-board
  .bwz-cheat-family:not(:has(.bwz-cheat[data-cheat-env="pwsh"])),
#cheat-env-shell:checked ~ .bwz-cheat-board
  .bwz-cheat-family:not(:has(.bwz-cheat[data-cheat-env="shell"])) {
  display: none;
}

/* The search box's half of it. Every member carries `hidden`, so the box has
 * nothing left to hold. */
.bwz-cheat-family:not(:has(.bwz-cheat:not([hidden]))) {
  display: none;
}

/* The estate summary is a wrapping row now, because the shell buttons sit in it
 * and a title row that cannot wrap pushes them off a narrow screen. */
.bwz-cheat-estate__summary { flex-wrap: wrap; }

.bwz-cheat-estate__where {
  font-size: var(--bwz-text-xs);
  color: var(--bwz-fg-muted);
  max-width: var(--bwz-measure);
  margin: 0 0 var(--bwz-space-2);
}

/* THE SHELL BUTTONS. A terminal-ish chrome built from neutral tokens rather
 * than §24's `.bwz-pwsh`, which is deliberately NOT reused: that control is
 * shared with the product cards, its width is tuned to a 288px card and its
 * label is capped at five characters (ops/shells.py:MAX_LABEL_CHARS), and the
 * label here is a landing ACCOUNT -- `runcloud` is eight. Restyling a shared
 * control to fit a new page is a monitoring defect waiting to happen.
 *
 * [CRITICAL] `flex: 0 1 auto` AND NEVER `0 0 auto` AGAIN -- 869fmdb62 round 4,
 * Dan's Q1(a), 2026-10-28: "let that row wrap". [M] 2026-10-26 this row was
 * 1367px of buttons under `0 0 auto`, which never shrinks, so the
 * `flex-wrap: wrap` two lines up never took effect and the estate card's own
 * `overflow: clip visible` (§28h.1) cut off up to 1020px of it below roughly
 * 1,400px -- buttons drawn nowhere and impossible to press. Allowed to SHRINK,
 * the row first drops onto its own line of the summary (which wraps) and then
 * wraps its own buttons to fit, down to one button wide, which is its min-content
 * and the reason `min-width` is left at `auto`. A window wide enough for one line
 * is unchanged: nothing shrinks that already fits. The compact form inside a
 * family card keeps `0 0 auto` below, deliberately -- it is one glyph wide. */
.bwz-cheat-shells {
  display: inline-flex;
  flex-wrap: wrap;
  gap: var(--bwz-space-1);
  flex: 0 1 auto;
}

.bwz-cheat-shell {
  display: inline-flex;
  align-items: center;
  gap: 0.3em;
  font-size: var(--bwz-text-xs);
  font-family: var(--bwz-font-mono);
  padding: 0.2rem 0.45rem;
  border: var(--bwz-border) solid var(--bwz-rule);
  border-radius: var(--bwz-radius-sm);
  background: var(--bwz-surface-3);
  color: var(--bwz-fg);
  text-decoration: none;
}

.bwz-cheat-shell:hover { background: var(--bwz-surface-2); }

/* The icon beside a button inside a family card (869fjnggu). The label span is
 * not rendered at all in this form — see _cheat_shells.html — so the button is
 * the glyph and its own padding, and the accessible name is the `aria-label`
 * exactly as it is on the wide form. */
.bwz-cheat-shells--compact { flex: 0 0 auto; }

.bwz-cheat-shell__glyph { flex: 0 0 auto; }

/* A group is a <details> now, so its heading is a <summary> and needs the
 * marker removed in all three engines exactly as the button faces do. */
.bwz-cheat-group__head {
  display: flex;
  align-items: center;
  gap: var(--bwz-space-1);
  cursor: pointer;
  list-style: none;
}

.bwz-cheat-group__head::-webkit-details-marker { display: none; }

.bwz-cheat-group__glyph { flex: 0 0 auto; font-size: 1rem; line-height: 1; }

.bwz-cheat-group__name { flex: 0 1 auto; }

.bwz-cheat-group__count { flex: 0 0 auto; opacity: 0.75; }

/* The copy affordance: a glyph, never a control. It is the last thing on the
 * face and it is quiet, because the words are what a reader is scanning. */
.bwz-cheat__copy { flex: 0 0 auto; opacity: 0.6; }

.bwz-cheat__face:hover .bwz-cheat__copy { opacity: 1; }

/* The status line is FIRST in the panel so "Copied to the clipboard" lands
 * where the eye already is, and it collapses to nothing before it has anything
 * to say rather than leaving a blank line above the command. */
.bwz-cheat__status:empty { display: none; }

.bwz-cheat-key__list {
  display: grid;
  grid-template-columns: minmax(8rem, max-content) 1fr;
  gap: var(--bwz-space-1) var(--bwz-space-3);
  margin: 0;
  font-size: var(--bwz-text-xs);
}

.bwz-cheat-key__term { font-weight: 600; margin: 0; }

.bwz-cheat-key__glyph { margin-inline-end: 0.35em; }

.bwz-cheat-key__meaning { margin: 0; color: var(--bwz-fg-muted); }

@media (max-width: 36em) {
  .bwz-cheat-key__list { grid-template-columns: 1fr; }
  .bwz-cheat-key__meaning { margin-block-end: var(--bwz-space-2); }
}

/* ---------------------------------------------------------------------------
 * 28c. An OPEN cheat, the input box, and the $PROFILE blocks
 *      (869fgd7uj, Dan 2026-10-15)
 *
 * Dan: *"clicking a button currently expands it, but it is still unclear where
 * the details that have expanded stop and where the next button or other piece
 * of information starts. Put a much clearer border around a card that has
 * opened."*
 *
 * [CRITICAL] THE BORDER GOES ON THE `<details>` AND NOT ON THE TWO HALVES
 * INSIDE IT, which is what makes the boundary one rule round one box rather than
 * a face-border meeting a panel-border. §28 gave the face a border and the panel
 * a border with `border-block-start: 0`, which reads as one box while the row is
 * CLOSED (there is only a face) and reads as a seam while it is open. So: the
 * open row takes a 2px border, a radius, a lifted surface and a shadow, and the
 * face and the panel inside it give their own borders up.
 *
 * [CRITICAL] NOT A GRADED COLOUR AND NOT THE ACCENT EITHER. ok / warn / danger
 * mean *this check is failing* on this dashboard, and `--bwz-accent` is the
 * ACTION colour -- which on this page means *pressing this runs a command on the
 * box*, so an accent frame round every opened copy row would say that of 316
 * rows that do not. `--bwz-rule-strong` is the strong NEUTRAL, and the shadow is
 * what carries "this is in front of the row below it".
 *
 * ⚠️ `box-shadow` RATHER THAN A WIDER BORDER for the lift: a border changes the
 * box's own size and these sit in a wrapping flex row, so 2px of border on one
 * open row is 2px the row's other buttons do not have. The border is declared on
 * the CLOSED state too, transparent, so opening a row re-colours it instead of
 * re-sizing it -- and nothing moves sideways when a reader presses a button.
 * =========================================================================== */

/* ⚠️ The closed state's own 2px transparent border is declared in §28, on the
 * ONE `.bwz-cheat` rule there is. A second rule for the same selector here
 * would be two places to edit and one of them would go stale -- and a test that
 * reads a rule by its selector would read whichever came first. */
/* Inside an open row the two halves stop drawing their own edges: one box, one
 * border. The face keeps its bottom rule, which is what separates the control
 * from the prose under it. */
/* The pressed face. ⚠️ IT KEEPS ITS OWN SHAPE SINCE 869fmdb62: the panel is no
 * longer the bottom half of this box, so squaring off the bottom corners and
 * stretching to `width: 100%` would be a button changing size under the hand
 * that pressed it -- §28c's own rule, applied to the state that used to be the
 * exception to it. What is left is the quiet fill that says WHICH button the
 * floating panel belongs to. */
.bwz-cheat[open] > .bwz-cheat__face {
  background: var(--bwz-surface-3);
}

/* ⚠️ THE OPEN PANEL'S OWN RULE IS IN §28g SINCE 869fmdb62 AND THERE IS STILL
 * EXACTLY ONE OF THEM. It used to give up its border and its background here,
 * because it was the bottom half of the row's one box; it floats now and draws
 * its own chrome, and a second rule for this selector would be two places to
 * edit with `_rule`'s own substring hazard waiting in between. */

/* THE INPUT BOX. Two rows have one (ops/cheatsheet.py:VARIABLE_ROWS). It wraps,
 * because the label, the box and the button are three things in a panel that is
 * as narrow as the card at 320px. */
.bwz-cheat__fill {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bwz-space-2);
}

.bwz-cheat__fill-label {
  font-size: var(--bwz-text-xs);
  font-weight: 600;
}

.bwz-cheat__fill-box {
  flex: 1 1 10rem;
  min-width: 8rem;
  max-width: 20rem;
  padding: var(--bwz-space-1) var(--bwz-space-2);
  border: var(--bwz-border) solid var(--bwz-rule-strong);
  border-radius: var(--bwz-radius-sm);
  background: var(--bwz-surface);
  color: var(--bwz-fg);
  font-family: var(--bwz-font-mono);
  font-size: var(--bwz-text-xs);
}

/* THE $PROFILE BLOCKS. `white-space: pre` is the fallback as much as the
 * formatting: the whitespace in these is load-bearing, so with scripts blocked
 * the whole block has to be on screen as selectable text in the shape it will be
 * pasted in. It scrolls rather than wrapping, because a wrapped PowerShell line
 * reads as two statements. */
.bwz-cheat__profile {
  display: block;
  max-height: 22rem;
  overflow: auto;
  white-space: pre;
  font-size: var(--bwz-text-xs);
  line-height: 1.45;
}

/* ⚠️ THE HELPER COUNT IS IN THE FACE'S WORDS AND NOT IN A `.bwz-badge`, and
 * that is measured rather than stylistic: a badge has its own padding and font
 * size, which made these four faces two line boxes tall where the other 318 are
 * one -- and one-line faces are what "maximise the number of cheats on one
 * screen" rests on. The tally is still on the closed row, which is the rule
 * (15.2.0); it is in the label. */

/* A block's face is wider than a cheat's, because its label is a sentence
 * rather than four words. It is still a <summary> and still the control. */
.bwz-cheat--profile { flex: 0 1 auto; }

/* The setup card's own prose. A class of its own and NOT
 * `.bwz-cheat-estate__where`, which is the ONE-LINER each estate card carries
 * and whose count a test asserts exactly: borrowing it here would have made
 * that number 7 where there are 5 estates, and the honest fix is a second class
 * rather than a looser assertion. */
.bwz-cheat-setup__note {
  font-size: var(--bwz-text-xs);
  color: var(--bwz-fg-muted);
  max-width: var(--bwz-measure);
  margin: 0 0 var(--bwz-space-2);
}

/* ---------------------------------------------------------------------------
 * 28d. The FLOATING HOVER TABLE (869fgd7uj, Dan 2026-10-18)
 *
 * Dan, picking (b) over the recommendation: *"build the bordered hover table as
 * a floating panel, with the superseding decision-log entry."* He was shown in
 * the option text that it reverses a `[CRITICAL]` rule and he took it.
 * Decision log 2026-10-18, superseding the entry of 2026-10-15.
 *
 * [CRITICAL] THIS IS THE ONE SECTION IN THIS STYLESHEET THAT OVERLAYS PROSE ON A
 * CONTROL, AND §22 IS UNTOUCHED EVERYWHERE ELSE. §22's rule — a note is a
 * `<details>` in flow, never `:hover`, never a popover, because an overlaying
 * panel "either blocks the control it explains or passes the click onto what is
 * underneath, which here can be STOP" — still governs the runner cards, the
 * answer-sheet notes and every other surface on this site. What bought the
 * exception on THIS page is the thing §22 could not have: a measurement that the
 * hazard it names cannot arise here.
 *
 * [CRITICAL] `pointer-events: none` IS THE WHOLE OF THAT, AND IT IS NOT
 * DECORATION. The panel never receives a press, so it cannot BLOCK one. And the
 * press cannot be MISROUTED either, because the panel exists only while the
 * pointer is on its own control — that is why it is showing — so by the time a
 * pointer is over a covered button the panel has gone. There is no STOP on this
 * page: the worst a stray press can do here is copy a string or open a panel.
 * DELETING THAT ONE DECLARATION RE-OPENS §22's HAZARD IN FULL.
 *
 * [CRITICAL] IT IS `display: none` BY DEFAULT AND ONE RULE TURNS IT ON. That is
 * `bwz-nav.js`'s and §25's direction of failure: an engine that cannot evaluate
 * the reveal rule shows nothing, which is the page 29.1.0 shipped, rather than
 * 318 permanently visible panels stacked on one another.
 *
 * ⚠️ IT IS SUPPRESSED ON AN OPEN ROW. An open `<details>` already has every one
 * of these words in it as real text, and two copies of one thing on screen at
 * once is the duplicate a reader cannot explain.
 *
 * ⚠️ `:focus-visible` AS WELL AS `:hover`, which is strictly more than the
 * `title=` it replaces ever gave a keyboard. The `<details>` panel is still the
 * accessible path and the no-script fallback, and that is unchanged: a hover is
 * invisible on a touch screen and reaches no pointer-less reader, which is §22's
 * own reason and the stated, accepted cost of this reversal.
 *
 * ⚠️ NOT ONE GRADED COLOUR, exactly as §28 -- and it HAS colour since
 * 2026-10-19, which is not the same sentence. It read *"it wears the chrome a
 * card wears"* until Dan asked for colour, and what it wears now is an accent
 * TINT on the title and a tinted header column, both of them pairs
 * `core/design.py` already documents.
 *
 * ⚠️ AND IT NO LONGER WEARS THE CHROME A BUTTON WEARS EITHER, SINCE 2026-10-20.
 * Having colour was not the same as standing APART from the thing it explains:
 * Dan read the 2026-10-19 page and said so. The panel's own three declarations
 * carry the whole of that -- a 2px border, `--bwz-surface-3` behind the body and
 * `--bwz-shadow-lg` -- and the rule block says why. The colours that GRADE something on this
 * dashboard are ok / warn / danger, none of the three is here, and a test
 * asserts their absence: a graded colour on a hover would say *this check is
 * failing* about a button.
 * =========================================================================== */

/* The containing block. A `<summary>` is `display: inline-flex` in §28 and an
 * `<a>` is inline, so `position: relative` on the control itself is what makes
 * the panel sit under THAT button rather than under the row. */
.bwz-cheat__face,
.bwz-cheat-shell { position: relative; }

/* [CRITICAL] THE EDGE FLIP, WHICH IS DAN'S "don't allow a pop up to be missing
 * off the side of a smaller screen" (869fmdb62, 2026-10-25, with a screenshot of
 * the Memory panel cut off at the right).
 *
 * A panel anchored at `inset-inline-start: 0` on a button near the right edge
 * runs past the viewport, and NO selector can know that: it is arithmetic on a
 * rendered box. `core/static/core/js/bwz-cheatpop.js` measures it and writes one
 * attribute; these two rules are the whole of what that attribute may mean. With
 * that file blocked no attribute is ever written, every panel opens from the
 * start edge, and the page is exactly what it is today -- the same direction
 * every other enhancement on this page fails in.
 *
 * ⚠️ `end` IS AN ANCHOR AND NOT A TRANSFORM. Moving the panel with `translate`
 * would keep its layout box where it was, so the next measurement would read the
 * old geometry and the flip would oscillate. */
[data-bwz-pop="end"] > .bwz-cheat-hover,
[data-bwz-pop="end"] > .bwz-cheat__face > .bwz-cheat-hover,
[data-bwz-pop="end"] > .bwz-cheat__panel {
  inset-inline-start: auto;
  inset-inline-end: 0;
}

.bwz-cheat-hover {
  /* DEFAULT OFF. See the note above: the fallback is no panel, never 318. */
  display: none;
  position: absolute;
  inset-block-start: calc(100% + 0.25rem);
  inset-inline-start: 0;
  z-index: var(--bwz-z-overlay);
  /* [CRITICAL] The declaration that answers §22. Never delete it. */
  pointer-events: none;
  width: max-content;
  max-width: min(34rem, 88vw);
  /* [CRITICAL] A 2px BORDER AND A SURFACE THE BUTTONS DO NOT USE, AND BOTH ARE
   * DAN'S OWN WORDS OF 2026-10-20: "there's no contrast with the colour scheme
   * of existing buttons and/or strong border so it's hard to read. make them
   * more visually separate." He was shown the 2026-10-19 panel, which carried a
   * 1px `--bwz-rule-strong` edge and `--bwz-surface` behind its body -- one step
   * LIGHTER than the `--bwz-surface-2` of the button it was floating over, which
   * is a difference a reader has to look for.
   *
   * So the border is doubled to the 2px this page already reserves on a card
   * (§28c) and the lift goes up a step.
   *
   * [CRITICAL] AND THE BODY STAYS `--bwz-surface` BECAUSE `--bwz-surface-3` IS
   * THE HOVERED FACE'S OWN BACKGROUND -- MEASURED, after this round's first
   * draft took it. `.bwz-cheat__face:hover` below sets `--bwz-surface-3`, and
   * THIS PANEL EXISTS ONLY WHILE THAT CONTROL IS HOVERED, so [M] 2026-10-21 the
   * drill read the panel and the face as the SAME computed colour --
   * rgb(233, 238, 247) against rgb(233, 238, 247), 18 times across three engines
   * -- which is the exact fault Dan reported, reintroduced by the change meant
   * to fix it. A surface step is not available here: the only one darker than
   * the resting button is the one the button takes when it is hovered, and
   * `--bwz-accent-surface` is a hair off `--bwz-surface-3` in the light theme --
   * see core/design.py, where they differ by one step in the blue channel and
   * nothing else. SO THE SEPARATION IS THE BORDER AND THE LIFT, and that
   * is the half of his "and/or" that role tokens can carry. It is said on the
   * ticket rather than presented as the colour he asked for.
   *
   * ⚠️ THE KEY COLUMN STAYS `--bwz-surface-2` and the title band stays
   * `--bwz-accent-surface`: both are that token's own documented use, and
   * `--bwz-fg-muted` on the first is core/design.py's "Table header label" pair.
   * Those two are the colour 32.2.0 added and they are untouched.
   *
   * ⚠️ STILL NOT ONE GRADED COLOUR AND STILL NOT THE SOLID ACCENT. ok / warn /
   * danger mean "this check is failing" on this dashboard, and `--bwz-accent`
   * means "pressing this runs a command on the box" on this page -- so neither
   * may be the thing that makes a hover stand out, however well it would work.
   * A neutral rule token and a neutral surface are what is left, and they are
   * enough because the border is 2px.
   *
   * ⚠️ `--bwz-shadow-lg` RATHER THAN `-md`: this is a floating panel over a page
   * of 300 buttons, and the lift is the third cue after the border and the
   * surface. It costs no layout, which is why it is available at all (§28c). */
  border: 2px solid var(--bwz-rule-strong);
  border-radius: var(--bwz-radius-sm);
  background: var(--bwz-surface);
  color: var(--bwz-fg);
  box-shadow: var(--bwz-shadow-lg);
  font-size: var(--bwz-text-xs);
  line-height: 1.4;
  text-align: start;
  /* [CRITICAL] NO PADDING ON THE PANEL, AND THAT IS WHAT MAKES THIS A TABLE
   * RATHER THAN A PANEL WITH A TABLE IN IT. The title band and the cells carry
   * their own padding, so with none here the band and the row rules run edge to
   * edge against the border -- and `overflow: hidden` is what clips the band's
   * corners to the radius, which a background on a child does not do by itself.
   *
   * [CRITICAL] `white-space: pre-wrap` IS ON `__val` AND MUST NEVER COME BACK
   * HERE. It was on the panel from 2026-10-18 to 2026-10-19, and the panel's
   * own children are separated in `_cheat_hover.html` by ordinary template
   * indentation -- `"\n  "` text nodes -- which `pre-wrap` PRESERVES AND DRAWS
   * AS REAL LINES. [M] 2026-10-19: three such nodes rendered 35px above the
   * title, 29px between the title and the table and 18px below it, so 82px of a
   * 192px panel was blank lines nothing had written. That is the whole of Dan's
   * *"there is a lot of white space in the hover section"*, and it is why the
   * declaration belongs on the one element whose text is whitespace-significant
   * and nowhere else. */
  overflow: hidden;
}

/* THE ONE RULE THAT TURNS IT ON. `:not([open])` is the suppression above. */
.bwz-cheat:not([open]) > .bwz-cheat__face:hover > .bwz-cheat-hover,
.bwz-cheat:not([open]) > .bwz-cheat__face:focus-visible > .bwz-cheat-hover,
.bwz-cheat-shell:hover > .bwz-cheat-hover,
.bwz-cheat-shell:focus-visible > .bwz-cheat-hover {
  display: block;
}

/* [CRITICAL] AND THE ONE RULE THAT TURNS IT OFF AGAIN, WHICH IS DAN'S "ONLY ONE
 * THING DISPLAYS AT A TIME" (869fmdb62, 2026-10-25, with a screenshot).
 *
 * [M] THE OVERLAP WAS NOT TWO CONTROLS, IT WAS ONE. His screenshot shows the
 * Uptime button's own hover table and a small `blueworkz - prod` panel drawn
 * across it. Both belong to the SAME button: a quick-action face carries a
 * compact `.bwz-cheat-shell` anchor inside its `__head`, and `:hover` matches
 * every ANCESTOR of the pointed element -- so putting the pointer on that `⌨`
 * satisfies `.bwz-cheat-shell:hover` AND `.bwz-cheat__face:hover` at the same
 * instant, and the two rules above each turned a panel on. Nothing was wrong
 * with either rule on its own, which is why four days of drill checks in three
 * engines never saw it: each panel was measured alone.
 *
 * So the inner control wins, because it is the more specific thing the pointer
 * is actually on. `:has()` is the only selector that can say "this face contains
 * a hovered shell"; an engine without it draws both panels, which is exactly
 * today's page -- degraded, never broken, and the direction every disclosure on
 * this site fails in.
 *
 * ⚠️ IT IS THE FACE'S OWN HOVER THAT IS SUPPRESSED AND NEVER THE SHELL'S. The
 * shell panel names the box a press will open, which is the thing the reader is
 * reaching for; the face's panel is still one pointer-move away and is still in
 * the opened popup as real text. */
/* ⚠️ [CRITICAL] THE FULL ANCESTOR CHAIN IS A SPECIFICITY REQUIREMENT AND NOT
 * TIDINESS, AND IT IS MEASURED. [M] 2026-10-25, 18 failures across three engines
 * and six viewports: written as `.bwz-cheat__face:has(…) > .bwz-cheat-hover` this
 * rule scores (0,4,0) -- `:has()` takes the specificity of its most specific
 * argument, which is `.bwz-cheat-shell:hover` at (0,2,0) -- against the reveal
 * rule above at (0,5,0), so `display: block` won and BOTH panels still drew. The
 * fix is not `!important`: it is to say the same thing the reveal rule says and
 * then one thing more, which is what the chain does. A reader adding a selector
 * to either rule has to keep them in step, and the drill is what checks it. */
.bwz-cheat:not([open]) > .bwz-cheat__face:has(.bwz-cheat-shell:hover) > .bwz-cheat-hover,
.bwz-cheat:not([open]) > .bwz-cheat__face:has(.bwz-cheat-shell:focus-visible) > .bwz-cheat-hover {
  display: none;
}

/* The heading: the same words as the button face, which is Dan's first line.
 *
 * ⚠️ IT IS A TINTED BAND SINCE 2026-10-19 AND THE TOKEN IS NOT THE SOLID ACCENT.
 * Dan: *"they could also use some colour."* `--bwz-accent-surface` under
 * `--bwz-fg` is a pair `core/design.py` already documents at 4.5 in both themes
 * (*"Body text in an accent callout"*), so the WCAG sweep gains nothing to fail
 * on -- and the TINT is deliberately not `--bwz-accent`'s solid fill, which on
 * this page means *pressing this runs a command on the box* (§28b's selected
 * tab made the same distinction for the same reason). The `margin-block-end`
 * went with it: a band's own bottom edge is the rule, where a hairline running
 * 526px under 244px of text was the white space it was supposed to divide. */
.bwz-cheat-hover__title {
  display: block;
  font-weight: 600;
  padding: 0.2rem 0.4rem;
  background: var(--bwz-accent-surface);
  color: var(--bwz-fg);
  border-block-end: var(--bwz-border) solid var(--bwz-rule-strong);
}

/* THE BORDERED TABLE. A grid of spans rather than a `<table>`, because a
 * `<summary>`'s content model is phrasing content — and this is what lines the
 * columns up, which is the one thing a `title=` could not do however it was
 * padded. The row rules are what make it read as a table rather than as a
 * paragraph. */
.bwz-cheat-hover__grid {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0;
}

.bwz-cheat-hover__key,
.bwz-cheat-hover__val {
  padding: 0.2rem 0.4rem;
  border-block-start: var(--bwz-border) solid var(--bwz-rule);
}

.bwz-cheat-hover__key:first-of-type,
.bwz-cheat-hover__key:first-of-type + .bwz-cheat-hover__val {
  border-block-start: 0;
}

/* ⚠️ A TINTED HEADER COLUMN, and `--bwz-surface-2`'s own entry in
 * `core/design.py` reads *"Tinted band, table header, card footer"* -- so this
 * is the token's documented use rather than a colour chosen for this page, and
 * `--bwz-fg-muted` on it is that file's *"Table header label"* pair at 4.5 in
 * both themes. It is the second half of what makes a reader see a table. */
.bwz-cheat-hover__key {
  font-weight: 600;
  color: var(--bwz-fg-muted);
  background: var(--bwz-surface-2);
  border-inline-end: var(--bwz-border) solid var(--bwz-rule);
  white-space: nowrap;
}

/* [CRITICAL] THE ONE ELEMENT ON THIS PANEL WHOSE TEXT IS WHITESPACE-SIGNIFICANT,
 * AND THEREFORE THE ONLY ONE THAT MAY CARRY `pre-wrap`. A command must not be
 * re-wrapped into a lie about where its spaces are; `pre-wrap` keeps them and
 * still wraps a long one rather than running off the screen, and
 * `overflow-wrap: anywhere` breaks one with no spaces in it at all. Putting
 * either on the panel draws the template's own indentation as blank lines --
 * see the measurement in `.bwz-cheat-hover` above. */
.bwz-cheat-hover__val {
  font-family: var(--bwz-font-mono);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* ------------------------------------------------------------------ §28g
 * THE POPUP THAT GROWS IN PLACE, AND THE COMPRESSED LIST BEHIND THE DROPDOWN
 * (869fmdb62, Dan 2026-10-25, after reading the deployed 40.1.0 page)
 *
 * Two of his four sentences on that page are in here and they are one change:
 *
 *   *"when I hover over a cheat and I get the pop up if I then click the cheat
 *   itself then a different expanded view displays. The information in the
 *   expanded display is correct but the form is not. When I click on the button,
 *   the already visible popup should expand in place to display the full detail
 *   on the cards. They commands, what it does, use, lines should stay as they
 *   are and the popup should get larger to display the additional information,
 *   in the same format as the pop up, with some buttons at the bottom."*
 *
 *   *"clicking the all of bluework'z' cheats link should not just expand the
 *   existing set of cards in place, rather it should tabulate all of them so I
 *   get something more akin to a drop down list I can scroll up and down ... two
 *   columns, title and description, never the command itself, and copy buttons
 *   on each line ... as compressed as possible so if there are 20 cheats then I
 *   see 20 lines rather than a whole screen full of buttons."*
 *
 * [CRITICAL] THIS REVERSES `THE PANEL OPENS IN FLOW AND NEVER COVERS A CONTROL`,
 * WHICH WAS A RULE IN CAPITALS IN §28c, AND THE BOUND IS THE ACCORDION
 *
 * §22's refusal of an overlay is that it *"either blocks the control it explains
 * or passes the click onto what is underneath"*. §28d bought its exception with
 * `pointer-events: none` -- a panel that never receives a press. **This panel
 * does receive presses**, so that answer is not available to it and a different
 * one is owed:
 *
 *   1. **It exists only on a deliberate press and one press closes it**, which
 *      is not true of a hover. A reader who cannot reach a covered button has
 *      the same one press to get it back.
 *   2. **At most ONE is open on the whole page.** `name="bwz-cheat"` makes all
 *      322 rows, the quick buttons and the four `$PROFILE` blocks a single
 *      exclusive-accordion group, so the engine closes the previous one before
 *      opening this one. That is also Dan's *"only one thing displays at a
 *      time"* on this same ticket.
 *   3. **Nothing it can cover runs anything.** §22's named hazard is a misrouted
 *      click landing on STOP; every control under this panel is a copy row, and
 *      the three execute rows are BlueWorkz's own, on this page, behind a POST.
 *
 * ⚠️ THE STATED COST, which is the old rule's own sentence and is not denied: a
 * panel this size covers several buttons while it is open. That is what *"expand
 * in place"* means and it was asked for in those words.
 *
 * [CRITICAL] IT IS THE SAME CHROME AND THE SAME GRID AS §28d, DRAWN BY THE SAME
 * CLASSES. `_cheat_panel.html` emits `.bwz-cheat-hover__title`, `__grid`, `__key`
 * and `__val` -- the hover's own three rules, which are not scoped under
 * `.bwz-cheat-hover` and never were. So *"the same format as the pop up"* is not
 * a second set of declarations kept in step by hand: there is one set. The only
 * additions here are the box itself and the footer. */

/* The containing block for the opened panel. The face is already `position:
 * relative` for the hover; the panel is the face's SIBLING, so its anchor has to
 * be the row. Both panels therefore start at the same inline edge, which is what
 * makes the growth read as one box rather than two. */
.bwz-cheat { position: relative; }

.bwz-cheat[open] > .bwz-cheat__panel {
  position: absolute;
  /* The hover's own offset, to the pixel: the popup does not move when it
   * grows. §28d sets the same two values on `.bwz-cheat-hover`. */
  inset-block-start: calc(100% + 0.25rem);
  inset-inline-start: 0;
  z-index: var(--bwz-z-overlay);
  width: max-content;
  /* ⚠️ `30rem` AND NOT §28d's `34rem`. This panel carries every row the hover
   * does plus four more, so it is the taller of the two and the narrower cap is
   * what keeps the flip in §28d able to find room on an ordinary laptop screen.
   * `92vw` is the floor at 320px, where a card is the whole width anyway. */
  max-width: min(30rem, 92vw);
  /* The hover's three separation cues, unchanged and for its reasons: a 2px
   * border because `--bwz-surface-3` is the hovered face's own background, and
   * the lift because this floats over a page of 300 buttons. */
  border: 2px solid var(--bwz-rule-strong);
  border-radius: var(--bwz-radius-sm);
  background: var(--bwz-surface);
  color: var(--bwz-fg);
  box-shadow: var(--bwz-shadow-lg);
  font-size: var(--bwz-text-xs);
  line-height: 1.4;
  text-align: start;
  /* NO PADDING, for §28d's reason: the title band and the cells carry their own,
   * so the rules run edge to edge against the border. `overflow: hidden` clips
   * the band to the radius; the panel is capped in height and scrolls rather
   * than running off the bottom of a short window. */
  padding: 0;
  overflow: hidden auto;
  max-height: min(32rem, 80vh);
}

/* The footer. Dan's *"some buttons at the bottom"* -- the one part of this panel
 * that is not a row of the table, so it is divided from it by the same rule the
 * rows use rather than by a gap. */
.bwz-cheat__foot {
  display: flex;
  flex-direction: column;
  gap: var(--bwz-space-1);
  padding: 0.35rem 0.4rem;
  border-block-start: var(--bwz-border) solid var(--bwz-rule-strong);
  background: var(--bwz-surface-2);
}

.bwz-cheat__foot-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bwz-space-2);
  margin: 0;
}

.bwz-cheat__foot-note { margin: 0; }

/* The copy confirmation. It is empty until a press, so it must not reserve a
 * line: `:empty` keeps the footer the height of its buttons until there is
 * something to say. */
.bwz-cheat__status:empty { display: none; }

/* ------------------------------------------------------------------ §28g.2
 * THE COMPRESSED LIST
 *
 * [CRITICAL] A LIST AND NOT A GRID, WHICH IS THE WHOLE OF *"20 cheats, 20
 * lines"*. `.bwz-cheat-row`'s `minmax(15rem, 1fr)` gives every card a column and
 * a mini-card face three lines tall; at 322 rows that is the *"whole screen full
 * of buttons"* Dan is describing. These are block rows with a 2-column inner
 * grid, so a group of 20 is 20 line boxes however wide the window is. */
.bwz-cheat-list {
  display: flex;
  flex-direction: column;
}

/* ⚠️ NO BORDER AND NO RADIUS ON A LINE, AND THE SEPARATOR IS A SINGLE RULE. A
 * 2px border per row is 644px of chrome across this page; the row rule is what
 * makes it read as the table he asked for, and it is the same token §28d's own
 * grid uses between its rows. */
.bwz-cheat--line {
  border: 0;
  border-radius: 0;
  border-block-start: var(--bwz-border) solid var(--bwz-rule);
}

.bwz-cheat--line:first-child { border-block-start: 0; }

/* ⚠️ `[open]` ON A LINE PAINTS AND NEVER MOVES, for §28c's reason: a line that
 * grew a border on open would shift every line under it by 2px under the hand
 * that pressed it. */
.bwz-cheat--line[open] { background: var(--bwz-surface-2); }

/* The face: one line box, two columns and two glyphs. `grid` rather than `flex`
 * so the descriptions line up down the list, which is the *"tabulate"* in his
 * own word -- a flex row sizes each title to its own content and the second
 * column is ragged. */
.bwz-cheat__face--line {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) minmax(0, 1.2fr) auto;
  align-items: baseline;
  gap: var(--bwz-space-2);
  padding: 0.2rem 0.4rem;
  border: 0;
  border-radius: 0;
}

/* ⚠️ [CRITICAL] `:not(--run)` AND NOT A BARE `background: transparent`, AND IT
 * IS MEASURED. §28 gives an execute row the ACTION accent -- the one colour on
 * this page that means *pressing this runs a command on the box* -- and this
 * section is LATER in the file, so a flat `background: transparent` here wins on
 * source order and strips it. [M] 2026-10-25, all three engines: the drill read
 * `run=['rgba(0, 0, 0, 0)']` against `copy=['rgb(244, 246, 251)']`, so
 * BlueWorkz's three execute rows were drawn as ordinary copy rows **and the only
 * three controls on this page that post anything looked like the 319 that do
 * not**. The exclusion is the fix; the accent is not re-declared here, because a
 * second declaration of it is a second place to edit. */
.bwz-cheat__face--line:not(.bwz-cheat__face--run) {
  background: transparent;
}

/* ⚠️ AT 48em THE SECOND COLUMN GOES UNDER THE FIRST RATHER THAN BEING CUT. A
 * description squeezed into 80px is not a description, and `overflow-wrap`
 * would make a one-line row four lines tall, which is the density this section
 * exists to protect. */
@media (max-width: 48em) {
  .bwz-cheat__face--line {
    grid-template-columns: auto minmax(0, 1fr) auto;
  }

  .bwz-cheat__line-note { grid-column: 2 / 3; }
}

/* The title. The step up in size a lone card's title has, and NO ceiling of any
 * kind -- 869fjnggu is the ticket that says a name must be fully visible, and a
 * list is the one shape where that costs nothing. */
.bwz-cheat__line-title {
  min-width: 0;
  font-size: var(--bwz-text-sm);
  font-weight: 600;
  overflow-wrap: anywhere;
}

/* The description, where one is DECLARED. Quiet, because the title is what is
 * being scanned; `--bwz-fg-muted` on `--bwz-surface-2` is core/design.py's own
 * documented pair. It is empty on most rows and draws nothing when it is. */
.bwz-cheat__line-note {
  min-width: 0;
  color: var(--bwz-fg-muted);
  overflow-wrap: anywhere;
}

/* ------------------------------------------------------------------ §28h
 * A POPUP IS NEVER CLIPPED AND THERE IS NEVER MORE THAN ONE ON SCREEN
 * (869fmdb62, Dan 2026-10-26, after reading the deployed 40.2.0 page)
 *
 * Two sentences, two faults, and they are different kinds of fault:
 *
 *   *"I still see overlapping popups / tooltips, ensure this never happens. If I
 *   click on a cheat to open the popup, close it automatically again when I
 *   hover and / or click over another cheat so that opens the other cheat's
 *   popup / hover card."*
 *
 *   *"cards are truncated by other sections. Never do this, always allow the
 *   popup / hover card to go over the other section if it needs the space."*
 *
 * ⚠️ 40.2.0 ANSWERED NEITHER AND BOTH ARE MEASURED HERE RATHER THAN ARGUED.
 * 40.2.0 fixed the overlap it could see — one pointer lighting two panels on one
 * control — and the exclusive accordion has always stopped two OPEN popups. What
 * neither reaches is an open popup and a DIFFERENT row's hover, because `:hover`
 * does not open anything for the accordion to close.
 *
 * ------------------------------------------------------------------ §28h.1
 * THE CLIP, WHICH IS `.bwz-card`'s OWN `overflow: hidden` AND NOT A Z-INDEX
 *
 * [M] 2026-10-26, chromium at 1680px, the LAST line of BlueWorkz's dropdown: the
 * opened popup is 345px tall and **328px of it is not drawn** — 95% of it. The
 * clipping ancestor is the estate card itself: an estate is
 * `<details class="bwz-card bwz-cheat-estate">`, §13's `.bwz-card` carries
 * `overflow: hidden` so a child's background is clipped to its radius, and an
 * `overflow` other than `visible` clips an absolutely positioned DESCENDANT
 * whatever its `z-index` is. The panel was not behind the next section; it was
 * cut off at the card's own bottom edge, which is exactly what a reader sees as
 * *"truncated by the other section"*.
 *
 * [CRITICAL] SO THE FIX IS ON THE CARD AND NOT ON THE PANEL. There is no value
 * of `z-index`, `position` or `inset` on `.bwz-cheat__panel` that escapes a
 * clipping ancestor — the only way out is for the ancestor to stop clipping.
 * And it is scoped to `.bwz-cheat-estate` rather than lifted off `.bwz-card`,
 * because `.bwz-card` is the chrome of every card on this dashboard and the
 * runner cards, the check cards and the product cards all rely on that clip to
 * keep a child's background inside the radius.
 *
 * ⚠️ WHAT THE CLIP WAS BUYING VERTICALLY IS NOT LOST ON THIS CARD. Nothing
 * inside a cheat estate card paints to the card's own top or bottom edge:
 * `.bwz-cheat-estate__summary` has `padding: var(--bwz-space-2) 0` and no
 * background, and `.bwz-cheat-estate__body` has none either. The radius has
 * nothing to clip there, which is why this card can give that axis up and the
 * others cannot.
 *
 * [CRITICAL] AND IT IS `clip visible` RATHER THAN `visible`, WHICH IS ONE AXIS
 * AND NOT TWO, BECAUSE THE FIRST DRAFT MEASURED THE COST OF TWO. [M] 2026-10-26,
 * all three engines, with `overflow: visible`: the document scrolled sideways by
 * **1004px at 380px, 572px at 820px and 292px at 1100px**, and the offender was
 * not a popup at all — it is `.bwz-cheat-shells` in an estate card's own
 * `<summary>`, 1367px of terminal buttons that `flex: 0 0 auto` refused to
 * shrink and that the card had been CLIPPING AWAY since the page was built. That
 * was a real fault and older than that round; what it was not was a thing to
 * uncover in passing while fixing something else, so it was reported on
 * `869fmdb62` rather than fixed there. **Dan answered Q1(a) on 2026-10-28 and
 * the row wraps now** (see `.bwz-cheat-shells`), so nothing in a title row passes
 * the card's edge any more -- and this override STAYS one axis, because a later
 * row that outgrows its card should be cut and measured, not scroll the page.
 *
 * ⚠️ `clip` AND NOT `hidden` ON THAT AXIS, AND THE DIFFERENCE IS THE WHOLE
 * TECHNIQUE. An `overflow-x: hidden` forces the other axis to compute as `auto`
 * — so `hidden visible` is `hidden auto`, the card becomes a scroll container
 * and the popup is clipped at the bottom again, which is the fault. `clip`
 * carries no such rule: with one axis `clip` and the other `visible`, both
 * compute as specified. [M] 2026-10-26, chromium / firefox / webkit.
 *
 * ⚠️ THE POPUP STILL FITS INSIDE THE CARD HORIZONTALLY, AND THAT IS WHY ONE AXIS
 * IS ENOUGH. The card's padding box runs to `.bwz-container--dash`'s own 16px
 * gutter, and `bwz-cheatpop.js`'s flip uses that same 16px as `MARGIN_PX` — so a
 * panel the flip has judged to fit the viewport fits the card. With that file
 * blocked a panel near the right edge is now clipped at the card's edge instead
 * of running off the screen, which is the better of the two.
 *
 * ⚠️ THE SELECTOR NAMES THE BOARD AS WELL AS THE CARD, and that is not
 * decoration: `.bwz-cheat-estate` already has a rule in §28 carrying its
 * `margin-block-end`, and two rules with one selector is the shape that makes a
 * guard read the wrong block and fail about a declaration that has not moved
 * (`_rule`'s own note in ops/test_cheatsheet.py). It also says plainly that this
 * is the cheat BOARD's exception and not every card's.
 * -------------------------------------------------------------------------- */
.bwz-cheat-board .bwz-cheat-estate { overflow: clip visible; }

/* ------------------------------------------------------------------ §28h.2
 * NEVER TWO PANELS: THE STYLESHEET HOLDS THE INVARIANT, THE SCRIPT RESTORES THE
 * AFFORDANCE
 *
 * [M] 2026-10-26: with one cheat open and the pointer moved onto a different
 * row, chromium reported `openPanels: 1, shownHovers: 1` — the two things in
 * Dan's screenshot. The accordion cannot help, because hovering opens nothing.
 *
 * [CRITICAL] THE SPLIT IS THE WHOLE RISK DECISION AND IT IS §25's AND
 * `bwz-nav.js`'s DIRECTION OF FAILURE. *"Ensure this never happens"* is an
 * invariant, so it is held by CSS, which cannot fail to load: while any cheat on
 * this board is open, NO hover panel is drawn anywhere on it. *"So that opens
 * the other cheat's popup / hover card"* is an affordance, so it is restored by
 * `core/static/core/js/bwz-cheatpop.js`, which closes the open popup when the
 * pointer reaches a different control — at which point this rule stops matching
 * and the hovered row's own panel appears, alone.
 *
 * ⚠️ WITH THAT FILE BLOCKED THE PAGE IS STRICTLY BETTER THAN TODAY'S AND NOT
 * WORSE: no overlap is possible, and the cost is that a hover is quiet until the
 * reader closes the popup they opened — which is one press, on the control they
 * just pressed. The failure direction never produces the thing he asked never to
 * happen.
 *
 * [CRITICAL] THE FULL ANCESTOR CHAIN IS A SPECIFICITY REQUIREMENT, EXACTLY AS IT
 * IS IN §28d. The reveal rules there score (0,5,0) and (0,2,0); a bare
 * `.bwz-cheat-board:has(.bwz-cheat[open]) .bwz-cheat-hover` scores (0,3,0) and
 * would LOSE to the first of them, drawing both panels and passing a drill that
 * only ever measured one panel at a time. So each selector below is the reveal
 * rule it answers, plus the board condition — say the same thing and then one
 * thing more. A reader adding a reveal selector to §28d has to add its twin
 * here, and `drills/cheatsheet_board_drill.py` is what checks it.
 *
 * ⚠️ `:has()` IS LOAD-BEARING HERE AND THAT IS ACCEPTED. An engine without it
 * draws the page 40.2.0 shipped, which is the fault being fixed rather than a
 * new one; every engine this project supports has had it since 2023.
 * -------------------------------------------------------------------------- */
.bwz-cheat-board:has(.bwz-cheat[open]) .bwz-cheat:not([open]) > .bwz-cheat__face:hover > .bwz-cheat-hover,
.bwz-cheat-board:has(.bwz-cheat[open]) .bwz-cheat:not([open]) > .bwz-cheat__face:focus-visible > .bwz-cheat-hover,
.bwz-cheat-board:has(.bwz-cheat[open]) .bwz-cheat-shell:hover > .bwz-cheat-hover,
.bwz-cheat-board:has(.bwz-cheat[open]) .bwz-cheat-shell:focus-visible > .bwz-cheat-hover {
  display: none;
}

/* ---------------------------------------------------------------------------
 * 29. The email-rules BOARD (869fh1w9f, Dan 2026-10-19)
 *
 * His words: *"Currently I just Scroll down and I see a whole load of text and
 * it takes me a very long time to get any useful information. One single rule
 * barely fits on a full height screen. Make the display much more user friendly
 * so I can easily see what rules I have in place across the whole estate and
 * for individual [estates]. Use expandable and collapsible sections, but also
 * careful layout that makes much better use of the available screen space. Use
 * some graphical elements to make it easier to see what is going on."*
 *
 * [M] 2026-10-19 on the live data (two rules, one live), measured the same way
 * either side: **626 words of text on screen on arrival** before this section
 * existed and **312 after** -- and, which matters more, the page grew by **127
 * words per rule** then and by **14 now**, because every rule drew its own
 * expanded form. So the "whole load of text" was arithmetic rather than an
 * impression. The HTML got BIGGER (60,793 bytes to 72,243): a closed <details>
 * still carries its children, so what fell is what a reader has to read rather
 * than what the server sends, and saying it the other way round would be a
 * claim wider than the measurement.
 *
 * [CRITICAL] NOT ONE GRADED COLOUR IS INTRODUCED HERE. The one place this
 * section names a status colour is the tally bar, and it names the pair
 * `28.0.0` already chose for this page's own `live` / `draft` badges -- so the
 * bar says what the badge beside it says, where a third vocabulary would be one
 * fact drawn two ways. This page carries no check tile at all. **A run must not
 * read that as licence to put a graded colour on a page that does grade checks.**
 *
 * [CRITICAL] NO SCRIPT, AND NOTHING HERE DEPENDS ON ONE. Every collapse is a
 * `<details>`; nothing carries `data-bwz-remember`, deliberately, because a
 * remembered collapse on a board of rules hides an estate somebody forgot they
 * closed. A scope section's `open` is COMPUTED (open where it has rules) and a
 * rule's is never set at all -- `ops/rollup.py:runners_and_board_open`'s shape,
 * and `runner.md`'s rule that a card never opens itself.
 *
 * [CRITICAL] NO CONTROL SITS INSIDE A `<summary>`. A click's activation target
 * is the first element in the event path with an activation behaviour, so a
 * button or a form there is toggled by every click on it -- measured in all
 * three engines (`runner.md` §12.12.20). Every summary on this board is a glyph,
 * a code span, badges and text.
 * ------------------------------------------------------------------------- */

/* ---- the BOARD is wide and the PROSE is not ------------------------------ */

/* [CRITICAL] Dan's Q2(c) of 2026-10-19 on `869fh1w9f`, the ⭐, given in prose in
 * a laptop chat session: *"The board (index strip, estate sections, rule rows)
 * takes the wide container, and every block of explanation stays at the reading
 * measure."*
 *
 * The page container is `.bwz-container--wide` (112rem). That is what puts
 * several chips across the index strip and what lets a criterion's three
 * controls share one line -- and the cost of it, before this rule, was that
 * every PARAGRAPH on the page was 112rem wide as well. A line of 13px text
 * 1,792px long is the readability problem `--bwz-measure` exists for, and it was
 * on the one page in this project whose whole job is to explain itself.
 *
 * ⚠️ **IT IS DECLARED FOR THIS PAGE AND IS NOT A RULE ABOUT THE DASHBOARD**, which
 * is Dan's own wording ("declared with its reason, not made a rule").
 * `.bwz-callout`, `.bwz-dash-meta` and `.bwz-help` are shared with every other
 * dashboard page and with the public site, so capping them where they are
 * declared would be restyling a shared component to fix one page -- which
 * `CLAUDE.md` §6 refuses outright. The scope is `.bwz-er-board`, this page's own
 * container class, and `.bwz-dash-grid--aligned .bwz-info` in §17b is the same
 * shape one section along.
 *
 * ⚠️ **`.bwz-er-form` IS NOT CAPPED AND MUST NOT BE.** `max-width: none` on it is
 * what this section already measures as *"the whole of why a rule now fits a
 * screen"*. What is capped is the PROSE INSIDE the form -- the field hints and
 * the criteria heading -- so the two coexist: a hint wraps at the measure and
 * the control row beneath it still takes the width it has.
 *
 * ⚠️ **`.bwz-info__panel` ALREADY CAPS ITSELF AT `--bwz-measure` IN §22**, so the
 * three help notes needed nothing here and are deliberately NOT re-declared:
 * one fact in two places is one fact that goes stale, and the one nobody edited
 * is the one that is believed.
 *
 * ⚠️ **The CALLOUT is capped rather than its `__body`.** A tinted panel spanning
 * 1,792px with 672px of text inside it is the same fault wearing a border. */
.bwz-er-board .bwz-callout,
.bwz-er-board .bwz-dash-meta,
.bwz-er-board .bwz-help { max-width: var(--bwz-measure); }

/* ---- the index strip: one chip per scope, several across ----------------- */

/* auto-fit with a min() floor, so 320px falls out of it with no breakpoint --
 * .bwz-dash-grid's own reason, and the reason there is no media query in this
 * whole section. */
.bwz-er-index {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr));
  gap: var(--bwz-space-3);
}

/* 869fn18ba: the card is a <div> and the anchor is inside it, because the pill
 * below carries a <details> and a disclosure nested in an anchor is interactive
 * content inside interactive content. `position: relative` is what lets
 * .bwz-er-chip__jump::after stretch over the card, so the WHOLE card is still
 * one click to its scope and nothing about the affordance changed. */
.bwz-er-chip {
  position: relative;
  display: block;
  padding: var(--bwz-space-3);
  border: var(--bwz-border) solid var(--bwz-rule);
  border-radius: var(--bwz-radius-md);
  background: var(--bwz-surface-2);
  color: inherit;
  text-decoration: none;
}
/* :focus-within rather than :focus-visible, because the focusable element is
 * now a child. A card whose link has focus must still show it. */
.bwz-er-chip:hover,
.bwz-er-chip:focus-within { border-color: var(--bwz-rule-strong); }

.bwz-er-chip__jump {
  color: inherit;
  text-decoration: none;
}
/* The stretched hit area. It sits UNDER the pill (z-index 0 against the pill's
 * 1), so the one control on the card that is not the jump link still gets its
 * own press. */
.bwz-er-chip__jump::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: var(--bwz-radius-md);
}

/* The count and the pill share one line. The count is --bwz-text-xl and the
 * rest of its line was empty, so the pill costs NO height -- Dan's "don't make
 * the cards any bigger than they are already". `align-items: baseline` would
 * drag the pill down against a 1.1 line-height number, so it is centred. */
.bwz-er-chip__line {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--bwz-space-2);
}

.bwz-er-chip__label {
  display: block;
  font-weight: var(--bwz-weight-semibold);
  font-size: var(--bwz-text-sm);
}

/* The big number. tabular-nums so a column of chips lines its figures up. */
.bwz-er-chip__count {
  display: block;
  margin-block: var(--bwz-space-1);
  font-size: var(--bwz-text-xl);
  font-weight: var(--bwz-weight-semibold);
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}

.bwz-er-chip__words {
  display: block;
  font-size: var(--bwz-text-xs);
  color: var(--bwz-fg-muted);
}

/* ---- the mailbox pill (869fn18ba) ---------------------------------------
 *
 * It borrows .bwz-badge's SHAPE rather than its class, because the pill wraps
 * onto a second line when its disclosure is open and .bwz-badge is
 * `white-space: nowrap` for every other caller on the site. Restyling the
 * shared badge to suit one card is what CLAUDE.md §6 calls a monitoring
 * defect; declaring the one that differs here is not.
 *
 * [CRITICAL] EVERY COLOUR BELOW IS A ROLE TOKEN FROM §2 AND NOT ONE HEX, so
 * core/tests.py's palette and WCAG sweeps grade these exactly as they grade the
 * badge pairs they are taken from. The default is the PLAIN pair -- the neutral
 * surface and muted ink -- which is what "no mailbox here" and "no mailbox of
 * its own" draw, because grey outranks green and neither is a grade.
 *
 * z-index 1 against .bwz-er-chip__jump::after: this is the one thing on the
 * card that is not the jump link, so it has to be above the stretched anchor
 * to be pressable at all. */
.bwz-er-pill {
  position: relative;
  z-index: 1;
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  /* Column gap only. A ROW gap would be 4px of air between the two lines the
   * face wraps onto at 320px, which is 4px the count's line box has not got --
   * [M] it was the difference between +2px and parity on every card. */
  column-gap: var(--bwz-space-1);
  row-gap: 0;
  padding: 0.15rem var(--bwz-space-2);
  border-radius: var(--bwz-radius-md);
  background: var(--bwz-surface-3);
  color: var(--bwz-fg-muted);
  font-size: var(--bwz-text-xs);
  font-weight: var(--bwz-weight-semibold);
  letter-spacing: 0.02em;
  /* [CRITICAL] THIS LINE-HEIGHT IS WHAT KEEPS DAN'S "don't make the cards any
   * bigger than they are already" TRUE AT EVERY WIDTH, and it is the reason the
   * words on the pill could stay honest instead of being clipped to fit.
   * [M] 2026-10-29 in Chromium: the count's line box is 34-39px, and at the
   * 13rem column floor the pill's face wraps to two lines. At the inherited 1.6
   * that is 47px and every card grew 9px; at 1.15 it is 35px and fits inside
   * the line box the count already occupied. 29.12.0's no-truncation rule for
   * this board is what made shrinking the TEXT the wrong lever. */
  line-height: var(--bwz-leading-tight);
}
.bwz-er-pill--ok { background: var(--bwz-ok-surface); color: var(--bwz-ok); }
.bwz-er-pill--warn { background: var(--bwz-warn-surface); color: var(--bwz-warn); }
.bwz-er-pill--danger { background: var(--bwz-danger-surface); color: var(--bwz-danger); }

.bwz-er-pill__word { white-space: nowrap; }

/* The single clickable word. It is underlined so it reads as a control without
 * a second colour -- the pill already carries the only colour on it, and that
 * colour is the reading rather than the affordance. */
.bwz-er-pill__more > summary {
  cursor: pointer;
  text-decoration: underline;
  white-space: nowrap;
}
.bwz-er-pill__more > summary:focus-visible { color: var(--bwz-fg); }

/* IN FLOW and never a popover -- runner.md's rule, because an overlaying panel
 * either blocks the control it explains or passes the click onto what is under
 * it. `flex-basis: 100%` drops the list onto its own line inside the pill, so
 * the pill grows downward on a press and the card is untouched until then. */
.bwz-er-pill__list {
  flex-basis: 100%;
  margin: var(--bwz-space-1) 0 0;
  padding: 0;
  list-style: none;
}

.bwz-er-pill__item {
  display: block;
  font-weight: var(--bwz-weight-normal);
  letter-spacing: normal;
  /* No nowrap and no truncation: 29.12.0's rule for this board is that a value
   * takes a second line rather than losing a character, which is also what
   * keeps the 320px no-overflow property. */
  overflow-wrap: anywhere;
}

.bwz-er-pill__where {
  display: block;
  color: var(--bwz-fg-muted);
}

/* ---- a scope section ----------------------------------------------------- */

.bwz-er-scope { display: block; }
.bwz-er-scope > summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bwz-space-3);
  padding: var(--bwz-space-2) 0;
  cursor: pointer;
  list-style: none;
}
.bwz-er-scope > summary::marker,
.bwz-er-scope > summary::-webkit-details-marker { display: none; }

.bwz-er-scope__label {
  font-size: var(--bwz-text-lg);
  font-weight: var(--bwz-weight-semibold);
}

/* The bar in a summary needs a width of its own: a flex item with no basis
 * would be as wide as nothing. Capped so it never grows to the width of a
 * 3440px screen, which would read as a progress bar rather than a tally. */
.bwz-er-bar { width: 100%; max-width: 9rem; }

/* ---- one rule ------------------------------------------------------------ */

/* [CRITICAL] THE BORDER IS DECLARED ON THE CLOSED STATE AND ONLY ITS COLOUR
 * MOVES. §28c's measured rule: a border that appeared on open would make the
 * pressed row 2px taller than its neighbours and shift the page under the
 * reader's hand. */
.bwz-er-rule {
  display: block;
  border: 2px solid transparent;
  border-radius: var(--bwz-radius-md);
  padding: 0 var(--bwz-space-3);
}
.bwz-er-rule[open] {
  border-color: var(--bwz-rule-strong);
  background: var(--bwz-surface-2);
  padding-block-end: var(--bwz-space-4);
}

/* [CRITICAL] ONE LINE WHERE THE WIDTH ALLOWS, WHICH IS A WRAP AND NEVER A
 * TRUNCATION (869fmzc1g round 2, Dan 2026-10-29: "I collapsed row of one rule,
 * put it all on one row (not the current 2) where screen width allows ... so
 * that the rows themselves take up less vertical space").
 *
 * The summary is the flex row, so the identity (glyph, name, action) and the
 * tests are items in ONE line rather than two blocks stacked by a margin. His
 * own example is exactly the order they are in: "🗄 Archive DMARC reports
 * Archive  Subject contains Report Domain: and From contains dmarc".
 *
 * ⚠️ **`flex-wrap: wrap` IS THE WHOLE OF "where screen width allows", AND AN
 * ELLIPSIS WOULD HAVE BEEN THE WRONG ANSWER.** 29.12.0 measured that cutting a
 * title makes a board of rows useless, and half a rule's tests read as a
 * different rule -- so a rule with more tests than fit takes a second line
 * rather than losing a word. That is also what keeps the 320px no-overflow
 * property: nothing here is `nowrap`. */
.bwz-er-rule > summary {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  column-gap: var(--bwz-space-3);
  row-gap: var(--bwz-space-1);
  padding-block: var(--bwz-space-2);
  cursor: pointer;
  list-style: none;
}
.bwz-er-rule > summary::marker,
.bwz-er-rule > summary::-webkit-details-marker { display: none; }

/* The identity line: everything that says WHICH rule this is. */
.bwz-er-rule__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bwz-space-2);
  /* So it can give way to the tests beside it rather than pushing them off. */
  min-width: 0;
}

/* The glyph. A scanning aid and never the only statement -- the verb's own word
 * is drawn beside it, which is why no contrast pair is owed here. */
.bwz-er-glyph {
  flex: none;
  font-size: 1.15em;
  line-height: 1;
}

.bwz-er-rule__id { font-family: var(--bwz-font-mono); font-weight: var(--bwz-weight-semibold); }
.bwz-er-rule__verb { font-size: var(--bwz-text-sm); color: var(--bwz-fg-muted); }

/* Dan's own summary, which is the sentence he wrote. NOT truncated and not
 * clamped: 29.12.0 measured that cutting a title makes a board of cards
 * useless, and half a rule's summary reads as a different rule. */
.bwz-er-rule__summary {
  display: block;
  margin-block-start: var(--bwz-space-1);
  font-size: var(--bwz-text-sm);
}

/* The tests, as chips, on the closed row. */
/* ⚠️ NO `margin-block-start` SINCE 2026-10-29: it was what put the tests on a
 * line of their own, which is the second line Dan asked to be rid of. The
 * summary's own `row-gap` spaces them when they do wrap. */
.bwz-er-tests {
  display: flex;
  flex-wrap: wrap;
  gap: var(--bwz-space-1);
  min-width: 0;
}
.bwz-er-test {
  padding: 0.1rem var(--bwz-space-2);
  border: var(--bwz-border) solid var(--bwz-rule);
  border-radius: var(--bwz-radius-sm);
  background: var(--bwz-surface);
  font-family: var(--bwz-font-mono);
  font-size: var(--bwz-text-xs);
  color: var(--bwz-fg-muted);
  /* overflow-wrap rather than nowrap: a 120-character criterion value is
   * permitted, and anywhere is what a value with no spaces in it needs --
   * 26.3.0's measured answer on .bwz-code, same hazard. */
  overflow-wrap: anywhere;
}

/* ---- the edit form, on one line per thing -------------------------------- */

/* [CRITICAL] `max-width: none` is the whole of why a rule now fits a screen.
 * .bwz-form caps at --bwz-measure, which is right for prose and is what forced
 * each criterion's three controls onto three lines of their own. */
.bwz-er-form { max-width: none; }
.bwz-er-form > * + * { margin-top: var(--bwz-space-4); }

.bwz-er-line {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--bwz-space-3);
}

/* flex rather than a grid of named columns, so the value box gets the room and
 * the whole row collapses to one column at 320px with no breakpoint. */
.bwz-er-line > .bwz-field { flex: 1 1 9rem; min-width: 0; }
.bwz-er-line > .bwz-field--value { flex: 2 1 16rem; }

/* The criterion rows, as a numbered list of lines. */
.bwz-er-criteria { display: block; }
.bwz-er-criteria > * + * { margin-top: var(--bwz-space-2); }

.bwz-er-criterion {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bwz-space-2);
}
.bwz-er-criterion > .bwz-er-n {
  flex: none;
  min-width: 1.5rem;
  font-size: var(--bwz-text-xs);
  font-variant-numeric: tabular-nums;
  color: var(--bwz-fg-muted);
}
.bwz-er-criterion > select { flex: 1 1 9rem; min-width: 0; }
.bwz-er-criterion > input { flex: 2 1 14rem; min-width: 0; }

/* THE RED X, AT THE FAR RIGHT OF EVERY TEST ROW (869fmzc1g round 2, Dan
 * 2026-10-29: "put a red X button at the far right of each row to remove
 * conditions (including empty condition rows)").
 *
 * It is a real submit button wearing one glyph, so with the stylesheet blocked
 * it is a button reading "×" that still posts. The colour is `--bwz-danger`,
 * which is the same role token `.bwz-error` and `.bwz-label__required` already
 * use as a foreground on this surface -- NO NEW HEX AND NO NEW PAIR, which is
 * what CLAUDE.md §6 asks of a component. The ⋯ menu's Delete is still the only
 * red press that reaches the database; this one only shortens a form. */
.bwz-er-x {
  flex: none;
  padding-inline: var(--bwz-space-2);
  color: var(--bwz-danger);
  font-size: var(--bwz-text-md);
  line-height: 1;
}
.bwz-er-x:hover,
.bwz-er-x:focus-visible {
  border-color: var(--bwz-danger);
  color: var(--bwz-danger);
}

/* The condition groups (869fmzc1f, Dan 2026-10-28). A real <fieldset> per
 * bracket, so the bracket is a structure a screen reader announces rather than
 * an indent a sighted reader infers -- and the browser's own default border is
 * replaced with a token one rather than removed, because the WHOLE POINT of this
 * control is seeing where one bracket ends and the next begins.
 *
 * Deliberately minimal: ticket 3 of this round (869fmzc1g) is the page's
 * redesign, and polishing this form twice would be the second polish thrown
 * away. No colour is introduced -- the border and the muted legend are existing
 * role tokens, so nothing here can be read as a health grading on a page that
 * draws badges for live and draft. */
.bwz-er-groups > * + * { margin-top: var(--bwz-space-3); }
.bwz-er-group {
  border: var(--bwz-border) solid var(--bwz-rule);
  border-radius: var(--bwz-radius-sm);
  /* space-2 and not space-3 since 869fmzc1g: [M] 2026-10-29, 16px of padding
   * each side of every bracket was 32px per group of a panel Dan had called too
   * big, on a box whose only job is to hold three dropdowns apart. */
  padding: var(--bwz-space-2);
  margin: 0;
  min-width: 0;
}
.bwz-er-group > legend {
  padding: 0 var(--bwz-space-2);
  color: var(--bwz-fg-muted);
}
.bwz-er-group > * + * { margin-top: var(--bwz-space-2); }

/* ---- THE COMPACT ROW (869fmzc1g, Dan 2026-10-28) ------------------------- */

/* Dan, dictated: *"currently editing one rule takes up about half of the screen
 * and there's actually only 15 words or so that are relevant, the whole thing
 * needs to be much more compact and user friendly, the sort of thing I might see
 * on a professional SaaS platform offering something like this."*
 *
 * [CRITICAL] THE ROW IS A GRID OF THREE SIBLINGS AND NOT A <summary> WITH
 * CONTROLS IN IT. A click's activation target is the first element in the event
 * path with an activation behaviour, so a checkbox inside a <summary> is toggled
 * by every click anywhere on the row -- measured in all three engines,
 * runner.md §12.12.20. So the switch, the <details> and the menu are
 * three siblings, and this grid is the whole of what makes them read as one line.
 *
 * ⚠️ **The panel opens inside COLUMN 2 and is therefore narrower than the row by
 * the switch and the menu.** That is the cost of the no-control-in-a-summary
 * rule and it is about 5rem; the alternative is a panel that spans the grid,
 * which cannot be done without taking the <details> out of the line.
 *
 * ⚠️ **No new colour and no new hex.** Every value here is an existing role
 * token. The switch is drawn from `--bwz-accent` when on and `--bwz-rule-strong`
 * when off, which is the SAME pair the page's live/draft badges already use, so
 * one fact is not drawn in two vocabularies. */
.bwz-er-rows { display: block; }
.bwz-er-rows > * + * { margin-top: var(--bwz-space-1); }

.bwz-er-row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: start;
  column-gap: var(--bwz-space-2);
  border-radius: var(--bwz-radius-md);
}
.bwz-er-row--problem {
  outline: 2px solid var(--bwz-danger);
  outline-offset: 2px;
}

/* At a phone width the three columns would leave the name about 15 characters,
 * so the row becomes two lines: the switch and the menu on the first, the rule
 * on the second, full width. One breakpoint, and the only one on this page. */
@media (max-width: 30em) {
  .bwz-er-row { grid-template-columns: auto minmax(0, 1fr); }
  .bwz-er-row > .bwz-er-rule { grid-column: 1 / -1; }
  .bwz-er-row > .bwz-er-more { justify-self: end; }
}

/* ---- the ON/OFF switch, which replaces Publish / Unpublish --------------- */

/* [CRITICAL] IT IS A REAL CHECKBOX AND THE PILL IS DRAWN FROM IT. With the
 * stylesheet blocked this is a labelled checkbox that posts, which is the same
 * property `.bwz-nav-toggle` buys by being a real button. The WORD beside it is
 * this dashboard's honesty rule: nothing is published only as a picture. */
.bwz-er-switch {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding-block: var(--bwz-space-2);
  cursor: pointer;
  user-select: none;
}
.bwz-er-switch__box {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.bwz-er-switch__track {
  display: block;
  width: 2.25rem;
  height: 1.25rem;
  border: var(--bwz-border) solid var(--bwz-rule-strong);
  border-radius: 1rem;
  background: var(--bwz-surface-2);
  transition: background-color var(--bwz-duration) var(--bwz-ease),
    border-color var(--bwz-duration) var(--bwz-ease);
}
.bwz-er-switch__knob {
  display: block;
  width: 0.875rem;
  height: 0.875rem;
  margin: 2px;
  border-radius: 50%;
  background: var(--bwz-fg-muted);
  transition: transform var(--bwz-duration) var(--bwz-ease),
    background-color var(--bwz-duration) var(--bwz-ease);
}
.bwz-er-switch__box:checked + .bwz-er-switch__track {
  border-color: var(--bwz-accent);
  background: var(--bwz-accent-surface);
}
.bwz-er-switch__box:checked + .bwz-er-switch__track > .bwz-er-switch__knob {
  background: var(--bwz-accent);
  transform: translateX(1rem);
}
.bwz-er-switch__box:focus-visible + .bwz-er-switch__track {
  outline: 2px solid var(--bwz-accent);
  outline-offset: 2px;
}
.bwz-er-switch__word {
  font-size: var(--bwz-text-xs);
  color: var(--bwz-fg-muted);
  font-variant-numeric: tabular-nums;
}

/* ---- the closed line ----------------------------------------------------- */

/* One line that wraps rather than one line that truncates: 29.12.0's measured
 * lesson is that a cut criterion value reads as a different value. */
.bwz-er-rule__name {
  font-weight: var(--bwz-weight-semibold);
  font-size: var(--bwz-text-sm);
  color: var(--bwz-fg);
}
.bwz-er-rule__does {
  font-size: var(--bwz-text-xs);
  color: var(--bwz-fg-muted);
  text-transform: none;
}
/* The refusal takes a line of its own even though its siblings share one: it is
 * the one thing on a closed row that is not an identity, and a reader scanning
 * for the row that was refused must not have to read along a line to find it. */
.bwz-er-rule__problem {
  flex-basis: 100%;
  font-size: var(--bwz-text-xs);
  color: var(--bwz-danger);
}

/* ---- the "unsaved" marker, which only a script can honestly draw ---------- */

/* [CRITICAL] HIDDEN UNTIL bwz-email-rules.js HAS RUN, AND THAT IS THE OPPOSITE
 * OF `bwz-nav.js`'s DIRECTION ON PURPOSE. The nav's rule is that every
 * declaration which HIDES something is scoped behind the script's own attribute,
 * so a blocked script leaves the page showing everything. Here the span is a
 * CLAIM about one row -- "you have edited this and not saved it" -- and only the
 * script can establish it: drawn with the script blocked it would be a lie on
 * every row on arrival. So the SHOWING is what is scoped, and with JavaScript
 * off this draws nothing and the row is exactly what it was before 2026-10-29.
 * Dan's tick of Q1(a) on 869fmzc1g. */
.bwz-er-dirty { display: none; }
:root[data-bwz-er="js"] .bwz-er-row--dirty .bwz-er-dirty { display: inline-flex; }

/* ---- the editing panel --------------------------------------------------- */

.bwz-er-panel { padding-block-start: var(--bwz-space-2); }

/* THE TWO SUMMARISE SETTINGS, SHOWN ONLY FOR A SUMMARISE RULE.
 *
 * [CRITICAL] IT FAILS OPEN, AND THAT IS WHY THE HIDING RULE IS ALSO WRITTEN
 * WITH `:has()`. An engine that does not support the selector drops BOTH rules,
 * leaving the two fields visible on every rule -- which is exactly what shipped
 * in 42.0.0 and is harmless, because both settings are ignored unless the rule
 * asks for a summary. A plain `display: none` default would have hidden them
 * permanently on such an engine: a field Dan cannot reach, which is worse than
 * one he sees too often. */
.bwz-er-panel:has(select[name$="-verb"]) .bwz-er-sum { display: none; }
.bwz-er-panel:has(select[name$="-verb"] option[value="summarise"]:checked) .bwz-er-sum {
  display: flex;
}

/* ⚠️ "When" IS INSIDE `.bwz-er-top` SINCE 2026-10-29, so it shares a line with
 * `+ group` -- Dan's "Move '+ group' up to the same row as the 'When' heading".
 * `.bwz-label` is `display: block`, which as a flex item it simply is not, so
 * nothing is owed here but the weight it already has. */
.bwz-er-when__word { flex: none; }

.bwz-er-top {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bwz-space-2);
}
.bwz-er-top > select { flex: 0 1 7rem; min-width: 0; }

/* ---- the ⋯ menu ---------------------------------------------------------- */

/* Absolutely positioned, so opening it does not push the rows below it down the
 * page -- which on a board of twenty rules is the reader's own place moving
 * under their hand. §28c's rule about a border on open, one control along.
 * It is still a native <details> and still no script. */
.bwz-er-more { position: relative; }
.bwz-er-more > summary {
  display: block;
  padding: var(--bwz-space-2);
  cursor: pointer;
  list-style: none;
  color: var(--bwz-fg-muted);
  border-radius: var(--bwz-radius-sm);
}
.bwz-er-more > summary::marker,
.bwz-er-more > summary::-webkit-details-marker { display: none; }
.bwz-er-more > summary:hover,
.bwz-er-more[open] > summary { color: var(--bwz-fg); }
.bwz-er-more__mark {
  font-size: var(--bwz-text-lg);
  line-height: 1;
}
.bwz-er-more__panel {
  position: absolute;
  inset-inline-end: 0;
  z-index: 3;
  min-width: 11rem;
  padding: var(--bwz-space-3);
  border: var(--bwz-border) solid var(--bwz-rule-strong);
  border-radius: var(--bwz-radius-md);
  background: var(--bwz-surface);
  box-shadow: var(--bwz-shadow-md);
}
.bwz-er-more__panel > .bwz-btn { display: block; width: 100%; }
.bwz-er-more--empty { display: block; width: 2.5rem; }

/* ---- the Save all bar ---------------------------------------------------- */

.bwz-er-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bwz-space-3);
}
/* The one-line note beside the buttons keeps the reading measure the rest of
 * the board's prose has; `.bwz-er-board .bwz-help` already caps it. */
.bwz-er-actions > .bwz-help { flex: 1 1 18rem; }

.bwz-er-new > * + * { margin-top: var(--bwz-space-1); }

/* ===========================================================================
 * 30. The email tidy-up page's message box (869fkcqtt, Dan 2026-10-20)
 *
 * Dan, on the line of Zoho message ids this replaces: *"this is essentially
 * meaningless and is worse than will not be read, it just gets in the way.
 * Instead I should see a list of any affected messages in a small pop out but
 * well styled box, with from and subject of each message displayed not a
 * meaningless long number."*
 *
 * [CRITICAL] THIS SECTION IS SMALL BECAUSE THE PAGE REUSES §22 AND §25 RATHER
 * THAN RESTATING THEM. Every `i` on that page is §22's `.bwz-info` -- a native
 * <details>, in flow, never a popover, with the circled "i" of
 * `.bwz-info__mark` -- which is the component Dan's 2026-09-11 instruction
 * ("move all the information in to clickable tooltips") already created. The
 * "Which emails?" handle is §22's own `.bwz-info--btn` modifier. The
 * confirmation is §25's `.bwz-snapshot` chrome. So all that is genuinely new is
 * the SHAPE OF ONE LIST, and that is all this section holds.
 *
 * [CRITICAL] IT IS A PANEL IN FLOW AND NOT AN OVERLAY, which is §22's rule and
 * §22's measurement rather than a choice made here: an absolutely positioned
 * panel covers the control it explains, and the control under this one is the
 * button that moves real email. §28d's hover exception is page-specific and
 * does not reach this page.
 *
 * NO HEX IS NAMED HERE. Every colour is a role token from §2, so both themes
 * follow, and no graded colour appears at all -- on this dashboard ok/warn/
 * danger mean *this check is failing*, and a list of email is not a check.
 * ------------------------------------------------------------------------- */

/* The row a count, a "Which emails?" handle and an `i` share. A flex row for
 * §22's own reason: an OPEN note is `flex: 1 0 100%`, so it takes the line
 * rather than squeezing the count, and the whole thing wraps at 320px with no
 * breakpoint. */
.bwz-mail-line {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--bwz-space-1) var(--bwz-space-2);
}

.bwz-mail-count {
  font-size: var(--bwz-text-sm);
  color: var(--bwz-fg-muted);
  font-variant-numeric: tabular-nums;
}

/* THE ONE LINE A MODEL WROTE ABOUT A BATCH (869fmzc1d, 2026-10-28). It is the
 * first thing in its card and it is the thing Dan reads before he decides
 * anything, so it is bigger than the body text and it is NOT muted -- the
 * count, the dates and the notes around it all are, which is what makes this
 * the thing the eye lands on.
 *
 * ⚠️ NOT ONE HEX AND NO GRADED COLOUR. On this dashboard ok/warn/danger mean
 * THIS CHECK IS FAILING, and a summary is neither passing nor failing; it is
 * information. So it borrows `--bwz-fg` and the accent only as a rule on the
 * inline edge, which is §22's own clickable-note device used for emphasis
 * rather than for state. */
.bwz-mail-summary {
  margin: 0;
  font-size: var(--bwz-text-lg);
  line-height: var(--bwz-leading-tight);
  color: var(--bwz-fg);
  border-inline-start: 3px solid var(--bwz-accent);
  padding-inline-start: var(--bwz-space-2);
}

/* The panel holding the list is tighter than §22's prose panel: a list of email
 * is scanned rather than read, and the measure that suits a paragraph wastes the
 * width a subject wants. `max-width: none` is deliberate and is the same thing
 * §29 does for `.bwz-er-form`. */
.bwz-info__panel--list {
  max-width: none;
  padding: var(--bwz-space-2);
}

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

/* Each message is ONE LINE: who it is from, who it went to, what it is about,
 * when it arrived. A grid rather than a table, because a table forces a
 * horizontal scrollbar at 320px and CLAUDE.md §6 names 320px as a hard target:
 * below 32em the cells stack and the date keeps its own row end.
 *
 * ⚠️ [CRITICAL] EVERY COLUMN BUT THE SUBJECT IS A FIXED WIDTH SINCE 43.6.0
 * (869fn1zyk, Dan 2026-10-29: "add headers to the table"), AND THAT IS WHAT
 * MAKES THE HEADING ROW POSSIBLE AT ALL. Each row is its own grid, so two `auto`
 * columns -- which is what the date and the Exclude button were -- resolve to a
 * different width in every row: "1 Oct" and "12 Oct" do not line up, and a
 * heading row over columns that move is worse than no heading row. With four
 * fixed tracks and one `1fr` the column edges are identical in every row and in
 * the heading, by construction rather than by two sets of numbers agreeing.
 *
 * ⚠️ [CRITICAL] `align-items: center` AND NOT `baseline`, WHICH IS DAN'S THIRD
 * CHANGE: "make the Exclude button line up with the date, currently it is
 * slightly below it". A button is a 36px box whose text sits on its own
 * baseline, so baseline alignment of a text cell against it is what put the two
 * a few pixels apart. Centre alignment gives every cell in a row one vertical
 * centre line, which is the ticket's own wording. */
.bwz-maillist__row {
  display: grid;
  grid-template-columns: minmax(0, 11rem) minmax(0, 11rem) minmax(0, 1fr) 3.5rem 5rem;
  gap: 0 var(--bwz-space-3);
  align-items: center;
  /* ⚠️ HALF-HEIGHT, which is Dan's fourth change: "Make the rows less tall,
   * ideally about half their current height". [M] 2026-10-29 in Chromium at
   * 1280px: 53px before, and the 53 was `--bwz-space-2` top and bottom (16px)
   * plus `.bwz-btn--sm`'s own `min-height: 36px`. Both had to move, so the
   * padding is `--bwz-space-1` here and the button's box is reset below. */
  padding: var(--bwz-space-1) var(--bwz-space-1);
  font-size: var(--bwz-text-sm);
}
.bwz-maillist__row + .bwz-maillist__row {
  border-top: var(--bwz-border) solid var(--bwz-rule);
}

/* THE HEADING ROW. Quiet, because it is a label and the email is the content;
 * `aria-hidden` in the markup keeps it out of a screen reader's way, since the
 * cells below carry their own `title`. */
.bwz-maillist__row--head {
  color: var(--bwz-fg-subtle);
  font-size: var(--bwz-text-xs);
  font-weight: var(--bwz-weight-semibold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  border-bottom: var(--bwz-border) solid var(--bwz-rule);
}
.bwz-maillist__row--head + .bwz-maillist__row { border-top: 0; }

/* ⚠️ [CRITICAL] A CELL IS ONE LINE AND IS CLIPPED HERE AS WELL AS TRUNCATED IN
 * THE TEMPLATE, AND THE COMMENT THIS REPLACES SAID NOT TO. It said a CSS
 * ellipsis on top of a truncated string "would cut the same line twice and the
 * reader would have no idea how much is missing", which was right when a row
 * could be two lines tall. It is not right now: a half-height row is a
 * ONE-LINE row by definition, so a cell that wrapped would silently undo the
 * change Dan asked for. The template's `truncatechars` is still the primary cut
 * and the full text is still in `title`; this is the backstop for a font wider
 * than the column, and it is the reason the three text columns have a width at
 * all. */
.bwz-maillist__cell,
.bwz-maillist__who,
.bwz-maillist__to,
.bwz-maillist__what,
.bwz-maillist__when {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* The sender carries the weight, because "who is this from" is the first
 * question somebody scanning this list asks. */
.bwz-maillist__who {
  font-weight: 600;
  color: var(--bwz-fg);
}

/* The recipient is the quietest of the three: it is usually Dan's own address
 * and is there to answer "was this to me or to a list", not to be read first. */
.bwz-maillist__to { color: var(--bwz-fg-subtle); }

.bwz-maillist__what { color: var(--bwz-fg-muted); }

.bwz-maillist__when {
  color: var(--bwz-fg-subtle);
  font-variant-numeric: tabular-nums;
}

/* ---------------------------------------------------------------------------
 * 30b. CLICK A ROW TO READ THE EMAIL (869fn1zyk, 43.6.0, Dan 2026-10-29:
 * "Allow me to click a row and see a new pop up with the e-mail content in it").
 *
 * [CRITICAL] THE FORM IS `display: contents` AND THE FOUR CELLS ARE SUBMIT
 * BUTTONS. A <button> ignores `display: contents` -- it is a form control and
 * the spec exempts it -- so one button wrapping the whole row would take all
 * four cells out of the grid and the columns would stop lining up. The form
 * carries it instead (the same trick `.bwz-mail-undo` above already uses), so
 * each cell stays a grid item and is its own submit control.
 *
 * [CRITICAL] NO NEW BUTTON COMPONENT, AND NOT ONE COLOUR IS WRITTEN HERE. A
 * cell must look exactly like the text it replaced, because a row of four
 * buttons that looked like buttons would be four controls competing with the
 * Accept. All these rules do is undo a <button>'s own appearance; the colour,
 * the weight and the size are the cell classes' own, above, and the focus ring
 * is §4's.
 * ------------------------------------------------------------------------- */
.bwz-maillist__open { margin: 0; display: contents; }
.bwz-maillist__cell {
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-size: inherit;
  font-weight: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
/* The whole row answers a hover, so it is obvious the row is the target rather
 * than the word under the pointer. `:focus-visible` is per cell, which is
 * right: the keyboard moves cell by cell and the ring must say where it is. */
.bwz-maillist__row:hover .bwz-maillist__cell { text-decoration: underline; }

/* ---------------------------------------------------------------------------
 * 30c. THE READER PAGE, /dashboard/email-proposals/read/ (869fn1zyk, 43.6.0).
 *
 * Three small things and no new colour: the email's own facts above the frame,
 * a taller frame than §4's default because an email is read rather than
 * glanced at, and the list of where its links really go.
 * ------------------------------------------------------------------------- */
.bwz-mailfacts {
  list-style: none;
  margin: 0;
  padding: 0;
  font-size: var(--bwz-text-sm);
}
.bwz-mailfacts > li { display: flex; gap: var(--bwz-space-2); }
.bwz-mailfacts > li + li { margin-top: var(--bwz-space-1); }
.bwz-mailfacts__key {
  flex: 0 0 5rem;
  color: var(--bwz-fg-subtle);
  font-weight: var(--bwz-weight-semibold);
}
/* ⚠️ `white-space: pre-line` is what makes the `title`'s one-per-line recipient
 * list readable here too, where there is room for all of them. */
.bwz-mailfacts__val {
  min-width: 0;
  color: var(--bwz-fg);
  overflow-wrap: anywhere;
  white-space: pre-line;
}

/* An email is read, not glanced at, so the frame is taller than §4's default. */
.bwz-embed--mail iframe { height: max(32rem, 76vh); }

/* [CRITICAL] A STRANGER'S URL AS TEXT AND NEVER AS A LINK. The whole point of
 * this list is that Dan can READ where a link goes before he follows it, so
 * `<code>` and `overflow-wrap` rather than an `<a>`. */
.bwz-maillinks {
  margin: 0;
  padding-left: var(--bwz-space-5);
  font-size: var(--bwz-text-sm);
}
.bwz-maillinks > li { overflow-wrap: anywhere; }
.bwz-maillinks > li + li { margin-top: var(--bwz-space-1); }

/* ---------------------------------------------------------------------------
 * 30a. ONE ROW'S `Exclude`, AND THE `Undo edits` BESIDE THE HANDLE
 * (869fkcqtt Q2, 36.1.0, Dan 2026-10-20: *"give each one a button to say
 * "exclude" and clicking it moves it immediately out of the list, also creates
 * "undo edits" link (not button) next to Which emails button"*).
 *
 * [CRITICAL] NO NEW BUTTON COMPONENT IS DEFINED HERE. `Exclude` wears §12's own
 * `.bwz-btn--ghost .bwz-btn--sm`, the quiet variant, because the loud one on
 * this page is the Accept and a row of eleven equally loud buttons would be
 * eleven things competing with the one that matters. All this rule does is let
 * the form sit in the grid without inheriting the list's baseline.
 * ------------------------------------------------------------------------- */
.bwz-maillist__act {
  margin: 0;
  justify-self: end;
  align-self: center;
}

/* ⚠️ [CRITICAL] THE BUTTON'S BOX IS RESET **ONLY INSIDE THIS LIST**, AND THAT
 * SCOPING IS THE WHOLE OF WHY THIS IS NOT A MONITORING DEFECT. `.bwz-btn--sm`'s
 * `min-height: 36px` is what made the row 53px tall ([M] 2026-10-29, Chromium at
 * 1280px), so halving the row means shrinking that box -- and editing §12's own
 * component would shrink every small button on the dashboard, which CLAUDE.md §6
 * calls out by name ("restyling a shared component outright is a monitoring
 * defect, not a design change"). So the selector is the list's own cell and
 * nothing else, and NOT ONE COLOUR, border or radius is touched.
 *
 * ⚠️ And it is undone below 32em, where the row stacks: 44px is "a real touch
 * target at 320px" in §12's own words, and a 20px target on a phone would be
 * trading Dan's laptop comfort for somebody's thumb. */
.bwz-maillist__act > .bwz-btn {
  min-height: 1.25rem;
  padding-block: 0;
}

/* [CRITICAL] A SUBMIT BUTTON WEARING §4's OWN `.bwz-link`, AND THE REASON IS A
 * SAFETY PROPERTY RATHER THAN A STYLE. Dan asked for a *"link (not button)"*,
 * and a real <a href> that changed something would be a write a prefetch, a
 * crawler or a link preview could make with nobody having clicked -- which is
 * why every control on that page is POST-only. So the MARKUP is a form and the
 * APPEARANCE is his: the element keeps a button's keyboard behaviour (Enter and
 * Space, focusable, announced as a button) and WEARS `.bwz-link` itself, so the
 * colour, the underline, the hover, the active state and the focus ring are
 * §4's and not a second copy of them. All that is written here is the reset
 * that stops a <button> looking like one -- NOT ONE COLOUR, which is why no hex
 * and no token appears below. */
.bwz-mail-undo { margin: 0; display: contents; }
.bwz-mail-undo > button {
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-size: var(--bwz-text-sm);
  cursor: pointer;
}

@media (max-width: 32em) {
  /* The row stacks: who takes its own line, then the recipient, then the
   * subject, then the date and the button share the last one. `Exclude` keeps
   * its own cell at 320px rather than being pushed off the end, which is
   * CLAUDE.md §6's hard target.
   *
   * ⚠️ THE HEADING ROW IS HIDDEN HERE and that is not a cut corner: a heading
   * row names columns, and there are no columns once the cells stack -- "From"
   * sitting above a stack of four different things would be a label that lies. */
  .bwz-maillist__row {
    grid-template-columns: minmax(0, 1fr) auto auto;
    gap: var(--bwz-space-1) var(--bwz-space-3);
    padding: var(--bwz-space-2) var(--bwz-space-1);
  }
  .bwz-maillist__row--head { display: none; }
  /* ⚠️ `who` and `to` take a line each and the SUBJECT DOES NOT, which is the
   * one place a column was added without a line being added with it: the
   * subject keeps sharing its line with the date and the button, exactly as it
   * did before 43.6.0. [M] 2026-10-29 in Chromium at 320px: giving the subject
   * its own line as well took the row from 85px to 129px -- taller, on the
   * ticket whose fourth change is "make the rows less tall". */
  .bwz-maillist__who { grid-column: 1 / -1; }
  .bwz-maillist__to { grid-column: 1 / -1; }
  /* A stacked cell may use the width it has: there is no column to clip to. */
  .bwz-maillist__cell,
  .bwz-maillist__who,
  .bwz-maillist__to,
  .bwz-maillist__what {
    white-space: normal;
    overflow: visible;
    overflow-wrap: anywhere;
  }
  /* §12's real touch target, back again. */
  .bwz-maillist__act > .bwz-btn {
    min-height: 36px;
    padding-block: var(--bwz-space-1);
  }
}
