/* ============================================================================================
   THE CASCADE, DECLARED. Item 38 session 6, backlog §38.5, gate answer Q6(a).

   Read this before adding a stylesheet, adding a <link>, or writing a rule whose job is to beat
   another rule.

   THE ORDER IS: vendor, then base, then site. Later wins. Nothing in the document is unlayered.

     vendor  Bootstrap 5.3.3, imported by Pages/Shared/_Layout.cshtml as
             `@import url(...) layer(vendor)`. It is the FIRST layer, so every rule in this file
             beats every rule in Bootstrap, whatever the two selectors weigh.
     base    `:root` and the element defaults below: the tokens, the box model, the two type
             families, the link colour. They lose to everything in `site` by layer rather than by
             weight.
     site    The rest of this file, whole and in the order it is written. Ties inside it resolve
             on specificity and then on source order EXACTLY as they did before this block existed.

   WHAT INTRODUCING IT MOVED ON THE SCREEN, BECAUSE IT WAS NOT NOTHING AND THE MEASUREMENT IS THE
   ONLY HONEST ANSWER. Fifty computed properties and a bounding box were recorded for every element
   on 27 routes, before and after -- 5,952 elements, and two runs of an unchanged tree differ in ONE
   pair, so the noise floor is known. 86 pairs moved and both causes are this file winning where
   Bootstrap had been winning on specificity:

 - EIGHT DISABLED BUTTONS. `.btn:disabled` here says `opacity:.55` and nothing else, on purpose:
       a switched-off button keeps its own colour. Bootstrap's `.btn:disabled` weighs 0,2,0 and
       states three colours, so it out-specified `.btn-brass` and `.admin-btn-delete` at 0,1,0 and
       repainted them -- six Delete buttons on /Admin/Roles in near-black instead of claret, and a
       brass button in each of two dialogs rendered transparent. No test in the corpus asserted the
       colour of a disabled button; `CascadeLayerPlaywrightTests.ADisabledButtonKeepsItsOwnColour`
       does now.
 - FOUR HEADINGS ON /Admin/Content, 24px of line-height to 21.6px, as `h1,h2,h3,h4` reclaimed
       the property from Bootstrap's `.h5`. That page's own comment already calls it the one drawn
       in Bootstrap idioms the rest of the site does not theme.

   Everything else on those 27 routes is identical.

   THE ONE FACT THAT DECIDES EVERYTHING HERE: A LAYER BEATS NO SPECIFICITY, AND UNLAYERED BEATS
   EVERY LAYER. Measured in the engine the E2E net drives, not reasoned about:

     @layer first { .b1.b2.b3 { color:red } }   span { color:green } ->  GREEN

   A rule weighing 0,3,0 inside a layer loses to a bare type selector outside one. That is not a
   tie-break, it is a sort key above specificity, and it is why `@layer` cannot be adopted a rule
   at a time in a document that has unlayered rules left in it.

   WHICH IS WHY BOOTSTRAP HAD TO MOVE FIRST, AND IT IS THE WHOLE FINDING OF THIS SESSION.
   Bootstrap was a plain <link> ABOVE site.css, so it was unlayered, and unlayered is the top of
   the stack. The two files declare the same property for the same key selector in 36 places --
   `.btn` in sixteen of them, and `body`, `a`, `p`, `h1` to `h5`, `button`, `input`, `textarea`,
   `select`, `.card`, `.form-control`, `.nav` and `.table` among the rest. Putting ANY of those
   rules of ours into a layer while Bootstrap stayed outside one would have handed the property
   back to Bootstrap: the site would have repainted in Bootstrap's link blue and Bootstrap's
   system font stack, at every specificity we could have written.

   AND `!important` RUNS THE OTHER WAY, WHICH IS THE TRAP IN THIS FEATURE. For important
   declarations the layer order REVERSES -- an earlier layer wins, and an unlayered important
   loses to every layered one. Measured the same way:

     @layer first { #c { color:red !important } }   #c { color:green !important } ->  RED

   So Bootstrap's 1,715 important declarations now beat this file's 29. That was measured before it
   was done, by walking every element the markup writes and asking which rules land on it: NO ELEMENT
   ON THIS SITE carries an important from both stylesheets that disagree about a property. The nearest
   thing is the masthead's `btn btn-ghost ms-md-2`, where this file says `color` and Bootstrap says
   `margin-left`, so both apply and neither is overruled -- and that element is the positive control
   `CascadeLayerTests.NoImportantBootstrapCanNowBeat_LandsOnAnElementThisSiteWrites` asserts on,
   because an empty answer from a scan that matched nothing looks exactly like an empty answer from a
   scan that worked. Three pairs do exist between the two files and land on nothing the markup spells:
   `[hidden]{display:none!important}` in the media picker and the photo editor against Bootstrap's
   reboot saying the same words, at the same value, to every `[hidden]` element.

   A LAYER NAME THAT IS NOT IN THE ORDER STATEMENT IS NOT A MISTAKE THE BROWSER REPORTS. It is a
   NEW layer, appended AFTER every declared one, so a typo does not lose a rule -- it promotes it
   above the whole file. Measured:

     @layer first, second;
     @layer nothere { #g { color:red } }   @layer second { #g { color:green } } ->  RED

   `CascadeLayerTests.EveryLayerBlockNamesADeclaredLayer` is the only thing standing between that
   spelling mistake and a cascade nobody can read.

   TO MOVE A RULE INTO AN EARLIER LAYER you must show it beats nothing that stays behind: a rule
   may only move into layer L if every rule it currently beats is in L or earlier, and every rule
   that currently beats it is in L or later.
   `CascadeLayerTests.NoRuleInAnEarlierLayerWouldHaveBeatenOneInALater` computes that over this file
   and fails naming the pair -- TWICE, in opposite directions, because for `!important` the earlier
   layer is the one that wins now. The cohort in `base` is `:root`, whose 43 declarations are all
   custom properties and therefore have no property to lose, plus every rule whose every selector is
   type-only -- weight 0,0,n -- because any rule carrying one class already outranked those on every
   element the two could share, so the move could not change an outcome. That proof is the reason
   `base` is small: it is what could be proved today, not what belongs there.
   ============================================================================================ */
@layer vendor, base, site;

/* ---------------------------------------------------------------------------------------------
   @layer base -- THE TOKENS AND THE ELEMENT DEFAULTS.

   Every selector in here is either `:root` or type-only (weight 0,0,n), and that is the whole
   argument for the move rather than a tidiness claim:

 - the type-only rules were already beaten by any rule carrying even one class, on every
       element the two could share, so putting them under `site` cannot change an outcome;
 - `:root` declares 43 things and all 43 are custom properties, so there is no property for it
       to lose. The four rules anywhere in this file that match `html` or `:root` were read one by
       one: `html{scroll-behavior}` is in here beside it, and the other two are
       `html.board-snaps:has(.home-widgets)` and `html:has(.home-widgets) .site-foot`, which
       declare `scroll-snap-type` and `scroll-snap-align`. Nothing overlaps.

   `p, li, label, input, textarea, select, button, .admin-list__meta, .page-hero__lead` is in here
   WITH its two class selectors, and that is the one entry that needed a measurement rather than an
   argument: nothing anywhere in this file declares `font-family` on either class, so the two cannot
   lose anything by sitting under `site`. If that ever stops being true the right answer is for the
   later rule to win, which is what this arrangement now does by itself.

   What the layer buys is the next one. An element default written here can no longer out-rank a
   component by being written later in the file, which is the mistake the forty-six position
   comments in this file exist to prevent one at a time.
 --------------------------------------------------------------------------------------------- */
@layer base {


/* ============================================================================================
   COLOUR RULES. Read these before adding a rule that pairs a colour with a surface.

   This site is drawn on TEN palettes. Seven are light, four have dark surfaces and light text,
   and four have a brass so dark that it is unreadable as text. The values below are Heritage's,
   and Heritage is the theme the developer usually has active — which is exactly why a rule that
   looks right here can be unreadable on three others without anyone noticing.

   The TC sweep (2026-08-03) measured every piece of text on 38 pages across all ten themes and
   found 136 broken shapes. These are the rules it distilled. See docs/theme-contrast-sweep-plan.md.

     NEVER  a fixed surface (#fff, #fafafa, #111) with a theme text token
            -> the four dark themes give you near-white text on near-white, at 1.2:1
     NEVER  a fixed text colour on a themed surface — the same defect from the other end
     NEVER  var(--brass) as TEXT. 1.75:1 on College against --ink, 2.42:1 on Heritage against
            white. As a FILL it is fine, but only with var(--on-brass), which is derived.
     NEVER  var(--ink) as TEXT. It is a SURFACE token; --heading is the text one and is safe on
            --surface, --parchment and --parchment-2 on all ten (>= 10.98:1).
     NEVER  a hairline token (--line-light, --line-dark) as a background behind text.
     NEVER  a DERIVED accent on a ground washed with that same accent. --brass-ink, --claret, --slate-soft
            and --on-brass are lifted to a 4.5:1 floor and then stopped, so where the floor binds they have
            ZERO headroom and any color-mix of the token under itself spends it. Measured: --brass-ink on
            bare --parchment-2 is 4.53; at a 7% wash of itself, 4.14; at 16%, 3.43. Tint from the GROUND
            (--surface, --parchment-2, or a mix), which the floor already covers. HW Phase 4;
            StylesheetPairingTests.NoDerivedAccentTintsTheGroundBeneathItself.

      USE   var(--surface) for cards and form controls, --parchment-2 for panels and chips
      USE   var(--heading) for headings, --slate for body, --slate-soft for muted
      USE   var(--brass-soft) for an accent on a DARK band (>= 8.18:1 on all ten)
      USE   var(--brass-ink) for an accent on a LIGHT one (a derived 4.5:1 floor)
      USE   var(--on-dark) for text on --ink / --ink-deep
      USE   the --ok-* / --warn-* / --bad-* pairs below for status; they are fixed on purpose

   FOUR TOKENS ARE DERIVED, not stored, and ThemeStyleComposer is where: --on-brass,
   --brass-ink, --claret and --slate-soft carry a measured 4.5:1 floor, plus color-scheme so the
   browser paints native controls (date pickers, select popups) for the right kind of page.
   The values in this :root block are the FALLBACK for when no theme is active.

   TWO THEME TOKENS PAIRED IS NOT SAFE. --brass on --ink are both themed and still fail at
   1.75:1. Measure; do not reason. The instrument is
   ThemeContrastSweepPlaywrightTests.FullSweep_WritesTheWorkList (HOGLODGE_CONTRAST_SWEEP=1) and
   EveryThemeKeepsTheRepresentativePagesReadable is the standing guard.

   AND TAKE A SCREENSHOT. The crawler proves ratios. It found none of these three, all real:
   five white form inputs on a dark theme (an empty input has no text to measure), a green status
   label that became a full-width banner, and text over a background image, whose measured ratio
   is fiction.
   ============================================================================================ */
:root{
  --ink:#111111;
  --ink-deep:#000000;
  --surface:#fff;
  --brass:#C7A24A;
  --brass-soft:#E4CF94;
  --brass-ink:#9A7C2E;
  --on-brass:#000000;
  --parchment:#F6F2E9;
  --parchment-2:#EFE9DA;
  --claret:#6F2435;
  --slate:#1f1f1f;
  --slate-soft:#4f4f4f;
  --on-dark:#F6F2E9;
  --heading:#111111;
  --line-dark:rgba(231,217,178,.22);
  --line-light:rgba(0,0,0,.12);
  /* Status pairs (TC, 2026-08-03). DELIBERATELY not themed, and deliberately opaque.
     A status chip was written as fixed ink over a TRANSLUCENT tint, so the tint composited
     against whatever card was behind it: on the four dark themes "Paid" measured 2.04:1 in dark
     green on a dark card. Green means paid on every theme, so the honest fix is a self-contained
     pair that is provably readable everywhere rather than a colour that follows the palette.
     Measured: ok 7.37:1, warn 6.87:1, bad 9.08:1. */
  --ok-ink:#2F5238;   --ok-fill:#E4EDE7;   --ok-line:#A9C3B4;
  --warn-ink:#6E4A12; --warn-fill:#F7EEDC; --warn-line:#DCC392;
  --bad-ink:#6F2435;  --bad-fill:#F8EAED;  --bad-line:#DCB1B9;
  --maxw:1180px;
  --r:14px;
  --serif:"Cormorant Garamond", Georgia, "Times New Roman", serif;
  --sans:"Libre Franklin", system-ui, -apple-system, "Segoe UI", sans-serif;

  /* THE HOME BOARD'S GEOMETRY (SCC Phase 7b, plan §23.13 Q2/Q9, §25). Six values that were six literals
     scattered through this file until migration 062 gave them a row. HomeBoardStyleComposer emits the
     stored ones in a :root block AFTER this one, so these are the FALLBACK — the same contract the theme
     tokens above have — and they hold today's values, which is what makes 062 a no-op on deploy.

     --board-columns IS THREE NUMBERS, and that is the finding this token pair exists for. The grid narrows
     at 901px and again at 561px, and before 7b those two steps were independent literals: a setting that
     wrote only the desktop count would have left two queries quietly asserting three (§23.3). The two
     narrow counts are DERIVED in C# (HomeBoardLayout.MediumColumns / NarrowColumns) and emitted here, so
     the queries below read an answer rather than restating a number.

     --r is NOT board-only. It is the site's corner radius, read in eleven places, and it lives with the
     board settings because ThemeStyleComposer rules it out of the palette on purpose (geometry, not
     colour) — so the board row is the first thing that has ever been able to set it. */
  --board-columns:3;
  --board-columns-md:2;
  --board-columns-sm:1;
  --board-gap:24px;
  --board-card-min-height:0px;
  --board-space-above:44px;
  --board-space-below:74px;

  /* SCC Phase 7e (plan §23.13 Q11, §31). Two tokens that were two literals, and both had to stop being
     literals before a card could fill the window.

     --masthead-h is the height of the sticky bar the board scrolls under, and IT IS 75px AND NOT 74 — which
     is the whole reason it is composed here rather than copied. `.nav` states `min-height:74px`, and the
     gate artifact says "the sticky masthead is 74px of it" — but `.site-head` also draws a 1px
     `border-bottom`, so the box the board actually disappears under is 75px tall. Measured in the browser,
     not reasoned: a full-window card built on 74 is one pixel too tall and a snap stop built on 74 comes to
     rest one pixel underneath the bar. Composing it from the nav's height plus the border's width is what
     stops the two disagreeing again the next time either moves.

     --wrap-pad is .wrap's own gutter. A full-bleed card keeps its CONTENT to the board's measure while its
     background runs edge to edge, and that measure is `--maxw` minus this twice — derived, so the bleed
     card's content column is the same width as an ordinary card's by construction rather than by a third
     copy of 1132. */
  --masthead-nav-h:74px;
  --masthead-border-w:1px;
  --masthead-h:calc(var(--masthead-nav-h) + var(--masthead-border-w));
  --wrap-pad:24px;

  /* SCC Phase 7e: the board's scroll snapping, and the WHOLE `scroll-snap-type` value rather than the
     keyword — `scroll-snap-type:y none` is not a declaration a browser accepts, so an off state expressed as
     a bare keyword beside a hardcoded axis would fail to parse and leave the board snapping.
     HomeBoardStyleComposer emits the stored one; this is the fallback, and it is `y proximity` because that
     is what migration 064 seeds. It costs nothing until a card is full-window, because a scroll container
     with no snap positions in it does not snap. */
  --board-snap:y proximity;

  /* ITEM 22 (migration 080): the two `@keyframes` names a full-window card's arrival runs, and the fallback
     is `none` on both because that is what migration 080 seeds and what every board has rendered as since
     there was a board. HomeBoardStyleComposer emits the stored pair; a token holding a keyframes NAME is
     what lets one stored word choose between five animations with no copy of the vocabulary in this file.
     TWO tokens and not one, because the measurement split the vocabulary in two, see the arrival block
     beside the seam, about eighteen hundred lines down. */
  --board-card-arrival:none;
  --board-content-arrival:none;
}

*{box-sizing:border-box}
html{scroll-behavior:smooth}
body{
  margin:0;
  background:var(--parchment);
  color:var(--slate);
  font-family:var(--sans);
  font-size:17px;
  line-height:1.6;
  -webkit-font-smoothing:antialiased;
}

/* Keep body copy consistently on the brand sans-serif across all UI controls/content */
p, li, label, input, textarea, select, button, .admin-list__meta, .page-hero__lead{
  font-family:var(--sans);
}

h1,h2,h3,h4{
  font-family:var(--serif);
  font-weight:500;
  line-height:1.08;
}

a{color:inherit}

}
/* ---------------------------------------------------------------------------------------------
   @layer site -- EVERYTHING ELSE IN THIS FILE, WHOLE AND IN THE ORDER IT IS WRITTEN.

   One layer and not five. Splitting this into base/components/pages would need every pair where
   an EARLIER rule currently beats a LATER one on specificity to be found and settled first --
   `.home-widget .content-block-rich` at 0,2,0 beating a plain `.content-block-rich` written two
   thousand lines below it is the shape -- and a partition that got one of those backwards would
   invert it silently. Gate answer Q6(a) says over time rather than in one commit, and this is
   what over time looks like on its first day: the vocabulary exists, the file is inside it, and
   nothing moved that could not be proved.

   The closing brace is the last line of the file. A rule added after it would be unlayered, and
   unlayered beats every layer here, including this one -- CascadeLayerTests.NoRuleSitsOutsideALayer
   is what stops that being discovered on a screen.
 --------------------------------------------------------------------------------------------- */
@layer site {

.wrap{
  max-width:var(--maxw);
  margin:0 auto;
  padding:0 var(--wrap-pad);
}

.text-parchment{
  color:var(--on-dark) !important;
}

.eyebrow{
  font-size:.72rem;
  font-weight:600;
  letter-spacing:.18em;
  text-transform:uppercase;
  color:var(--brass-soft);
  margin:0 0 14px;
}

.eyebrow.on-light{
  color:var(--brass-ink);
}

.skip{
  position:absolute;
  left:-999px;
  top:0;
  background:var(--brass);
  color:var(--on-brass);
  padding:10px 16px;
  border-radius:0 0 8px 0;
  font-weight:600;
  z-index:100;
}

.skip:focus{
  left:0;
}

.app-toast{
  position:fixed;
  top:88px;
  right:24px;
  z-index:1200;
  display:flex;
  align-items:center;
  gap:12px;
  background:var(--ink);
  color:var(--on-dark);
  border:1px solid var(--brass);
  border-radius:10px;
  padding:10px 14px;
  box-shadow:0 12px 24px rgba(0,0,0,.22);
}

.app-toast__close{
  border:0;
  background:transparent;
  color:var(--on-dark);
  font-size:1.1rem;
  line-height:1;
  padding:0;
}

.app-toast.is-hidden{
  display:none;
}

a:focus-visible,
button:focus-visible,
input:focus-visible,
textarea:focus-visible,
select:focus-visible{
  outline:2px solid var(--brass);
  outline-offset:3px;
}

section{padding:74px 0}

.site-head{
  position:sticky;
  top:0;
  z-index:50;
  background:rgba(0,0,0,.92);
  backdrop-filter:blur(8px);
  /* SCC Phase 7e: this border is the pixel --masthead-h was missing. It reads the token rather than the
     token guessing at it, so the bar's real height stays composable. */
  border-bottom:var(--masthead-border-w) solid var(--line-dark);
}

.nav{
  display:flex;
  align-items:center;
  gap:24px;
  /* SCC Phase 7e: the NAV's height, which is one of the two things --masthead-h is composed from — the
     other being .site-head's border. It reads the token rather than the token restating this number, so
     there is one statement of how tall the bar is and the card that fills the window can be built on it. */
  min-height:var(--masthead-nav-h);
}

.brand{
  display:flex;
  align-items:center;
  gap:14px;
  text-decoration:none;
  color:var(--on-dark);
}

.seal{
  width:46px;
  height:46px;
  flex:none;
  object-fit:contain;
}

.brand-name{
  font-family:var(--serif);
  font-size:1.05rem;
  line-height:1.05;
  font-weight:600;
}

.brand-name span{
  display:block;
  font-family:var(--sans);
  font-size:.62rem;
  font-weight:600;
  letter-spacing:.22em;
  text-transform:uppercase;
  color:var(--brass-soft);
  margin-top:3px;
}

.menu-toggle{
  display:none;
  /* ITEM 46 SLICE 2: the anchor for .menu-toggle__dot, which is the phone's
     only sight of the messages count. Declared here rather than beside the dot
     because a positioned child whose containing block is set somewhere else is
     a rule that can be deleted from either end. */
  position:relative;
  margin-left:auto;
  align-items:center;
  justify-content:center;
  width:46px;
  height:46px;
  background:rgba(255,255,255,.03);
  border:1px solid var(--line-dark);
  color:var(--on-dark);
  border-radius:10px;
  padding:0;
  font-size:1.1rem;
}

/* ITEM 46 SLICE 2 -- WHAT A PHONE SEES OF THE MESSAGE COUNT (plan §7.5, and
   the departure from gate answer Q5(a) recorded in _Layout).

   Below 900px .nav-links is display:none and the Members link goes with it, so
   the masthead badge -- the whole of Q5 -- was measured INVISIBLE AT REST on
   five screens at 320 and 390. This is what is left visible: the button that
   opens the menu the badge is inside.

   THE RING IS NOT DECORATION. The dot sits on a 46px control whose own ground is
   rgba(255,255,255,.03) over --ink-deep with a --line-dark border, and the glyph
   behind it is --on-dark. A bare brass circle on that reads as part of the
   glyph; the ring is the masthead's own ground, so the dot reads as sitting ON
   the button rather than in it.

   --brass AND NOT A STATUS COLOUR. Every other spelling of this count on this
   site is a brass pill (.cnv-portal__badge, .cnv-nav__badge, .cnv-hub__badge),
   and a red dot would be the one place the site says "messages" in the
   vocabulary it uses for a problem. */
.menu-toggle__dot{
  position:absolute;
  top:7px;
  right:7px;
  width:10px;
  height:10px;
  border-radius:999px;
  background:var(--brass);
  box-shadow:0 0 0 2px var(--ink-deep);
}

/* ==================================================================================================
   ITEM 53 SLICE 3 — THE USER MENU (docs/member-account-plan.md §5.2, gate answers Q5(a) and Q6(a)).

   IT REPLACES THE `Logout` PILL, WHICH WAS 83x43 — one pixel under the floor item 44 exists for, measured
   in both engines. Everything here clears 44 from the first commit rather than waiting to be swept.

   AND IT SITS OUTSIDE .nav-links, which is the departure _Layout records: below 900px this site's nav is
   display:none until the hamburger is pressed, and the one piece of chrome that finally names the signed-in
   member would have been invisible at rest on every phone. That is item 46 slice 2's finding arriving a
   second time — a badge on a nav link collapses with the nav — and it is cheaper to obey it here than to
   pay for it twice.
   ================================================================================================== */

.usermenu{
  position:relative;
  /* Sits last in the masthead. At the desk .nav-links carries margin-left:auto, so the pair is pushed
     right together and the disc lands where the Logout pill was.

     ON A PHONE IT IS THE LAST THING ON THE FIRST ROW, AFTER THE HAMBURGER — brand / hamburger / disc.
     The first draft of this comment said brand / disc / hamburger and A SCREENSHOT SAID OTHERWISE:
     .menu-toggle is declared BEFORE the nav in the markup, and .nav-links takes order:3 below 900px, so
     the two controls fall in source order with the disc last. The screenshot's arrangement is the better
     one anyway — the disc is hard against the right edge, which is exactly where the Logout pill it
     replaces used to sit — so the comment is corrected rather than the layout. */
  flex:none;
}

/* A <summary> is a list item by default and draws a disclosure triangle. Both spellings are needed: the
   standard `list-style` covers Firefox and modern WebKit, the pseudo-element covers older WebKit. */
.usermenu__disc{
  list-style:none;
  display:inline-flex;
  align-items:center;
  justify-content:center;
  width:46px;
  height:46px;
  border-radius:999px;
  background:rgba(255,255,255,.03);
  border:1px solid var(--line-dark);
  color:var(--on-dark);
  cursor:pointer;
  /* 46 and not 44, so the disc stands level with the seal and the hamburger either side of it — both of
     which are 46. It clears the floor by two rather than sitting exactly on it. */
  flex:none;
}

.usermenu__disc::-webkit-details-marker{display:none}
.usermenu__disc::marker{content:""}

.usermenu__disc:hover,
.usermenu[open] .usermenu__disc{
  border-color:var(--brass);
}

/* --brass-soft and never --brass, which is the theme sweep's correction: --brass is 1.75:1 on the College
   palette and this text sits on the masthead's near-black ground on all eleven. */
.usermenu__initials{
  font-family:var(--sans);
  font-size:.82rem;
  font-weight:700;
  letter-spacing:.04em;
  color:var(--brass-soft);
  text-transform:uppercase;
}

/* THE TRAP, AND IT IS WHY THESE ARE TWO RULES RATHER THAN ONE.

   An author `display` BEATS the UA rule that hides a closed <details> — item 46's finding, paid for once
   already. `.usermenu__panel{display:grid}` on its own would paint the whole menu while the disc is shut,
   on every page of the site. The panel is display:none at rest and the [open] rule is the only thing that
   turns it on. */
.usermenu__panel{
  display:none;
}

.usermenu[open] .usermenu__panel{
  display:grid;
  position:absolute;
  top:calc(100% + 10px);
  right:0;
  z-index:60;
  /* min() rather than a bare width: at 320px a fixed 260 would push the panel off the left edge of the
     screen, which is the .wrap gutter arithmetic the 360px sweep already caught on the home board. */
  width:max-content;
  min-width:min(250px, calc(100vw - 32px));
  max-width:calc(100vw - 32px);
  background:var(--ink-deep);
  border:1px solid var(--line-dark);
  border-radius:12px;
  padding:6px;
  box-shadow:0 18px 40px rgba(0,0,0,.45);
}

.usermenu__who{
  display:grid;
  gap:2px;
  padding:10px 12px 12px;
  border-bottom:1px solid var(--line-dark);
  margin-bottom:6px;
  /* The address can be long and there is no useful place to break an email, so it is allowed to wrap
     rather than to widen the panel past the screen. */
  overflow-wrap:anywhere;
}

.usermenu__who strong{
  font-family:var(--serif);
  font-size:1rem;
  line-height:1.2;
  color:var(--on-dark);
}

.usermenu__who span{
  font-size:.78rem;
  color:var(--brass-soft);
}

/* 44px, from the first commit. min-height rather than height, because a wrapped label must grow the row
   instead of overflowing it — WebKit ignores min-height on a select and this is not one, but the same
   measurement is what item 44 will point at this page. */
.usermenu__item{
  display:flex;
  align-items:center;
  min-height:44px;
  padding:10px 12px;
  border-radius:8px;
  color:var(--on-dark);
  text-decoration:none;
  font-size:.92rem;
  font-weight:500;
  white-space:nowrap;
}

.usermenu__item:hover,
.usermenu__item:focus-visible{
  background:rgba(255,255,255,.06);
  color:var(--on-dark);
}

/* ITEM 53 SLICE 5's TOGGLE IS THE ONE <button> IN A PANEL OF ANCHORS, AND THE CLASS WAS WRITTEN FOR AN
   ANCHOR. `.usermenu__item` sets a colour and no background, which is right for an <a> — an anchor has no
   background — and wrong for a <button>, which carries the UA's `buttonface` grey and its own border and
   font-family. On this panel's near-black ground that rendered as a near-white box with --on-dark text
   inside it: "Edit wording" was invisible, reported from the live site, with every one of the nine E2E
   claims that PRESS this control perfectly happy.

   THIS IS THE THIRD TIME THIS PROJECT HAS PAID FOR IT. `button.mp-meeting__act--ghost` carries the same
   correction in the same words and `button.cnv-act` is the other. The shape is always a class written for
   one element re-used on the other, and the net is always green because a default button is a perfectly
   ordinary 44px box that can be clicked.

   Element-qualified so it beats the class rule whatever the source order, and it does NOT set `font`
   wholesale: the class owns the size and the weight, and `font:inherit` here would silently take both
   back. */
button.usermenu__item{
  background:none;
  border:0;
  font-family:inherit;
  text-align:left;
  cursor:pointer;
}

/* Signing out is the one destructive thing in the list, so it is separated rather than coloured: a red
   item in a menu of six reads as an error state, which is the vocabulary this site keeps for a problem. */
.usermenu__item--out{
  border-top:1px solid var(--line-dark);
  border-radius:0 0 8px 8px;
  margin-top:6px;
}

/* THE RESPONSIVE HALF OF THIS BLOCK IS IN THE FILE'S 900px AND 560px QUERIES, near the foot, and it had to
   be moved there: HomeBoardSeamStylesheetTests locates the 560px query by its FIRST occurrence in the file,
   so a second `@media(max-width:560px)` written up HERE made that test read the wrong block and go red - a
   guard doing its job on a rule that was simply in the wrong place.

   ITEM 54 HALF C CORRECTED THIS NOTE. It used to say "this stylesheet carries exactly ONE query per
   breakpoint", and it no longer does: there are SIX at each of 560px and 900px. That sentence was true when
   it was written and nothing checked it afterwards, which is exactly the defect half C fixes on three admin
   screens - found here by a test written against the claim, which went red on the claim rather than on the
   code. The constraint that is really load-bearing is ORDER, not uniqueness: the first 560px query in the
   file is the board's, and a new one must go below it. */

.nav-links{
  display:flex;
  flex-wrap:wrap;
  justify-content:flex-end;
  margin-left:auto;
  align-items:center;
  gap:16px;
}

.nav-links a{
  display:inline-flex;
  align-items:center;
  color:var(--on-dark);
  text-decoration:none;
  font-size:.92rem;
  font-weight:500;
  opacity:.88;
  border-bottom:2px solid transparent;
  padding:6px 0;
  white-space:nowrap;
}

.nav-links a:hover{
  opacity:1;
  border-color:var(--brass);
}

.nav-links .btn{
  padding:9px 16px;
}

/* BT PHASE 2 — TWO OF THESE DECLARATIONS READ A CONTENT BLOCK'S TOKEN, AND THE PLACEMENT IS THE WHOLE
   DESIGN. The button group's `label` part is the group's BASE (gate Q4/Q7), and a base has to lose to a
   button that names its own size. A rule scoped to `.content-block-cta .btn` would weigh 0,2,0 and beat
   `.btn--l`, which is the opposite; declaring it here instead puts the group's value exactly where `.btn`'s
   own default sits, so the presets below still override it and the per-button inline `font-size` still
   overrides them. `--tp-label-*` is only ever published on a content block's CTA wrapper, so every other
   button on the site substitutes the fallback and is unchanged. */
.btn{
  color:inherit;
  display:inline-flex;
  align-items:center;
  justify-content:center;
  gap:8px;
  font-weight:var(--tp-label-weight,600);
  font-size:var(--tp-label-size,.92rem);
  text-decoration:none;
  padding:12px 22px;
  border-radius:999px;
  border:1.5px solid transparent;
  transition:transform .15s, background .2s, color .2s;
  cursor:pointer;
}

.btn:hover{transform:translateY(-2px)}

.btn:disabled,
.btn[disabled]{
  opacity:.55;
  cursor:not-allowed;
}

.btn-brass,
.btn-primary{
  background:var(--brass);
  border-color:var(--brass);
  /* B3 T7: --on-brass (derived) not --ink-deep — on the coloured-accent themes brass is a dark hue where
     ink-deep text is dark-on-dark; --on-brass picks the neutral that actually contrasts with brass. */
  color:var(--on-brass);
}

.btn-brass:hover,
.btn-primary:hover{
  background:var(--brass-soft);
  border-color:var(--brass-soft);
  color:var(--ink-deep);
}

.btn-ghost{
  background:transparent;
  color:var(--on-dark) !important;
  border-color:var(--line-dark);
}

.btn-ghost:hover{
  border-color:var(--brass);
}

.btn-dark{
  background:var(--ink);
  color:var(--on-dark);
}

.btn-dark:hover{
  background:var(--ink-deep);
  color:var(--on-dark);
}

/* ==================================================================================================
   ITEM 55 — .btn-line, THE SECONDARY-ON-LIGHT THIS STYLESHEET HAS NEVER HAD (gate answers Q12, Q13, Q14).

   `.btn` above has a TRANSPARENT border and NO background, so a button carrying it and nothing else is
   indistinguishable from a paragraph on every light surface on the site. The three variants that paint a
   box are .btn-brass (the primary), .btn-ghost (--on-dark text, so a dark band only) and .btn-dark (a
   solid dark pill). There was no fourth.

   FOUND WITH EVERY ASSERTION GREEN, by the screenshot pass on /Members/Account: "Send me the link", "Your
   Masonic record", "Sign out" and "Sign out everywhere else" rendered as four lines of plain text beside
   a brass pill. Item 53 fixed that page alone, scoped to .acct-stack, and booked the site-wide question
   rather than inventing a variant from inside an account slice.

   TWENTY-FIVE CONTROLS, NOT EIGHT PAGES. The booking said eight pages; re-measuring found 25 bare `.btn`
   elements, and FOURTEEN of them are the minus/plus steppers inside the widget editor's control panels,
   which nobody has reported. That split is the whole of gate question 12.

   SO IT IS A NAMED VARIANT AND NOT A CHANGE TO `.btn` ITSELF (Q12a). Giving the base class a box would
   have produced the same eleven pixels on the eleven reader-facing controls AND repainted the fourteen
   steppers in the same stroke — a bordered pill around every minus and plus in a dense settings panel,
   changed by a stylesheet rule nobody had looked at on a screenshot. The counter-argument is real and
   worth recording: four authors in a row wrote a bare `.btn` expecting a box, which is an argument that
   the base class is the thing that is wrong. It is booked as its own question against the steppers rather
   than answered by a rule that reaches them sideways.

   THE 44px FLOOR IS HERE AND IT IS AHEAD OF ITEM 44 (Q14a). These are the same controls item 44 will
   measure, and item 53 already paid four lines to put the floor under one page's copy of them. Leaving
   eleven short so that item 44 can raise them is shipping a known fault twice. It is on .btn-line rather
   than on .btn, so the steppers are untouched by this too — a 44px minus inside
   .widget-layout-control__stepper breaks that row.

   The :not() chain is item 53's, for its reason: it names the three real variants rather than matching on
   "no other class", so a button that gains a size preset (.btn--s) is still a bare button and still gets
   its box.
   ================================================================================================== */
.btn-line:not(.btn-brass):not(.btn-primary):not(.btn-dark):not(.btn-ghost){
  border-color:var(--line-light);
  color:var(--heading);
  min-height:44px;
}

.btn-line:not(.btn-brass):not(.btn-primary):not(.btn-dark):not(.btn-ghost):hover,
.btn-line:not(.btn-brass):not(.btn-primary):not(.btn-dark):not(.btn-ghost):focus-visible{
  border-color:var(--brass);
}

/* ==================================================================================================
   ITEM 55 — .btn-step, THE FOURTEEN MINUS/PLUS STEPPERS IN THE WIDGET EDITOR (gate question 12's note).

   THE GATE SAID THESE WERE BOXLESS. THEY WERE ALSO THE WRONG SIZE AND THE WRONG SHAPE, and that was
   found here rather than in the measurement. A bare `.btn` is `padding:12px 22px`, `border-radius:999px`,
   `font-size:.92rem` and a transparent border. So each of these was an invisible 60px-wide pill carrying
   one glyph, sitting beside a 4rem number input that is `padding:.2rem .35rem`, `border-radius:5px` and
   `font-size:.75rem`. Nothing anywhere styled them: `.widget-layout-control__stepper` lays the row out
   with `display:inline-flex` and paints nothing.

   THIS IS WHY QUESTION 12 WENT TO A NAMED VARIANT RATHER THAN TO `.btn` ITSELF. Giving the base class the
   `.btn-line` box would have made these fourteen into bordered 60px pills -- visible at last, and still
   the wrong control. They do not want the reader-facing variant; they want to match the input they sit
   next to, which is what this rule does.

   THE 44px FLOOR IS DELIBERATELY NOT HERE, AND IT IS THE ONE PLACE ON THE SITE THAT SAYS SO. These sit in
   a dense settings panel inside a dialog, three and four rows to a panel; a 44px minus would push each
   row half as tall again and the panel off the bottom of the dialog, which is the fault item 42's own
   §1 was raised for. 30px clears the glyph and the input, and item 44 gets to decide what a control in a
   dialog owes -- which is a different question from what a control on a page owes, and one this item has
   no business answering from inside a stylesheet rule about a minus sign.
   ================================================================================================== */
.btn-step{
  padding:0;
  width:30px;
  height:30px;
  min-height:0;
  border-radius:5px;
  border:1px solid rgba(100,116,139,.35);
  background:var(--surface);
  color:var(--slate);
  font-size:.95rem;
  line-height:1;
  flex:none;
}

.btn-step:hover,
.btn-step:focus-visible{
  border-color:var(--brass);
  color:var(--heading);
}

/* `.btn:hover` lifts every button on the site by 2px. On a control 30px tall wedged between a label and
   a number input that do NOT move, that reads as the glyph coming loose from its own row. */
.btn-step:hover{transform:none}

/* F10: author-facing button size/shape presets (content-block editor). No class = .btn's default look,
   which equals btn--m + btn--pill, so pre-F10 buttons render unchanged. An explicit per-button text size
   is applied inline by the render partial and wins over the preset's font-size. */
.btn--s{
  padding:8px 14px;
  font-size:var(--tp-label-size,.8rem);
}

.btn--m{
  padding:12px 22px;
  font-size:var(--tp-label-size,.92rem);
}

.btn--l{
  padding:15px 30px;
  font-size:var(--tp-label-size,1.05rem);
}

.btn--pill{border-radius:999px}
.btn--rounded{border-radius:10px}
.btn--square{border-radius:0}

.home-widgets{
  padding:0 0 var(--board-space-below);

  /* SCC PHASE 7e — THIS IS THE RULER A FULL-BLEED CARD MEASURES ITSELF AGAINST, AND `100vw` IS NOT IT.
     A card inside .wrap has to break OUT to reach the window's edges, and the escape per side is
     (window - board)/2. The obvious spelling is `calc(50% - 50vw)`, and it is wrong in a way that only shows
     up on a platform with classic scrollbars: `100vw` INCLUDES the scrollbar, so on any board tall enough to
     scroll — which is every board — the card overhangs the document by the scrollbar's width and the page
     gains a horizontal scrollbar it never had. That is the defect §22 removed once already and told the next
     phase to remove rather than shave.

     `container-type:inline-size` makes this section a size container, so `cqw` inside it resolves against the
     section's own content box — the document's width, scrollbar already excluded. `calc(50% - 50cqw)` is then
     exact at every width on every platform, with nothing clipped and nothing to hide.

     It costs the containment `container-type` implies (layout, style, inline-size). Checked before it was
     written: no partial under this section positions anything `fixed`, and the only absolutely positioned
     thing on the board is `.home-widget--content-block::after`, whose containing block is the card. */
  container-type:inline-size;
}

/* SCC PHASE 7n — A MIN-HEIGHT IS A FLOOR AND IT CANNOT LEVEL A ROW.
   `align-items:start` stood here, so every grid item was exactly as tall as its own content and two cards
   in one row only ever matched by coincidence. The user reported it against the board's minimum card
   height, which is the control anybody would reach for: *"I cannot get the hero and menu widgets to sit
   flush, no matter how high I set the min height."*

   MEASURED ON THE RUNNING BOARD AT A 600px FLOOR — the hero needed 612.7px and the festive board 627.2px,
   so the floor was binding on NEITHER, and raising it lifted both together while the 14.5px difference
   between them stayed exactly where it was. Rows two to four looked flush in the same screenshot only
   because both of their cards were BELOW 600 and were therefore clamped to precisely it; levelling was a
   side effect of the floor binding, and it stops the instant one card outgrows the setting. That is why the
   control appeared to work sometimes, which is worse than never working.

   `stretch` is the grid default and it is what levels a row. The floor keeps its job — it sets how tall a
   row is when everything in it is short — and the two now answer the two different questions an author
   actually has: "how tall is a short row" and "do the cards in a row line up".

   WHAT IT COSTS: on any board where a row's cards have different natural heights, the shorter one now grows
   to the taller. That is the change being asked for. `.home-widget` carries `height:100%` below so the card
   fills the wrapper the grid stretched, because stretching the WRAPPER is not stretching the CARD — the
   same distinction 7c's rail hit when hiding a column left its track behind (§26.5). */
.home-widget-grid{
  display:grid;
  grid-template-columns:repeat(var(--board-columns),minmax(0,1fr));
  gap:var(--board-gap);
  align-items:stretch;
  padding-top:var(--board-space-above);
}

/* SCC PHASE 7b — THE MINIMUM CARD HEIGHT, AND THE RULE IT REPLACES.
   What stood here was the only rule in this stylesheet that spoke to a card's height:

     @media(min-width:901px){
       .home-widget-grid > .home-widget--content-block,
       .home-widget-grid > .board{ min-height:620px; }
     }

   IT HAD SELECTED NOTHING SINCE 26 JUNE. Both selectors need a DIRECT child of the grid, and since that
   date every card renders inside a `home-widget--span-N` wrapper (Index.cshtml), so its two subjects have
   been GRANDCHILDREN — live for five days after the initial commit, inert for the two months since.
   Measured rather than reasoned: all eight cards on the running site report `min-height: 0px`, including
   the two the rule named, and no test in the suite asserts 620 anywhere. The rule ten lines below already
   knew about the wrapper (`.home-widget-grid > .home-widget--with-custom-bg > .home-widget` reaches
   through two levels because it had to).

   IT IS DELETED RATHER THAN REVIVED, and that is a decision (§23.13 Q3). Reviving it would attach a height
   floor to `--content-block`, which Phase 2 turned into the FEATURE shell — so the floor would follow
   whatever card an author switched to Feature and leave again when they switched back. A widget's LOOK
   deciding its geometry is the exact weld Phase 2 broke. The floor belongs to the board, so it is the
   board's setting, it reaches every card through the wrapper the old one could not see, and it ships
   defaulting to 0 — which is not caution, it is what the board has actually been doing for two months. */
@media(min-width:901px){
  .home-widget-grid > * > .home-widget{
    min-height:var(--board-card-min-height);
  }
}

.home-widget{
  border-radius:var(--r);
}

/* SCC Phase 7n: STRETCHING THE WRAPPER IS NOT STRETCHING THE CARD. The grid above stretches its items, but
   every card renders inside a `home-widget--span-N` wrapper (Index.cshtml, since 26 June) — so without this
   the wrapper would fill the row and the card would sit at its natural height at the top of an empty box,
   which looks identical to the defect being fixed. Scoped to the wrapper's child so it cannot reach a
   `.home-widget` that is not on the board. */
.home-widget-grid > * > .home-widget{
  height:100%;
}

.home-widget--span-1{
  grid-column:span 1;
}

.home-widget--span-2{
  grid-column:span 2;
}

.home-widget--span-full{
  grid-column:1 / -1;
}

/* SCC PHASE 7e — THE LAYOUT MODE (plan §23.13 Q11, §31). Every rule below is on the WRAPPER, because the
   wrapper is the grid's item: the shell, the alignment and the frame all paint the card inside the space the
   board gave it, and these three decide how much space that is. A negative margin on the card would have
   pulled the card out of a wrapper that stayed 1132px wide and left the grid a hole where it used to be.

   FULL-BLEED IS ALSO FULL-SPAN, and it is stated here as well as derived in C#. HomeWidgetPartialModel
   forces the span so the wrapper carries `--span-full` too; this restates `grid-column` at 0,1,0 so that a
   card rendered by an older cached page — or by any future caller that forgets — still cannot bleed out of
   one column of three. The two agree, and neither is load-bearing alone. */
.home-widget--mode-bleed{
  grid-column:1 / -1;
  margin-inline:calc(50% - 50cqw);
  /* SCC PHASE 8 (plan §33, migration 065) — A STAGE HAS A MEASURE, AND ITS DEFAULT IS TODAY'S BEHAVIOUR.
     Every consumer below reads `--card-measure` instead of restating the board's width, so the four named
     measures are four declarations on this one element rather than a rewrite of each rule that caps
     something. The default is derived from the wrap this card is escaping (`--maxw` minus its gutter twice)
     exactly as the rule it replaces was, so a full-width card nobody has given a measure is byte-identical
     to before the column existed. */
  --card-measure:calc(var(--maxw) - var(--wrap-pad) * 2);

  /* BACKLOG §11. A BLOCK'S OWN WIDTH IS A SHARE OF THE CARD'S MEASURE, NOT A NUMBER OF ITS OWN.
     Reported as "I cannot get the charity card to span the page any wider than the tight central format it
     keeps for every setting", and the report is exact: SCC 7c's block Width control renders as an INLINE
     `max-width`, and an inline declaration cannot be beaten by a stylesheet rule at any specificity, so
     this whole seam, whose entire premise is that "every consumer reads --card-measure instead of restating
     the board's width", had one consumer restating it instead.

     MEASURED ON THE RUNNING SITE, one card, three settings of its Measure control: `--card-measure` moved
     100% -> 720px -> 100% exactly as promised, the charity figure followed it (1442 -> 720 -> 1442), and the
     text block never moved at all, 760, 760, 760. At `narrow` that is a block FORTY PIXELS WIDER than the
     stage its own card defines, so the control was not merely dead in the widening direction; the block was
     escaping the column in the other one.

     IT IS §33's OWN REPAIR, ONE CONTROL LATER. The join band's two typography measures were rewritten as
     shares of `--card-measure` for exactly this reason (see `--band-heading-measure` below, and the note
     beside it: "Left as absolutes they would have silently beaten a card-level measure ... and the new
     control would have shipped looking correct and doing nothing at all"). The block Width control is the
     one that phase did not reach.

     THE DIVISION IS WHAT KEEPS EVERY SHIPPED CARD STILL. 760 and 420 are the numbers 7c chose by eye for the
     board's own measure, so they are written as the SHARE of that measure they already are: at the default
     the arithmetic returns the very number it replaces, to the pixel, and not an approximation of it. 1132 is
     a literal here for one reason: `calc()` cannot portably divide a length by a length, so the ratio needs
     a number. It is the safest possible literal: `--maxw` and `--wrap-pad` are two of the three tokens
     deliberately kept out of the theme system, so the board's measure is a constant of the design.

     AND THEY LIVE ON `--mode-bleed`, WHICH IS WHAT MAKES AN ORDINARY CARD BYTE-IDENTICAL. A card with no
     stage has no `--card-measure` either, so the block's `var()` falls back to the absolute it has always
     used and 760/420 reach a 303px column exactly as before.

     THE `max()` IS A PHONE FIX AND IT WAS FOUND BY READING THE ARITHMETIC BACK, not by a screenshot. The
     `full` measure is the string `100%`, so its share is a PERCENTAGE, and a percentage binds at every
     width. A bare share would therefore have squeezed a Wide block to 67% of a 390px card, sixteen per cent
     of gutter each side ON TOP of the shell's own padding, on the narrowest screen there is and on the very
     card this item was reported about. `max()` states the floor the control has always meant: never narrower
     than 760px, and it GROWS with a wider stage. The outer `min()` is the other half of the same sentence,
     never wider than the stage either, which is the escape §11.2 measured. Read together: *up to 760px or the
     stage's share of it, whichever is more, and never past the stage.* */
  --block-width-wide:min(var(--card-measure), max(760px, calc(var(--card-measure) * 760 / 1132)));
  --block-width-narrow:min(var(--card-measure), max(420px, calc(var(--card-measure) * 420 / 1132)));
}

/* THE THREE NAMED MEASURES (ContentBlockStage.AllMeasures). Each is written WITH `--mode-bleed` rather than
   alone — 0,2,0 against the default's 0,1,0 — so it wins by specificity wherever either rule sits in this
   file. That is §33.5's whole instruction: this phase must not become the fifth instance of a setting
   written at the same weight as the thing it composes onto.

   THE LENGTHS LIVE HERE AND THE NAMES LIVE IN THE DOMAIN, which is the same division the shell vocabulary
   keeps: `narrow` is a single reading column at 720px, `wide` is a column half again past the board's own
   1132px, and `full` gives the words the whole stage inside the card's padding. A number of pixels in the
   database would have been a second stylesheet nobody could see. */
.home-widget--mode-bleed.home-widget--measure-narrow{
  --card-measure:720px;
}

/* SF PHASES 3+4, GATE Q1 AND Q2 — `wide` IS THE FLUID MEASURE, AND IT IS A REDEFINITION RATHER THAN A
   FIFTH NAME (docs/sample-front-end-plan.md §14.1, gate answers §15.3, build record §16).

   IT WAS `1560px`, AND MEASURED AT SEVEN WIDTHS THAT NUMBER MADE THE CONTROL SAY LESS THAN IT LOOKS.
   `wide` and `full` were BYTE-IDENTICAL from 390 to 1440 — 1560px cannot bind inside a card narrower than
   that, and the rules consuming `--card-measure` state `width:100%` first — so four names offered one
   behaviour at 390, two at 820 and three anywhere below 1920. Above it `wide` froze at 1560 and stopped.
   §1.1's frozen-pixel disease, in the width axis and in the VOCABULARY rather than in a number an author
   typed.

   THE SHAPE IS PHASE 2's OWN, IN THE OTHER AXIS: a floor, a viewport-proportional middle, and a ceiling.
   One mechanism and one sentence covers both — type and measure grow together and stop together — which is
   why the runner-up (`min(92vw, 1560px)`) was refused: it grows and then freezes again, one screen later.

   EVERY NUMBER IN IT IS SOMETHING THAT ALREADY EXISTS.
     · the FLOOR is today's default measure to the pixel, written as the same derivation the default above
       states (`--maxw` minus its gutter twice = 1132px) rather than as a third statement of 1132, so a card
       given `wide` on a laptop is byte-identical to one that was never given anything;
     · the MIDDLE is 68vw, which reaches the floor at 1665px and the ceiling at 2794px — the band between
       them is where every monitor this board is actually read on sits;
     · the CEILING is 1900px, which is where a 135px headline (Phase 2's ceiling on the hero's authored
       100px) has room for its two lines with the hero's own slack, measured rather than chosen.

   `vw` AND NOT `cqw`, AND THE SCROLLBAR IS THE REASON IT IS SAFE HERE RATHER THAN THE REASON TO AVOID IT.
   `vw` includes the scrollbar (`vw-includes-the-scrollbar`), so on a 1920 window with a 15px bar this
   returns 1305.6px against 1905px of real content box — under it, and every consumer states `max-width`, so
   an over-large measure cannot overflow anything. It is bounded by construction and needs no container.

   THE VOCABULARY DOES NOT MOVE. `ContentBlockStage.AllMeasures`, `MeasureLabel` and every stored value are
   unchanged: a card already set to `wide` keeps the setting and gets the new behaviour, which is the whole
   of gate Q2 and is free because NO card on the board was set to `wide` when this shipped. */
.home-widget--mode-bleed.home-widget--measure-wide{
  --card-measure:clamp(calc(var(--maxw) - var(--wrap-pad) * 2), 68vw, 1900px);
}

.home-widget--mode-bleed.home-widget--measure-full{
  --card-measure:100%;
}

/* The content stays at the board's measure while the background runs edge to edge, which is the "second
   inner wrap" the gate artifact asks for — written as a measure on the card's own top-level blocks rather
   than as a wrapper element, so it needs no change to any of the widget partials and, more importantly,
   leaves every shell's own padding exactly where it was. A rule that set `padding-inline` on the card would
   have had to restate 28px for `surface` and 42px for `content-block` and would have been a second copy of
   both, free to drift.

   The measure is DERIVED from the wrap it is escaping (`--maxw` minus its gutter twice) rather than being a
   third statement of 1132. */
/* THE SELECTOR IS DELIBERATELY HEAVIER THAN IT LOOKS, AND 7d's FRAME LEARNT THIS FIRST. Written at 0,2,0
   this rule TIES with `.home-widget .content-block-rich--fit`, which states `margin:0` five hundred lines
   later and therefore won — so a fit block hung at the left-hand end of its own measure while the button and
   stat rows beside it centred correctly. Reported from the running site as "center align still not working",
   and the asymmetry was the whole clue: one block was left, its neighbours were not.

   The fit rule's `margin:0` is about the BOTTOM margin (its comment says so: a bottom margin on a `flex:1`
   box shortens the very height the fit's search measures against), so overriding only the INLINE margins
   leaves its reasoning intact. Adding `.home-widget-grid` takes this to 0,3,0, which is
   position-independent — §30.4's rule, that a composed setting must beat whatever it composes onto no matter
   where either rule sits in the file. */
/* SCC PHASE 7f — `width:100%` IS LOAD-BEARING, AND IT IS WHERE 7e's TWO FIXES MET EACH OTHER.
   This rule centres by `margin-inline:auto`, which needs free space to absorb and therefore needs the box to
   have a width first. In normal flow it does: a block-level child is already the full width of its parent and
   `max-width` caps it. But §31.11 made a full-window card a FLEX COLUMN, and in a flex container auto margins
   on the cross axis are resolved BEFORE `align-items:stretch` and defeat it — so every block in a full-window
   card stopped stretching and became shrink-to-fit.

   Measured, because it is invisible until something inside the block needs the room: on a 1280px board the
   same blocks that are 303px wide on an ordinary card came back 219px (rich), 76px (buttons) and 274px
   (charity) in full-window, each perfectly centred at the wrong size. That is §31.13's "charity in
   full-window" — the alignment mechanism was never at fault, the block had no width for its own layout to
   happen in. It is also why short headings kept looking uncentred: centring text inside a box that has
   already shrunk to the text is a no-op.

   `width:100%` gives the box its width back in BOTH layout modes, so this one rule reads the same whichever
   shape the card is, and the cap and the centring do what they always said they did. Safe against padding
   because the file sets `*{box-sizing:border-box}` at the top. */
/* SCC PHASE 8: the cap is now the CARD'S MEASURE and not a restatement of the board's. The token's own
   default (on `--mode-bleed` above) is the expression that used to be written here, so this line changes no
   pixel on a card nobody has given a measure — and a card that has one narrows or widens by four
   declarations in one place instead of by a second copy of this rule per named width. */
/* IB PHASE 3 FOLLOW-UP — THE LAYER IS EXCLUDED, AND WITHOUT THIS THE PICTURE STOPS AT THE READING COLUMN.
   Reported from the running site as "the photos seem to be getting cropped for width — there is a lot of
   space at the sides on a wide screen monitor", and measured: on a 1920px window the card is 1920 wide at
   left 0 and the background layer came back 1132 wide at left 394. The layer is a `> *` of a full-bleed
   card, so it took this rule's `max-width` — and an absolutely positioned box with `inset:0`, a max-width
   and auto margins does not overflow, it SHRINKS AND CENTRES. Two hundred and seventy-eight pixels of
   flat shell each side of the photograph, on the one card shape a picture is most worth having.

   IT IS §11.1'S FINDING WITH A DIFFERENT PROPERTY, which is the part worth keeping. Phase 1 found three
   shells whose `> *` lift took the layer out of position, and the guard it wrote asks "can any rule
   REPOSITION the layer by matching it as an anonymous child". A rule that caps its width repositions
   nothing, so that guard was green with this fully in place — the question had to be asked again about SIZE.
   `CardBackgroundLayerTests` now asks both. */
.home-widget-grid > .home-widget--mode-bleed > .home-widget > *:not(.home-widget__bg):not(.home-widget__scrim):not(.home-widget__seam){
  width:100%;
  max-width:var(--card-measure);
  margin-inline:auto;
}

/* ITEM 50 — THE ONE CHILD THAT MUST NOT BE STRETCHED, AND IT IS WRITTEN IN THIS FAMILY ON PURPOSE.
   A matched button row (`.content-block-cta--match`) asks for shrink-to-fit so its equal `1fr` tracks
   resolve to the widest label rather than to a share of the card. The rule above is `0,6,0` and says
   `width:100%`; the match rule is `0,1,0`, so plainly written it LOST and the setting looked inert —
   two buttons at 559px each on a 1132px card, measured on the live page. That is this repository's
   recurring finding for the fourth time: a more specific rule cancels a class invisibly, and the
   `section.fbr-section--phone` fix in item 42 is the same shape.

   So the override is spelled with this rule's own `:not()` chain rather than by weight nobody can read:
   a reviewer who changes one of the two sees the other on the next line. `margin-inline:auto` above still
   centres it once it has shrunk, which is why that declaration is not repeated here.

   ABOVE 560px ONLY. Below it the match rule deliberately stacks the buttons one per row at full card
   width, and stretching is the point there — see the `@media (max-width:560px)` block beside the match
   rule itself. */
@media (min-width:561px){
  .home-widget-grid > .home-widget--mode-bleed > .home-widget > .content-block-cta--match:not(.home-widget__bg):not(.home-widget__scrim):not(.home-widget__seam){
    width:fit-content;
  }
}

/* SCC PHASE 7f FOLLOW-UP — A CENTRED BLOCK CENTRES ITS CAPPED CHILDREN, NOT ONLY ITS LINES.
   `text-align` is a property of INLINE content. It says nothing about where a block-level child sits, so any
   child that states a width of its own stays wherever its margins put it — which is flush left. `.section-head`
   is exactly that: `max-width:60ch` and no inline margins.

   It is invisible until the container is WIDER THAN THE CAP, which is why it took full-bleed to show it. On an
   ordinary 303px card 60ch never binds and the head fills the block; on the 1132px measure it stops at 60ch and
   centres its text around a point 226px left of the page's middle, with the events list beneath it correctly
   centred. Reported as "events is still not centralising properly" — and the events block was never at fault.

   `margin-inline:auto` IS PRECISELY TARGETED RATHER THAN BROAD, and that is why it can be applied to every
   child: auto margins only have an effect when there is free space to absorb, so a child that already fills the
   block is untouched and a capped one is centred. The same property that caused §32.2's collision does exactly
   the right thing here, because here the container is not a flex column. */
/* SCC PHASE 8 RAISED THIS FROM 0,2,0 TO 0,3,0, AND IT IS THE FIFTH INSTANCE OF ONE COLLISION — the one
   §33.5 named, found on the running site the hour this phase landed.

   `.home-widget--shell-join .join-copy` states `margin:0` and weighs 0,2,0, which TIED with this rule and
   won on source order (it is nine hundred lines below). `.home-widget--shell-join h2` states the same
   `margin:0` and weighs 0,1,1 — one class and one TYPE — so it LOST. Two rules that read identically in the
   stylesheet, one class apart, and the result was a band whose heading centred and whose paragraph hung at
   the left of the same card. Invisible until Phase 8 made the wrapper wide: at the band's old 820px track
   the copy was 46ch inside 820 and looked roughly right, and at a full-window card's 1661px measure it is
   698px of text pinned to the reading edge under a centred heading. That is the screenshot.

   0,3,0 beats every shell rule of the `.shell .thing` shape without caring where either sits in this file,
   which is §30.4's rule: a setting that COMPOSES onto something else has to out-specify what it composes
   onto, never merely follow it. The two previous fixes of the same shape are the bleed measure against the
   fit block (0,3,0) and the frame against seven shells (0,2,0). */
.home-widget-grid .home-widget [data-block-align="center"] > *{
  margin-inline:auto;
}

/* FULL WINDOW IS FULL BLEED PLUS A HEIGHT, so this class is always worn WITH `--mode-bleed` and states only
   the height and the snapping.

   `svh` AND NOT `vh`: on a phone `100vh` is the tallest the viewport ever gets, so a card sized in `vh` is
   taller than the window whenever the address bar is showing, and a mandatory snap then cannot rest on it.
   `svh` is the smallest, which is the one that always fits.

   `min-height` AND NOT `height`, because a card whose content outgrows the window has to be readable to the
   bottom. That is also why migration 064 seeds `gentle` snapping rather than `strict` — proximity lets the
   reader scroll past; mandatory pulls them back.

   THE SNAP MARGIN IS WHAT PUTS THE CARD UNDER THE BAR RATHER THAN BEHIND IT. .site-head is sticky and 74px
   tall, so a snap position aligned to the scrollport's own start edge hides the card's first 74px underneath
   it. Expanding the snap area upwards by exactly the masthead's height moves the resting place down by the
   same amount — and it is `scroll-margin` on the target rather than `scroll-padding` on the container
   deliberately: `scroll-padding` would also move where every `#fragment` on every page lands, which is a
   change nothing in this phase asked for. */
.home-widget--mode-window{
  min-height:calc(100svh - var(--masthead-h));
  scroll-snap-align:start;
  scroll-margin-top:var(--masthead-h);
}

/* SF PHASE 6 STEP 6 — A BAND SHORTER THAN THE WINDOW HAS TO BE A SNAP POSITION OF ITS OWN, OR THE BOARD
   SCROLLS STRAIGHT PAST IT (docs/sample-front-end-plan.md section 36, gate answer: "make it a snap stop,
   keep it short").

   MEASURED ON THE RUNNING BOARD. The document is `scroll-snap-type:y mandatory` and every content-block card
   is `min-height:calc(100svh - masthead)` with `scroll-snap-align:start` (the rule above). A photo gallery
   card is neither: the mode class rides the WRAPPER and comes from a content block's LayoutMode, so a
   photo-viewer card has no mode at all - `scroll-snap-align:none`, `min-height:auto`, 458.8px tall. Under
   mandatory snapping a scroll must come to rest ON a snap position, so the gallery sat in the gap between
   the History card's snap point and the Useful links card's: a request to scroll to its top landed with the
   card 384px ABOVE the viewport. It could be passed but never rested on.

   `start` AND NOT `end`, WHICH IS THE OPPOSITE OF THE FOOTER'S ANSWER TWO HUNDRED LINES DOWN - and the
   difference is what follows. The footer takes `end` because nothing comes after it, so aligning its start
   edge would strand it "at the top of an empty screen". A band in the MIDDLE of the board has a card
   underneath it: aligning its start edge puts the gallery at the top of the viewport with 366px of the next
   card showing beneath, which is the ordinary reading direction and a deliberate break in the
   one-card-per-screen premise rather than an accident of it.

   `:has()` REACHES THE WRAPPER, which is the element the grid lays out and therefore the element the snap
   applies to. The wrapper carries the span, the mode and the stage - none of which a photo-viewer card has -
   so the card's own class is the only thing to select on, one level in. Same mechanism 7e used for the
   full-window join band.

   THE SNAP MARGIN IS THE SAME 74px SENTENCE as the window rule's: `.site-head` is sticky, so a snap position
   aligned to the scrollport's own start edge hides the band's first 74px behind it. */
.home-widget-grid > :has(.home-widget--photo-viewer){
  scroll-snap-align:start;
  scroll-margin-top:var(--masthead-h);
}

/* SCC PHASE 7e — A WINDOW-TALL CARD HAS TO BE A FLEX COLUMN, AND THIS WAS FOUND FROM THE RUNNING SITE.
   Making the box a window tall is only half of it: the card's blocks are in normal flow, so all of them sat
   at the TOP of a screen-high card with the rest of it empty. Two separate reports, one cause — "text will
   not align properly" and "fit to card doesn't work either".

   FIT-TO-CARD IS THE HALF THAT EXPLAINS THE OTHER. The fit box carries `flex:1`, and this file already says
   what that is worth: *"`flex:1` is inert unless the shell is a flex column (only `board` is)"*. So on a
   feature or surface card the fit box never grew, the search measured a content-height box, and the setting
   looked broken — it was doing exactly what it always did, in a card that had become the wrong shape around
   it.

   So the mode supplies the shape the mechanism already needed. `justify-content:center` is what centres a
   card that has no fit block; a card that HAS one gives all the free space to `flex:1` and centres by
   construction, which is why one declaration serves both.

   THE JOIN SHELL IS A GRID AND KEEPS ITS OWN LAYOUT. It states `display:grid` with `align-content:start`, and
   overruling that with flex would collapse its 820px measure and its centring. Its rule is second and at the
   same weight, so it wins on order — the same composition problem 7d's frame had against seven shells.

   COST, said plainly: a full-window card's blocks no longer margin-collapse, because flex items do not. Only
   cards in this mode are affected and the mode is new, so nothing that exists today moves. */
.home-widget--mode-window > .home-widget{
  display:flex;
  flex-direction:column;
  justify-content:center;
}

.home-widget--mode-window > .home-widget--shell-join{
  display:grid;
  align-content:center;
}

/* A STAGE GETS A WIDER MEASURE, and this is the whole of what "make the text wider" needed. Custom
   properties inherit, so setting them on the CARD reaches the shell's own rules without either of them
   naming the other — the seam above is what makes this two declarations rather than a second copy of the
   band's typography.

   SCC PHASE 8 REWROTE THE TWO VALUES IN TERMS OF THE CARD'S MEASURE (gate Q4), AND THAT IS WHAT MAKES THE
   CONTROL DO ANYTHING. Step 1 chose 20ch and 46ch by eye for the board's own 1132px measure; a SHARE of the
   measure reproduces them there (.58 × 1132 ≈ 656px, which is 20ch at the band's display size on a desktop)
   and follows the author everywhere else. Left as absolutes they would have silently beaten a card-level
   measure at 0,2,0 and the new control would have shipped looking correct and doing nothing at all — §33.5,
   and the fifth instance of a collision this project has now paid for four times.

   TWO RATIOS AND NOT ONE, because a display heading and a paragraph do not want the same measure: the
   heading is set at up to 5.1rem and wants a stacked shape, the copy is set at ~1.1rem and wants a reading
   line. The two numbers ARE the band's typography, expressed as proportions of whatever room it is given. */
.home-widget--mode-bleed > .home-widget--shell-join{
  --band-heading-measure:calc(var(--card-measure) * .58);
  --band-copy-measure:calc(var(--card-measure) * .42);
}

/* ---------------------------------------------------------------------------------------------------------
   SCC PHASE 8 — THE ARRANGEMENT (plan §33 Q1/Q2, migration 065): where the content column sits in the stage.

   ONE STORED VALUE OWNS BOTH AXES, and it takes four rules rather than two because THE BOARD HOLDS TWO
   LAYOUT MODELS. Six shells lay their blocks out in normal flow — a flex column once the card is
   window-tall (§31.11) — and the join shell is a GRID with a track of its own. `justify-content` means the
   INLINE axis on a grid and the BLOCK axis on a flex column, and `align-content` means the opposite of
   each, so one declaration cannot serve both and a rule that tried would move the join band sideways when
   an author asked for it to sit low.

   `:not(.home-widget--shell-join)` IS WHAT KEEPS THAT HONEST BY SPECIFICITY RATHER THAN BY ORDER. §31.11
   already relies on source order for exactly this pair and says so; this phase does not add a second
   dependency on where a rule sits in a 14,000-line file.

   THE HORIZONTAL HALF APPLIES TO BOTH REAL MODES AND THE VERTICAL HALF ONLY TO FULL WINDOW, and that is by
   construction rather than by a guard: a full-width card is as tall as its content, so there is no free
   space on the block axis for an arrangement to place anything in. The same reasoning that keeps the phone
   order inert outside the single-column query.
   --------------------------------------------------------------------------------------------------------- */

/* HORIZONTAL — the column is held to the reading edge. `margin-inline:0 auto` is the whole of it for a
   normal-flow card: auto margins only absorb free space, so a block that already fills its measure is
   untouched and a capped one moves to the start. Written at 0,4,0 so it beats the centring rule above,
   which is 0,3,0 and earlier in the file. */
/* IB Phase 3 follow-up: the layer is excluded here too, and this half is DEFENSIVE rather than load-bearing
   — with the cap above gone the layer has no free space for an auto margin to absorb, so `0 auto` resolves
   to zero on it either way. It is stated because the family reads as one rule with one exception, and
   because the next arrangement to be added will be copied from this line rather than from the reasoning. */
.home-widget-grid > .home-widget--mode-bleed.home-widget--arrange-editorial > .home-widget > *:not(.home-widget__bg):not(.home-widget__scrim):not(.home-widget__seam),
.home-widget-grid > .home-widget--mode-bleed.home-widget--arrange-hero > .home-widget > *:not(.home-widget__bg):not(.home-widget__scrim):not(.home-widget__seam){
  margin-inline:0 auto;
  /* BACKLOG §11, THE SECOND HALF. AND `margin-inline:0 auto` ABOVE HAS NEVER REACHED A BLOCK WITH A WIDTH.
     Weight is not the problem this time: 0,4,0 beats the centring rule it was written to beat, and it loses
     anyway, because a block given a Width also renders an inline `margin-inline` and nothing in a stylesheet
     beats an inline declaration. Measured: the Charity card is arranged EDITORIAL and its text block sits
     dead centre, left 370 of 1500, which is the centre to the pixel.

     THE SEAM IS THE ONE THE BAND ALREADY USES: a custom property, so the two rules never name each other.
     The block's inline declaration is `margin-inline:var(--block-place, <its own alignment>)`, so this line
     hands it a value rather than trying to outweigh it, and the fallback keeps the block's own alignment in
     charge whenever no arrangement is set. Declared on the SAME elements the rule above targets, same
     selector, same two exceptions, so the layer and the scrim are excluded here for free.

     WHICH CONTROL WINS IS NOT A NEW DECISION. `CappedBlocks_ShareACentreLine_AndMoveTogetherWhenTheStage
     IsArranged` already seeds its blocks with an explicit `align: center` and requires editorial to move
     them left, so the arrangement beating a block's own alignment is the shipped answer. That test simply
     never gave one of its blocks a WIDTH, which is the only shape the defect has. */
  --block-place:0 auto;
}

/* AND ONE LEVEL DOWN, BECAUSE A BLOCK THAT KEEPS ITS WRAPPER PUTS ITS OWN CAP ON THE GRANDCHILD.
   Measured on the running site: a block with a Layout of its own is NOT dissolved, so the wrapper is the
   grid item — and the wrapper is `width:100%`, which is exactly what makes an arrangement invisible on it.
   The thing that is narrower than the stage is the element INSIDE it (the band's heading at 58% of the
   measure, its copy at 42%), and that element is centred by the card's own alignment. So an arrangement
   that only moved wrappers moved nothing an author could see, on every block that had ever been given a
   layout — which on the Invitation band is the eyebrow, the heading and the copy.

   0,4,0, so it beats the centring rule at the top of this file (0,3,0) rather than merely following it. */
.home-widget-grid > .home-widget--mode-bleed.home-widget--arrange-editorial > .home-widget > * > *,
.home-widget-grid > .home-widget--mode-bleed.home-widget--arrange-hero > .home-widget > * > *{
  margin-inline:0 auto;
}

/* HORIZONTAL, THE JOIN SHELL — its content column is a grid TRACK, so the track has to move (`justify-
   content`) and the items inside it have to stop being centred within it (`justify-items`). Setting only
   the first would leave a 1132px track at the reading edge with everything centred inside it, which is the
   ink still in the middle of the stage and is exactly the report this phase started from. */
.home-widget-grid > .home-widget--mode-bleed.home-widget--arrange-editorial > .home-widget--shell-join,
.home-widget-grid > .home-widget--mode-bleed.home-widget--arrange-hero > .home-widget--shell-join{
  justify-content:start;
  justify-items:start;
}

/* VERTICAL, EVERY SHELL BUT JOIN — the flex column's own axis. */
.home-widget-grid > .home-widget--mode-window.home-widget--arrange-hero > .home-widget:not(.home-widget--shell-join){
  justify-content:flex-end;
}

.home-widget-grid > .home-widget--mode-window.home-widget--arrange-raised > .home-widget:not(.home-widget--shell-join){
  justify-content:flex-start;
}

/* VERTICAL, THE JOIN SHELL — the same two positions, named for a grid. */
.home-widget-grid > .home-widget--mode-window.home-widget--arrange-hero > .home-widget--shell-join{
  align-content:end;
}

.home-widget-grid > .home-widget--mode-window.home-widget--arrange-raised > .home-widget--shell-join{
  align-content:start;
}

/* THE BOARD'S SNAPPING, AND `:has()` IS DOING REAL WORK HERE. Snapping is a property of the SCROLL
   CONTAINER, and the container the board scrolls in is the document itself — so this can only go on the
   root. Scoping it to a page that actually renders a board is not tidiness: `.widget-control-block` in the
   widget editor carries `scroll-snap-align:start` for a stack whose own `scroll-snap-type` sits on an
   element that never became a scroll container, so those blocks' nearest scrollable ancestor IS the root.
   An unscoped rule here would turn every block in the admin editor into a snap stop on /Admin/Widgets.

   `.board-snaps` IS "THE BOARD HAS FINISHED PARSING", AND IT IS THE WHOLE OF A REAL DEFECT'S FIX.
   Reported from an iPad: *"the homepage loads scrolled down to the bottom invitation widget by default."*
   Measured in WebKit against the running site, the home page arrived at a DIFFERENT card almost every load
   — 0, 1056, 2061, 3175, 4180, 5185 and 5750 all seen — and every one of those numbers is a card's own snap
   position.

   THE MECHANISM IS THE PARSER, NOT THE READER. A snap container must stay snapped, and this container is
   the document. While the page is still parsing, each full-window card the parser appends adds a snap area
   and grows the document — so WebKit re-snaps, repeatedly, to a list that is changing underneath it, and
   the page comes to rest wherever the race left it. A timeline of the load shows the whole walk finished
   BEFORE `DOMContentLoaded`: at `readyState:loading` the document is 3,403px and the scroll is 0; by
   `interactive` it is 6,783px and the scroll is already 5,138.

   FOUR THINGS THE MEASUREMENT RULED OUT, each re-run five times because the first run of each lied:
   it is not the footer's snap area (removing it: 0/5 loads at the top), not `scroll-margin-top` (0/5), not
   `svh` versus `dvh`, and NOT A SCRIPT — it happens with JavaScript disabled. Only `scroll-snap-type:none`
   fixed it, 5/5.

   AND IT WAS NEVER AN IPAD BUG. Desktop WebKit at 1280x900 reached the top in 2 loads out of 8; the iPad
   in 1 out of 8. The device changed how OFTEN it bit, not whether. A first run that measured desktop Safari
   at 0 is exactly the single green this project has been caught by before — the difference only appears
   when you run it eight times.

   NOR IS IT THE `strict` SETTING. `y proximity` — the value migration 064 SEEDS — reached the top 1 time in
   5 and lands even lower, because the footer's end-aligned area is then the nearest thing to fall into. Any
   board with a full-window card had this, whatever its snap mode.

   SO THE DECLARATION WAITS FOR THE PARSE IT CANNOT SURVIVE. `site.js` adds `.board-snaps` at
   `DOMContentLoaded`, by which time the walk has nothing left to walk: 8 loads out of 8 at the top, on the
   iPad and on the desktop. Snapping is otherwise untouched — the reader still settles onto a card
   (a scroll to 1400px comes to rest at 1056), and `/#join` still lands on the Invitation band at the
   masthead's own offset, 4 loads out of 4, exactly as it does today.

   A RE-ANCHOR TO THE FRAGMENT WAS TRIED HERE AND MADE IT WORSE. Re-running `scrollIntoView` after enabling
   the class dropped `#join` from 4/4 to 1/4 — the extra safety measure raced the same snap it was meant to
   settle. The fix is the class and nothing else.

   THE COST, SAID PLAINLY: with JavaScript off, a board never snaps. That is a capability quietly absent
   rather than a page quietly broken, and today JavaScript-off gets the defect in full — the screenshot that
   proved a script was not the cause was taken with scripting disabled, and it opened on the Invitation
   band. */
html.board-snaps:has(.home-widgets){
  scroll-snap-type:var(--board-snap, y proximity);
}

/* BACKLOG ITEM 15 — THE TOP OF THE BOARD HAS TO BE A SNAP POSITION, OR WEBKIT WILL NOT REST THERE.
   Found by SF Phase 1's WebKit pass and not caused by it (backlog §15); measured across thirty-one
   configurations in two engines before this line was written.

   THE RULE, MEASURED: A MANDATORY SNAP CONTAINER RESTS AT THE SNAP POSITION NEAREST THE ORIGIN, AND THE
   ORIGIN IS NOT ITSELF ONE. Chromium is content to rest at 0 whatever the list says; WebKit is not, and it
   is not "the first snap area in document order" either — with the FOOTER, the last element on the page,
   given a snap position at 1111 while the first card's sat at 2439, WebKit landed on 1111. It takes the
   NEAREST.

   AND THE TRIGGER IS `strict` ON ITS OWN. §15 recorded it as needing strict AND a full-window card that is
   not the first card. That AND is wrong, which is the half that makes this a live defect rather than a
   shape nobody has: the footer's own `scroll-snap-align:end` two rules down — added by 7e so the footer
   could be reached at all — is a snap area, and it is the only one a board with no full-window card has. So
   a board set to Strict with no full-window card anywhere opens at 2136 of a 2136 maximum in WebKit. That
   is the BOTTOM OF THE PAGE, and it is the iPad report §9 was filed under, arriving a second time as a rule
   instead of a race.

   A ZERO-HEIGHT SENTINEL, AND THE ZERO IS LOAD-BEARING TWICE OVER. It costs no layout — the board measures
   5036px with it and 5036px without, where a 1px sentinel measures 5037 — and, more importantly, a snap
   area TALLER THAN THE SNAPPORT makes every offset that keeps the area covering the snapport a legal rest.
   Saying this on `.home-widgets` itself was measured and is an off switch wearing a fix's clothes: it lands
   at 0 and then a scroll to 600 rests at 600 and a scroll to 2200 rests at 2200, which is the snapping the
   author asked for, silently deleted. With the sentinel the same jumps rest at 2439 and 3288, byte for byte
   what they were before the rule existed.

   THE OTHER TWO CANDIDATES §15 NAMED ARE BOTH REFUTED BY MEASUREMENT. `scroll-snap-align` on the cards is
   not a lever: on a card exactly one window tall, `start`, `center` and `end` all compute to the SAME
   position (2439 for all three), and the landing follows the position rather than the keyword. And moving
   the snap container off the root and onto the board — §15's "mechanism" option — does not fix it: the
   window stays at 0 and the BOARD's own scrollTop reads 2364 in WebKit and 2375 in Chromium. Same defect,
   one container down, and a scroll container inside a snap container is the shape §9 moved away from.

   IT IS INERT ON EVERY BOARD THAT SHIPS TODAY. Migration 064 seeds `gentle`, and under proximity the
   landings and the rests are identical with the sentinel and without it (0 / 600 / 2439 / 3000 both ways).
   The fragment is identical under all three modes too: `/#anchor` comes to rest on the card's snap
   position, 3288 in WebKit and 3299 in Chromium, with the sentinel and without. */
.home-widgets::before{
  content:'';
  display:block;
  height:0;
  scroll-snap-align:start;
  scroll-margin-top:var(--masthead-h);
}

/* SCC PHASE 7e — THE FOOTER NEEDS A SNAP POSITION OF ITS OWN, OR IT CANNOT BE REACHED.
   Reported from the running site ("can't scroll to the page footer either"), and the gate artifact predicted
   it in as many words: *"the footer is the last snap point whether or not it wants to be, and it is shorter
   than a window."* 7e shipped the snapping and did nothing about it.

   The mechanism: with a board of full-window cards the LAST snap position is the last card's top, and there
   is none below it. A reader scrolling past that card is always nearer to it than to anything else, so the
   scroll settles back onto it and the footer stays out of reach — under `mandatory` always, and under
   `proximity` whenever the footer is shorter than the snap's pull, which it is.

   `end` and not `start`, because the footer is SHORTER than the window: aligning its start edge to the
   scrollport's would leave it stranded at the top of an empty screen. Aligning its end edge to the
   scrollport's end is the ordinary bottom-of-the-page reading, and it gives the scroll somewhere legitimate
   to rest.

   Scoped to a page that renders a board, for the same reason the snapping itself is: the footer is on every
   page, and a snap area on a page whose root does not snap is inert but pointless. */
html:has(.home-widgets) .site-foot{
  scroll-snap-align:end;
}

.home-widget--surface{
  background:var(--surface);
  border:1px solid var(--line-light);
  padding:28px;
}

.home-widget--content-block{
  background:var(--ink);
  color:var(--on-dark);
  position:relative;
  overflow:hidden;
  padding:35px 42px;
}

.home-widget--content-block::after{
  content:"";
  position:absolute;
  inset:0;
  background:radial-gradient(120% 90% at 80% -10%,color-mix(in srgb, var(--brass) 18%, transparent),transparent 55%);
  pointer-events:none;
}

/* ITEM 19 — AND ON A PICTURED CARD THIS WASH HAS TO GO UNDER BOTH LAYERS, BECAUSE OTHERWISE IT IS THE
   GROUND THE WORDS LAND ON AND NOTHING CAN MEASURE IT (plan section 31, gate section 32 Q2 answered (c)).
   A `position:absolute` pseudo-element at `z-index:auto` paints with the positioned descendants, and the
   picture and the scrim are both at -1: so eighteen per cent of `--brass` was sitting BETWEEN the scrim and
   the text, in the token family the eyebrow above it is written in, which is the one thing
   [[theme-contrast-crawler]] says never to do.

   IT BROKE THE SENTENCE THE WHOLE FLOOR RESTS ON. `ScrimFloorUnmeasured` is 100 because at 100 the
   composite IS the tint - and measured in pixels at 100 the ground under the hero's eyebrow was `#211E16`
   and not `#111111`, so the eyebrow read 6.89:1 where the arithmetic promised 7.82. Solved back through the
   composite that is 8.8% of `--brass` at that spot, worth FOUR POINTS of scrim: the card's true minimum is
   79 at 1440 and 82 at 390 against a derived floor of 80. The board was safe only because an author had
   typed 85 by eye - and re-deriving the floor with the wash counted gives exactly 85, which is the whole
   diagnosis in one number.

   `-2` AND NOT `-1`: a pseudo-element comes after every child in the box tree, so at the layers' own -1 it
   would still paint on top of them. This is the same two-step the board shell's panel already needed, for
   the same reason, twenty lines from here. `article.home-widget` carries `isolation:isolate`, so a negative
   layer stays inside the card and above the card's own background rather than falling behind it.

   QUALIFIED WITH `--has-bg` (0,2,0) so it cannot reach a card with no picture: there the wash is the whole
   decoration and there is no scrim for it to be under. What it costs where it DOES apply is very nearly
   nothing - side by side, this card at 80 and the shipped card at 85 are the same picture, and on a phone
   the gradient's centre is cropped away almost entirely, so on the width where it cost the most it was
   doing the least. */
.home-widget--has-bg.home-widget--content-block::after{
  z-index:-2;
}

/* IB PHASE 1 — THE SAME EXCLUSION AS THE JOIN SHELL'S, AND THE PLAN SAID THIS RULE EXISTED ON ONE SHELL OUT
   OF SEVEN. It exists on three, and this is the one that mattered most: `--content-block` is the FEATURE
   shell, which is what every instance on every board stores today. A background layer added as a child takes
   `position:relative` from here and collapses to a zero-height in-flow span, so the picture does not
   restack — it vanishes. The rule was found by the guard rather than by reading, which is the whole argument
   for asking "can anything reposition the layer" instead of checking one selector for one `:not()`.
   See CardBackgroundLayerTests. */
.home-widget--content-block > *:not(.home-widget__bg):not(.home-widget__scrim):not(.home-widget__seam){
  position:relative;
  z-index:1;
}

.home-widget-grid > .home-widget--with-custom-bg > .home-widget{
  background:var(--widget-bg-color);
}

.home-widget--content-block h1{
  font-size:clamp(2.3rem,4.4vw,3.9rem);
  letter-spacing:-.01em;
}

.home-widget--content-block.home-widget--content-block--inherit-theme h1 em{
  font-style:italic;
  color:var(--brass-soft);
  font-weight:500;
}

.home-widget--heritage{
  background:var(--ink-deep);
  color:var(--on-dark);
  padding:34px;
}

.home-widget--heritage h2{
  color:var(--on-dark);
}

.home-widget--heritage p{
  color:color-mix(in srgb, var(--on-dark) 82%, transparent);
}

/* ---------------------------------------------------------------------------------------------------------
   SCC Phase 2 — the shells a widget can CARRY (docs/static-content-consolidation-plan.md §9 F1, §12).

   The three rules above (--surface, --content-block, --heritage) already name a LOOK rather than a widget,
   so nothing about them changes; what changed is that a partial no longer hardcodes which one it gets. The
   four below are the looks the board has that had no class of their own, because they lived inside a widget:
   Charity's hover lift, the motto's inset board, the join band, and "no shell at all".

   Two of them are MODIFIERS that compose onto --surface / --heritage rather than restating them. A
   self-contained .home-widget--card would be a second copy of the surface look, free to drift from the
   original, and drift between two rules is invisible to any test that reads only one of them.
   --------------------------------------------------------------------------------------------------------- */

/* `bare`: the escape hatch for a section that paints itself entirely out of its own blocks. Not a reset —
   border-radius stays on .home-widget so a bare section still clips like its neighbours if it grows a
   background of its own. */
.home-widget--bare{
  background:none;
  border:0;
  padding:0;
}

/* `card` = `surface` + this.

   SCC PHASE 2 WROTE "the whole of the difference between .home-widget--surface and the .card class Charity
   uses today" HERE, AND IT WAS WRONG. It was read out of THIS stylesheet, where `.card` is four declarations
   (surface, hairline, radius, 28px padding) plus the lift. But `.card` IS ALSO A BOOTSTRAP COMPONENT, and
   bootstrap.min.css is loaded on every page ahead of this file, so the legacy Charity card resolves through
   BOTH — and the five declarations below are the ones Bootstrap's `.card` contributes that site.css's own
   `.card` never overrides. Phase 4c found them by measuring the legacy card rather than by reading it: the
   flex column alone put the whole rebuild SIX PIXELS HIGH and the card six pixels short, on every palette at
   every width, because flex items do not collapse their margins and block boxes do.

   SCC PHASE 6 TOOK THE FIFTH DECLARATION BACK OUT, AND THE DECISION DISSOLVED RATHER THAN BEING TAKEN.
   Phase 4c shipped `color:var(--bs-body-color)` here and booked "whether the board should inherit
   Bootstrap's body colour at all" as a design decision, because dropping it inside a REBUILD would have
   been smuggling a change in as a match (§14.5's restraint). Phase 5b then deleted the legacy Charity
   card, so there is nothing left to match and no parity obligation to violate — and the measurement says
   the question was never a live one anyway: on the shipped board this shell is the ONLY card of eight
   whose computed `color` is rgb(33,37,41), and all five of its text-bearing descendants set their own
   colour, so not one pixel is painted with it. What it actually was is a fixed #212529 sitting on a
   THEMED board, waiting for the first block that does not paint itself — near-black on the four dark
   seeds. Deleted rather than restated as `var(--slate)`: `.home-widget--surface`, which this shell
   composes onto, sets no colour at all and inherits `--slate` from `body`, so removing the line makes
   this shell agree with its own base by construction instead of by two declarations that have to be
   kept in step.

   THE OTHER FOUR STAY, AND ONLY THIS ONE WAS EVER THE QUESTION. `display:flex;flex-direction:column` is
   load-bearing now rather than inherited: the `display:contents` rule below exists to answer it, and the
   Charity rebuild's measured spacing is what it is because of it. Changing that is a layout change with
   its own measurement to do, not a colour decision.

   The name is now understated (it carries a layout, not just a lift). Renames are Phase 7's, deliberately. */
.home-widget--shell-lift{
  position:relative;
  display:flex;
  flex-direction:column;
  overflow-wrap:break-word;
  transition:transform .18s, box-shadow .18s;
}

.home-widget--shell-lift:hover{
  transform:translateY(-3px);
  box-shadow:0 22px 40px -26px rgba(0,0,0,.45);
}

/* SCC Phase 4c: the card shell dissolves a rich block's wrapper, and it is the THIRD shell to do so for the
   THIRD reason (§14.2 join = one centred column, §15.2 heritage = a margin floor is not a rhythm).

   Here it is the flex column above. `.home-widget .content-block-rich` puts 16px under every block, and in
   normal flow a wrapper whose only box property is that margin collapses its child's through, so the larger
   of the two wins — which is why the surface shell needed no rule for Events or Links. A FLEX ITEM COLLAPSES
   NOTHING: the wrapper's 16px would be ADDED to the eyebrow's own 18px and every block below it would sit
   34px down instead of 18px. The floor is not too small here, it is too many.

   Only a RICH block, for §14.2's reason: `.content-block-cta` is a button ROW and `.content-block-meta` a
   stat ROW, and dissolving those would turn a button group in this shell into a vertical stack. The cost is
   §14.2's too — a rich block's own border and padding (F10) render on the wrapper, so they are inert here. */

/* SCC Phase 7c: `:not(.content-block-rich--laid-out)` -- the block Layout panel's whole reason for reaching all seven shells.
   Every layout declaration renders on THIS wrapper, so without the narrowing six of the thirteen settings
   would have shipped dead here. An unstyled block still dissolves, which is what keeps every rebuilt card's
   parity BY CONSTRUCTION rather than by a tolerance -- not one of the six sets a layout -- and a styled block
   keeps its wrapper, which is what the author asked for by touching it. The border is deliberately NOT part
   of the test: widening it would change how already-stored bordered blocks render in this shell, which is a
   change to content nobody is editing. */
.home-widget--shell-lift > .content-block-rich:not(.content-block-rich--laid-out){
  display:contents;
}

/* SCC Phase 3c: on the heritage shell the CARD'S RHYTHM IS ITS CONTENT'S, so a rich block's wrapper gets out
   of the way — the same `display:contents` the join shell uses, reached from the opposite direction.

   `.home-widget .content-block-rich` puts 16px under every block. Phase 2 widened that rule from the feature
   card to every shell by REASONING ("the same block in a surface or heritage shell would silently lose its
   margins"), and Phase 3c is the first time anything measured what it does somewhere else. It is wrong here:
   the legacy History card's four gaps compute to 14 / 8 / 26 / 28px — an eyebrow's own margin, a heading's,
   a paragraph's and an authored 28 — and TWO of them are under 16, so a rebuild made of blocks could not
   reproduce it at any authoring. A margin floor is not a rhythm; it is the feature card's rhythm generalised.

   Dissolving the wrapper makes each block's authored element a child of the article in normal flow, exactly
   as the legacy widget's five children are, and every spacing question then has the same answer on both sides
   by construction rather than by two numbers that happen to agree (§14.2's argument, second use). The cost is
   §14.2's too: a rich block's own border and padding (F10) render on the wrapper, so they are inert here.

   It is scoped to `--shell-heritage` and NOT to `--shell-board`, which also wears `.home-widget--heritage`:
   the motto's fitted block needs its wrapper, because the fit measures the AREA the wrapper is. */

/* SCC Phase 7c: `:not(.content-block-rich--laid-out)` -- the block Layout panel's whole reason for reaching all seven shells.
   Every layout declaration renders on THIS wrapper, so without the narrowing six of the thirteen settings
   would have shipped dead here. An unstyled block still dissolves, which is what keeps every rebuilt card's
   parity BY CONSTRUCTION rather than by a tolerance -- not one of the six sets a layout -- and a styled block
   keeps its wrapper, which is what the author asked for by touching it. The border is deliberately NOT part
   of the test: widening it would change how already-stored bordered blocks render in this shell, which is a
   change to content nobody is editing. */
.home-widget--shell-heritage > .content-block-rich:not(.content-block-rich--laid-out){
  display:contents;
}

/* `board` = `heritage` + the inset panel the motto paints inside its card.

   §3 books this whole thing as "Motto — widget-level border (frame)", which understates it the same way it
   understated F1: the motto's frame is not a border on the card, it is a SECOND NESTED SURFACE with its own
   gradient, radius, padding and shadow. Drawing it as an inset ::before rather than as a real element is what
   lets a rebuild have it without a container block — there is no block kind that wraps other blocks, and
   inventing one to hold two children would be a far larger change than this.

   The article's own padding is the sum of the THREE boxes it replaces: heritage's 34px, the panel's own 2px
   border, and the board's 16/24/18. SCC Phase 3's parity gate is what found the border term — Phase 2 wrote
   50/58/52 and the rebuilt motto's text landed two pixels high and four pixels wide of the legacy one's, on
   every theme and every width. A frame drawn as a border-box ::before puts its inner face at inset + border,
   and the content has to start there.

   The colour overrides are not optional — .home-widget--heritage sets `color` and styles `h2`/`p` at 0,1,1
   for ON-DARK text, and this panel is LIGHT, so without them the motto would paint near-white text on a
   near-white board. Exactly the class of defect [[theme-render-mapping-tokens]] is about: a fixed surface
   meeting a theme text token. But `--slate` belongs on what sits INSIDE the panel, not on the article: the
   article's own text colour is still the heritage card's, and the parity gate reads it. */
.home-widget--shell-board{
  position:relative;
  display:flex;
  flex-direction:column;
  gap:8px;
  padding:52px 60px 54px;
}

.home-widget--shell-board::before{
  content:"";
  position:absolute;
  inset:34px;
  background:linear-gradient(180deg, var(--surface) 0%, color-mix(in srgb, var(--surface) 88%, var(--ink) 12%) 100%);
  border:2px solid color-mix(in srgb, var(--brass) 38%, transparent);
  border-radius:34px;
  box-shadow:0 24px 48px -34px rgba(0,0,0,.55);
  color:var(--slate);
  pointer-events:none;
}

/* IB PHASE 1 — the third shell carrying the lift, excluded for the same reason (see the note on
   `--content-block > *` above). The `color` here is harmless on a layer that holds no text; it is
   `position:relative` that deletes the picture. */
.home-widget--shell-board > *:not(.home-widget__bg):not(.home-widget__scrim):not(.home-widget__seam){
  position:relative;
  z-index:1;
  color:var(--slate);
}

.home-widget--shell-board h2,
.home-widget--shell-board p{
  color:var(--slate);
}

/* `join`: the claret band. SCC Phase 5b deleted the .join-band widget and the legacy half of every pair in
   this family with it; what is left is the shell, which is what draws the band now.

   THE DOUBLED CLASS BELOW IS LOAD-BEARING AND ITS ORIGINAL REASON HAS EXPIRED, WHICH IS EXACTLY WHY IT IS
   WRITTEN DOWN AGAIN. `.home-widget.home-widget--shell-join` weighs 0,2,0. Phase 3b qualified it that way to
   match `.home-widget.join-band`, because a selector list is resolved PER SELECTOR and the two halves were
   otherwise beaten by different rules ([[shared-selectors-do-not-share-specificity]]). The half it was
   matching is gone — and un-doubling it now would drop it to 0,1,0, where the 560px query below (also 0,1,0,
   and later in the file) would start winning and the band would change on every phone.

   So the qualification stays for the EFFECT it has rather than the symmetry it had. Making that 560px rule
   apply is a design change, not a tidy-up: it has never applied to this band on any phone since the day it
   was written, and it is booked for Phase 6 with the two /History defects. */
.home-widget.home-widget--shell-join{
  padding:54px 28px 44px;
}

/* SCC Phase 3b: the join shell LAYS ITS OWN BLOCKS OUT, which is the whole job of .join-band__inner.

   The legacy band is one centred 820px column of shrink-wrapped, centred items 14px apart, and every
   percentage inside it (the crestline's `min(100%,300px)`, the benefits row's `min(100%,760px)`) resolves
   against that column. Reproducing it with a wrapper element was not possible — there is no block kind that
   wraps other blocks — and reproducing it by styling each block's own wrapper was tried on paper and gets
   the geometry wrong: a wrapper that shrink-wraps makes 100% mean the widest word, and one that stretches
   puts the eyebrow's box at the full width of the card instead of around its text.

   So the card is the column and the wrappers are dissolved. `display:contents` makes each block's authored
   element the band's grid item DIRECTLY, exactly as the legacy's children are children of __inner, and every
   sizing question then has the same answer on both sides by construction rather than by translation. What it
   costs is real and worth naming: a block's own border/padding (F10) renders on the wrapper, so it is inert
   in this shell. This is the only shell that dissolves its wrappers, and it does it because its content is a
   single centred column by design rather than a stack of independent blocks.

   `align-content:start` is not decoration either. A grid with auto rows STRETCHES them to fill a container
   taller than its content, so a band that was ever given a taller row would spread its seven items apart —
   where the legacy, being a block, would leave the space at the bottom. Today the band is span-full and alone
   in its row, so nothing can stretch it; the declaration is what keeps that an accident rather than a
   dependency. */
/* SCC PHASE 8: the 820px column becomes the shell's FALLBACK and the card's measure becomes the value
   (gate Q4). On an ordinary card nothing is set, so the track is the 820px this band has always had; on a
   stage `--card-measure` is inherited from the wrapper and the band's column is the one the author chose.
   It is the same seam step 1 built for the two typographic caps, one level out — the shell states what it
   wants when nobody has said otherwise, and the card says otherwise. */
.home-widget--shell-join{
  display:grid;
  grid-template-columns:min(100%, var(--card-measure, 820px));
  justify-content:center;
  justify-items:center;
  align-content:start;
  gap:14px;
}

/* Only a RICH block is dissolved, and the narrowing is deliberate. A rich block's wrapper carries nothing of
   the block's own definition — the authored markup IS the content — while `.content-block-cta` is a button
   ROW and `.content-block-meta` is a stat ROW, and dissolving those would turn a button group in this shell
   into a vertical stack. The band is made of rich blocks, so it costs the rebuild nothing; what it buys is
   that the other five kinds still mean here what they mean in every other shell. */

/* SCC Phase 7c: `:not(.content-block-rich--laid-out)` -- the block Layout panel's whole reason for reaching all seven shells.
   Every layout declaration renders on THIS wrapper, so without the narrowing six of the thirteen settings
   would have shipped dead here. An unstyled block still dissolves, which is what keeps every rebuilt card's
   parity BY CONSTRUCTION rather than by a tolerance -- not one of the six sets a layout -- and a styled block
   keeps its wrapper, which is what the author asked for by touching it. The border is deliberately NOT part
   of the test: widening it would change how already-stored bordered blocks render in this shell, which is a
   change to content nobody is editing. */
.home-widget--shell-join > .content-block-rich:not(.content-block-rich--laid-out){
  display:contents;
}

/* The join shell paints a decorative pseudo-element — two of them until SF Phase 6 step 4 retired the circle
   into `CardArt` — so its content has to be lifted above it: the same job .join-band__inner does for the
   legacy markup, which a rebuild made of plain blocks will not have. BOTH levels, because there are two
   shapes to lift: a block that kept its wrapper is lifted by the wrapper, and a dissolved one has to be
   lifted by its authored elements instead (`position` on a display:contents element is ignored, so the first
   selector alone would leave the band's own content under the inset frame).

   IB PHASE 1 — THE EXCLUSION IS LOAD-BEARING AND THE DEFECT IT PREVENTS IS INVISIBLE ON THE OTHER SIX
   SHELLS (plan §2.1, risk 1). A background layer is a child of the article, so on this shell alone it is a
   `> *` and takes BOTH declarations above. `z-index:1` would merely restack it; `position:relative` is the
   worse half — it takes an absolutely positioned layer OUT of absolute positioning and collapses it to a
   zero-height in-flow span, so the picture does not move, IT VANISHES. That is not a theory: the gate
   artifact shipped exactly this in its own first render, four identical flat cards and no photograph
   anywhere, from a plain specificity tie (`.card > *` and `.photo` both weigh 0,1,0 and the later one won).

   AND IT HAS TO BE AN EXCLUSION HERE RATHER THAN A `z-index:0` ON THE LAYER, which is the part worth
   writing down: a z-index on the layer would have looked like a fix on five shells and left the invitation
   band blank, because on the other six the layer is never matched by anything and needs no defence. Any
   guard written against another shell passes with this defect fully in place. */
.home-widget--shell-join > *:not(.home-widget__bg):not(.home-widget__scrim):not(.home-widget__seam),
.home-widget--shell-join > *:not(.home-widget__bg):not(.home-widget__scrim):not(.home-widget__seam) > *{
  position:relative;
  z-index:1;
}

/* ---------------------------------------------------------------------------------------------------------
   IB PHASE 1 - THE PICTURE BEHIND A CARD (migration 067, docs/image-background-plan.md section 2.1 and
   section 4, gate section 9 answered on the recommendation).

   TWO REAL CHILD ELEMENTS, AND NEITHER COULD HAVE BEEN A `background` OR A PSEUDO-ELEMENT. All seven shells
   already own `background` - `--shell-join` stacks THREE of them, and `--content-block` pairs one with a
   radial wash - which is the same wall migration 063's chequered frame hit and the reason it became a
   `border-image`. Both pseudo-elements are taken too: `--shell-join::before` is its inset brass frame,
   `::after` WAS its circle motif until SF Phase 6 step 4 retired it into `CardArt`, `--shell-board::before`
   is its inset panel and `--content-block::after` is
   its brass wash. And a `filter` on the article FILTERS THE TEXT: `grayscale(1) blur(2px)` on the card greys
   and blurs every word inside it. A real child at `inset:0` is the only arrangement in which a blur blurs the
   picture and not the sentence, and the only one that composes with a shell already painting three gradients.

   NO `z-index` ON ANY OF THE THREE LAYERS, deliberately. Paint order already does it: the layer and the
   scrim are the article's first two children and the content follows, so the picture is under the tint and
   the tint is under the words. A z-index here would be a second, weaker statement of the same fact - and it
   is exactly the wrong cure for the one shell that breaks this, which is the `> *` exclusion up in the join
   shell's own rule rather than anything on this element.

   THE CLIP IS `border-radius:inherit` AND NOT `overflow:hidden` ON THE CARD, which is worth one sentence
   because the obvious version is wrong here. `overflow` on the article would be a change to seven shells, and
   the chequer frame is a `border-image` on the border area that `overflow` does not clip anyway. Inheriting
   the radius gets both cases right for free: a hairline-framed card keeps `--r` and a chequered one already
   states `border-radius:0`, so the layer squares its own corners exactly where the frame squares the card's.
   --------------------------------------------------------------------------------------------------------- */

/* ONE DECLARATION, AND IT IS WHY THIS CLASS EXISTS. Only four of the seven shells establish a containing
   block today (`--content-block`, `--shell-lift`, `--shell-board` and `--shell-join`); on `--surface`,
   `--bare` and `--shell-heritage` a layer at `inset:0` would position itself against the wrapper, the grid
   or the page and paint a picture across other cards. Stating it on a class the card wears ONLY while it has
   a picture is what keeps this from being a change to every card on every board. */
.home-widget--has-bg{
  position:relative;
  isolation:isolate;
}

/* IB PHASE 4b - AND THE CARD'S OWN CONTENT HAS TO BE LIFTED ABOVE THE SCRIM, WHICH PHASE 1 GOT WRONG ON FOUR
   SHELLS OUT OF SEVEN. Its comment beside the two layers reads "the picture, the scrim over it, and the
   content over both, with no z-index on any of the three" - and that is not what painting order does. A
   positioned box with `z-index:auto` paints in step 8 of its stacking context; in-flow inline content paints
   in step 7. So the scrim, an absolutely positioned SIBLING of the blocks, paints OVER the words.

   Reported from the running site: "when I set an image as a background on the event card, the content of the
   card is missing... it's like the scrim is lightening the content as much as the background." It was, and
   the events card is `--surface`.

   IT LOOKED CORRECT BECAUSE OF A COINCIDENCE. `--content-block`, `--shell-board` and `--shell-join` each
   already state `> * { position:relative; z-index:1 }` for their own decorative pseudo-elements - the very
   rule section 11.1 had to exclude the two layers FROM - so on those three the content was lifted for a
   reason that has nothing to do with pictures, and the default shell is one of them. The other four lift
   nothing and had no reason to.

   KEYED ON `--has-bg` AND NOT PER SHELL, so an eighth shell inherits the fix instead of inheriting the
   defect, and a card with no picture keeps the stacking it has always had. Same exclusions as every other
   rule that matches a card's anonymous children, for the same reason: `position:relative` on the layer
   collapses it to a zero-height in-flow span and the picture vanishes. See CardBackgroundLayerTests.

   AND IT IS NO LONGER WHAT KEEPS THE CONTENT ON TOP. It could not be: this rule lifts the card's own
   CHILDREN, and on `--shell-lift`, `--shell-heritage` and `--shell-join` a rich block the author never gave a
   layout to is `display:contents`, where `position` and `z-index` are ignored. Reported from the running site
   after Phase 4 shipped: "render bug with the history card when I use a background image. our history, the
   alumni and the button do not render unless I remove the background." Three of that card's five blocks were
   invisible and two were fine, and the two that were fine are the two carrying a `spaceAbove`, which keeps
   the wrapper and therefore keeps the lift. A second level (`> * > *`) was tried, is what the join shell's
   own rule does, and STILL leaves a block whose stored HTML is plain text under the scrim, because a bare
   text node has no element to lift at all.

   What holds it now is the layers going BEHIND the content instead (`isolation:isolate` here plus
   `z-index:-1` on the two layers below), which needs to lift nothing and therefore cannot miss anything.
   THIS RULE IS KEPT AS IT SHIPPED rather than deleted: it decides where a card's content sits against the
   decorative pseudo-elements of the shells that have them, which is a question the layers' stacking does not
   answer. It is no longer load-bearing for the picture, and a mutation that removes it is expected to
   survive. */
.home-widget--has-bg > *:not(.home-widget__bg):not(.home-widget__scrim):not(.home-widget__seam){
  position:relative;
  z-index:1;
}

.home-widget__bg,
.home-widget__scrim{
  position:absolute;
  inset:0;
  border-radius:inherit;
  pointer-events:none;
  z-index:-1;
}

/* The fit is a CLASS and the URL is inline, which is this project's line between the two: a vocabulary's
   lengths belong in the stylesheet where a designer can see them, and only what is genuinely per-instance
   and has no name rides on the element. `background-size` and `background-repeat` travel TOGETHER because
   "tile" is both of them at once - split into two settings, a card could be asked to repeat at cover size. */
.home-widget__bg{
  background-repeat:no-repeat;
}

.home-widget__bg--fit-cover{
  background-size:cover;
}

.home-widget__bg--fit-contain{
  background-size:contain;
}

.home-widget__bg--fit-tile{
  background-size:auto;
  background-repeat:repeat;
}

/* ---------------------------------------------------------------------------------------------------------
   SF PHASE 6 STEP 4 - THE DRAWN GROUND (migration 073, docs/sample-front-end-plan.md section 19 and section
   20, gate LOCKED all nine on the recommendation, build record section 23).

   THE SAME ELEMENT AS THE PICTURE, WHICH IS GATE Q1 ANSWERED (a). A drawn ground is `background-image:
   <gradients>` where a photograph is `background-image: url(...)` - the same property on the same box - so
   reusing `.home-widget__bg` costs NO NEW SELECTOR anywhere: the `--has-bg` containing block and its
   `isolation:isolate`, the `border-radius:inherit` clip, the `--shell-board` inset, the scrim at `inset:0`
   and the paint order all apply unchanged.

   A NEW LAYER CLASS WOULD HAVE HAD TO BE ADDED IN EIGHT PLACES, AND THE ONE SHELL IT IS FOR IS THE ONE WHERE
   MISSING IT IS FATAL. `:not(.home-widget__bg):not(.home-widget__scrim):not(.home-widget__seam)` is stated
   eight times in this file, and `--shell-join` - card 6's shell, the shell this step exists for - is one of
   them: its rule sets `position:relative; z-index:1` on every child that is not one of those three, and
   `position:relative` on an absolutely positioned layer does not restack it, IT COLLAPSES IT TO A
   ZERO-HEIGHT IN-FLOW SPAN. The drawing would not move; it would VANISH, on the one card that wants it, and
   every guard written against any other shell would pass with the defect fully in place.

   AND THE STRENGTH IS AN OPACITY, WHICH IS THE PHASE'S WHOLE FINDING WRITTEN AS ONE DECLARATION. A
   photograph's extremes arrive from a file and can only be measured; a drawing's extremes are the colours it
   is painted in, and one of those is the ground it sits on - so the cost of a drawn ground is BOUNDED by a
   number an author sets rather than DISCOVERED. The same circle costs 0 scrim on all eleven palettes at 12%
   and swings 0 to 54 at full brass. Every drawing below therefore states its ink at FULL alpha for its
   strongest mark and uses `color-mix` for the fainter ones RELATIVE to it, so this one number scales the lot
   and `CardArt.Extremes` can derive the floor from the ink alone without parsing a gradient.

   THE COLOURS ARE IN C# TOO, IN `CardArt`, AND `CardArtStylesheetTests` PARSES THIS BLOCK TO KEEP THE TWO
   HONEST - the arrangement `WidgetShellGround` already has for the seven shells' grounds. They have to be in
   C# because the floor is arithmetic; they stay here as well because a design value written only in C# is a
   second stylesheet no designer can see.
   --------------------------------------------------------------------------------------------------------- */
.home-widget__bg--art{
  opacity:var(--card-art-strength, .12);

  /* STATED HERE AND NOT INHERITED FROM `.home-widget__bg`, because the drawing chips in the widget dialog
     wear these `--art*` classes WITHOUT that one - the swatch is the real drawing at a `transform` scale, and
     it is not a positioned layer. Left to `.home-widget__bg` the four single-gradient drawings would tile
     across every chip. The pavement's own rule turns it back on, and wins on source order at an equal 0,1,0. */
  background-repeat:no-repeat;
}

/* THE PLAIN ONE - "drawn but not figurative" (gate Q4), and the entry that measures at floor 0 by
   construction on every palette: one colour, fading to nothing, so its luminance range is the ground's own
   plus a low-alpha wash of a single ink. */
.home-widget__bg--art-wash{
  background-image:linear-gradient(120deg, var(--brass-soft), transparent 70%);
}

/* THE TESSELLATED PAVEMENT, AND IT IS THE ONE ENTRY DRAWN IN LITERALS RATHER THAN TOKENS. Gate option (d)
   was a themed chequer in `--ink` and `--parchment`, and section 19.3 killed it: those two are only
   opposites on a LIGHT palette, and on Midnight, Evergreen, Garnet and Onyx they are two near-identical
   near-blacks (1.08:1, 1.12:1, 1.08:1, 1.08:1). The chequer there is not dim - IT IS NOT THERE - and its
   floor is 0 for the honest reason that there is nothing to protect the text from. A chequer IS its internal
   contrast, and no token pair carries an internal-contrast guarantee.

   Fixed instead, it is a pavement on all eleven and it costs 77% on card 6's shell - twenty points more than
   any photograph on the board. That is payable only because a block carrying its own `ContentBlockLayout.Fill`
   takes the same 77 to zero (backlog item 17): this is a drawing to put words BESIDE, not ON. */
.home-widget__bg--art-pavement{
  background-color:#FFFFFF;
  background-image:
    linear-gradient(45deg, #000000 25%, transparent 25% 75%, #000000 75%),
    linear-gradient(45deg, #000000 25%, transparent 25% 75%, #000000 75%);
  background-size:96px 96px;
  background-position:0 0, 48px 48px;
  background-repeat:repeat;
}

/* TWO CONCENTRIC SEMICIRCULAR ARCHES, standing on the card's own bottom edge - which is what `at 50% 100%`
   buys: the lower half of each circle is simply outside the box, so the arch needs no legs and no second
   layer to be cut off by. The inner one is at 45% of the ink for depth. */
.home-widget__bg--art-arch{
  background-image:
    radial-gradient(circle 168px at 50% 100%,
      transparent 0 164px, var(--brass-soft) 164px 167px, transparent 167px),
    radial-gradient(circle 132px at 50% 100%,
      transparent 0 128px,
      color-mix(in srgb, var(--brass-soft) 45%, transparent) 128px 130px,
      transparent 130px);
}

/* THE CIRCLE THE INVITATION BAND HAS PAINTED SINCE BEFORE ANY OF THIS, and it MUST be in the vocabulary or
   the vocabulary could not describe what card 6 looks like today - which would make this a redesign rather
   than a control (SCC Phase 2's rule: one entry per look the board actually has).

   THE RADII ARE THE RETIRED `--shell-join::after`'s, ARITHMETIC AND NOT TASTE. It was a 440px box at
   `right:-120px`, so its left edge sat at `100% - 320px` and its centre at `100% - 100px`, radius 220. Its
   2px border was `--brass-soft` at 12% (the full ink, at the strength), its `inset 0 0 0 52px` shadow was 4%
   over its `inset 0 0 0 110px` shadow at 3% - which composites to 6.88%, or 58% of the ink - and the 110px
   band alone was 3%, or 25% of it. So the three relative alphas below reproduce the shipped drawing exactly
   at a strength of 12, and scale together at any other. */
.home-widget__bg--art-circle{
  background-image:radial-gradient(circle 220px at calc(100% - 100px) 50%,
    transparent 0 108px,
    color-mix(in srgb, var(--brass-soft) 25%, transparent) 108px 166px,
    color-mix(in srgb, var(--brass-soft) 58%, transparent) 166px 218px,
    var(--brass-soft) 218px 220px,
    transparent 220px);
}

/* THE PENNANT the lodge's device is drawn on, as a swallowtail: the region below the top edge and above a
   90-degree notch rising from the bottom centre. A `conic-gradient` is the only gradient that can state that
   - `from -45deg` puts the transparent wedge symmetrically about straight up, which IS the notch, and
   everything outside it is the pennant. */
.home-widget__bg--art-banner{
  background-image:conic-gradient(from -45deg at 50% 100%,
    transparent 0 90deg, var(--brass-soft) 90deg);

  /* A PROPORTION AND NOT A LENGTH, WHICH IS THE ONE DRAWING THAT NEEDED IT. The circle, the arch and the
     pavement are all absolute because their scale IS the design - a 220px ring and a 96px floor tile look
     wrong at any other size. A pennant has no such size: it reads as a pennant at every scale, and stating
     it absolutely made it a sliver in the 68px swatch the drawing chips paint with these same rules. */
  background-size:38% 82%;
  background-position:50% 0;
}

/* THE SCRIM IS THE ONLY ONE OF THE SIX SETTINGS THAT PROMISES THE TEXT ANYTHING (plan section 4). Greyscale,
   blur and duotone change the picture and guarantee nothing - a mid-grey photograph is mid-grey - while a
   tint at a known alpha over a known luminance has a composited ground that can be computed, which is what
   lets the contrast sweep start measuring these cards instead of excusing them.

   AND IT IS TINTED IN A THEME TOKEN RATHER THAN A HEX, so it follows an eleven-palette switch. A hex resolved
   server-side would be frozen at whichever palette was active when the page rendered, which is the defect
   class a fixed surface meeting a theme text token always is. The alpha is the only part the card carries. */
.home-widget__scrim{
  background:color-mix(in srgb, var(--card-scrim-tint, var(--ink)) calc(var(--card-scrim, 1) * 100%), transparent);
}

/* THE TINT IS THE SHELL'S OWN GROUND, because at full strength the scrim has to BE the card the author had
   before - that is what makes 100 the provably safe floor rather than a guessed one. Each of these is
   qualified with `--has-bg` (0,2,0) so it cannot reach a card with no picture: a custom property nobody reads
   is harmless, and a rule that only exists where it is read is easier to trust than one that does not.

   IB PHASE 2 CORRECTED THREE OF THE SEVEN, AND FOUND THEM BY LOOKING AT A TABLE THAT ALREADY EXISTED.
   Phase 1 wrote these rules from scratch; `[data-widget-shell]` further down this file has said what each
   shell's ground and text are since SCC Phase 7a, with two readers (the widget dialog's block editors and
   the Section style chips). Comparing them showed `--heritage` tinted `--ink` where its ground is
   `--ink-deep`, `--shell-join` tinted `--claret` where the band's ground is a hardcoded gradient that is a
   different colour, and `--shell-board` inheriting `--ink` from `--heritage` where its text sits on a LIGHT
   inset panel - near-black behind near-black, which is the one of the three that was a contrast defect
   rather than a fidelity one.

   The pairs are now stated in C# too, as `WidgetShellGround`, because the scrim's FLOOR is derived from them
   and the derivation runs on the server. That makes three readers of one table, and `WidgetShellGroundTests`
   parses this file and fails when either CSS statement stops agreeing with it. */
.home-widget--has-bg.home-widget--content-block{
  --card-scrim-tint:var(--ink);
}

.home-widget--has-bg.home-widget--heritage{
  --card-scrim-tint:var(--ink-deep);
}

.home-widget--has-bg.home-widget--surface{
  --card-scrim-tint:var(--surface);
}

/* The band's ground is `linear-gradient(145deg,#641324,#5a1020,#430a18)` - authored in hex and not themed at
   all - so the honest flat stand-in is its middle stop and not `--claret`, which is #6F2435 and would repaint
   the band at full strength. The same literal, for the same reason, as the chip's. */
.home-widget--has-bg.home-widget--shell-join{
  --card-scrim-tint:#5A1020;
}

.home-widget--has-bg.home-widget--bare{
  --card-scrim-tint:var(--parchment);
}

/* AFTER `--heritage` AND NOT BEFORE IT: `board` carries both classes (`ShellClass` composes it as
   `home-widget--heritage home-widget--shell-board`) and both rules weigh 0,2,0, so source order is what
   decides. The blocks sit on the INSET PANEL and are painted `--slate` by `.home-widget--shell-board > *`,
   so the surface the text actually lands on is light. */
.home-widget--has-bg.home-widget--shell-board{
  --card-scrim-tint:var(--surface);
}

/* AND ON THAT SHELL THE LAYER IS INSET TO THE PANEL, which is what makes the 100%-is-the-old-card proof true
   here rather than nearly true. The panel is an `inset:34px` pseudo-element with a 34px radius; a picture at
   `inset:0` would paint over the 34px of ink-deep that frames it and a full-strength scrim would flatten the
   card into a pale rectangle. Only the two layers move - the panel, the text and the padding are untouched. */
.home-widget--shell-board .home-widget__bg,
.home-widget--shell-board .home-widget__scrim{
  inset:34px;
  border-radius:34px;
}

/* AND THIS SHELL IS THE ONE EXCEPTION THE LAYERS' OWN `z-index:-1` NEEDS, because it is the one shell whose
   decoration is a POSITIONED pseudo-element rather than a background. The panel above is
   `position:absolute` at `z-index:auto`, so it paints in the same step as any other positioned sibling and
   it beat the layers the moment they went negative: measured under the first version of this fix, a board
   card with a photograph rendered as the pale panel and no picture at all, on every other shell correct.

   `-2` puts the panel behind the picture and leaves both in front of the card's own background, which is the
   order the 34px inset was chosen for: the picture sits INSIDE the panel that frames it. Scoped to
   `--has-bg`, so a board card with no picture keeps the stacking it has always had and this rule cannot
   change a single card that has not been given one. */
.home-widget--has-bg.home-widget--shell-board::before{
  z-index:-2;
}

/* ---------------------------------------------------------------------------------------------------------
   SF PHASE 1 - THE SEAM: ONE PICTURE ACROSS THE BOUNDARY BETWEEN TWO FULL-WINDOW CARDS
   (docs/sample-front-end-plan.md section 6, gate answered 2026-08-23 seven of seven, migration 071).

   TWO LAYERS, ONE PER CARD, CONGRUENT - AND CONGRUENCE IS THE WHOLE MECHANISM. Each layer is
   `2 x depth` tall and hangs half of itself outside its own card: the upper card's below its foot, the lower
   card's above its head. So the two boxes are THE SAME BOX ON THE PAGE, which is why `background-size:cover`
   resolves identically in both and the two visible halves join exactly. Measured in Chromium 149 and
   WebKit 605.1.15 with the cards deliberately unequal (500px and 340px): both layers reported
   `top 300, bottom 700, height 400, width 600`, in both engines, at every combination of mask and clip.

   THERE IS NO SCROLL TIMELINE, NO SCRIPT AND NO BOARD-LEVEL BOX, and that is gate Q6 rather than a
   limitation. Continuity is a property of the construction, so nothing has to be measured on resize, nothing
   silently does not exist with scripting off (the trap `.board-snaps` already is on this board), there is no
   second code path for `prefers-reduced-motion`, and the effect reads AT REST - which is what the board's
   stored `y mandatory` snapping leaves a reader doing. Scroll-driven CSS is available in both engines; it was
   measured for the gate and it is not needed.

   AND THE BAND SITS BETWEEN THE CARD'S PICTURE AND THE CARD'S SCRIM, WHICH IS WHAT MAKES THE CONTRAST
   GUARANTEE REACH IT. All three layers are `position:absolute; z-index:-1`, so DOM order is paint order:
   `__bg`, then `__seam`, then `__scrim` at `inset:0` over both. One scrim tints two pictures, and its floor
   is the pair's rather than the card's (ContentBlockBackgroundResolver, gate Q7). A band painted ABOVE the
   scrim would be an untinted photograph behind the card's own words with nothing measuring it.
   --------------------------------------------------------------------------------------------------------- */

/* ONE DECLARATION, AND section 11.1's OWN COMMENT ARGUES AGAINST IT ON TWO GROUNDS THAT BOTH TURN OUT NOT TO
   REACH HERE. It says the layers clip with `border-radius:inherit` and NOT `overflow:hidden`, because
   "`overflow` on the article would be a change to seven shells, and the chequer frame is a `border-image` on
   the border area that `overflow` does not clip anyway". Both halves are still true. Neither objection
   applies: this declaration rides a class a card wears ONLY while a seam touches it, so it changes nothing
   that exists today, and the frame is untouched because `overflow` never clipped it.

   IT IS THE CORRECTNESS AND NOT A TIDY-UP. Without it each card's layer paints a full depth INTO its
   neighbour. Unmasked that is invisible for a bad reason - the neighbour's own layer paints the same pixels,
   so two wrongs look like one right - and masked, the lower card's opaque half covers the upper card's ground
   for a whole depth above the seam. This is section 2.3's cross-card bleed in a new form: the thing
   `isolation:isolate` was written to prevent.

   THREE OF THE SEVEN SHELLS STATE `overflow:hidden` ALREADY and five of the six dev cards wear one of them,
   so on that board this is a no-op on five cards and the fix on one - which is exactly the shape of thing
   that ships looking fine and is wrong on the next board somebody builds. */
.home-widget--seamed{
  overflow:hidden;
}

.home-widget__seam{
  position:absolute;
  left:0;
  right:0;
  /* TWICE THE DEPTH, BECAUSE THE DEPTH IS PER SIDE. `--seam-depth` is what the author chose, in `svh`, and
     the band reaches that far into each of the two cards. */
  height:calc(var(--seam-depth, 20svh) * 2);
  z-index:-1;
  pointer-events:none;
  background-repeat:no-repeat;
  /* `cover` AND NOT A FIT VOCABULARY. The card offers cover/contain/tile because a card is a shape an author
     is composing inside; a seam's job is that ONE picture reads as one picture across two boxes, and only
     cover makes two congruent boxes render the same pixels. `contain` would letterbox each half separately
     and `tile` would draw a texture at its intrinsic size, breaking the join in both cases. */
  background-size:cover;
  background-position:center center;
}

/* HALF THE BOARD'S GAP IS IN EACH OFFSET, AND THE E2E NET FOUND THAT THE HARD WAY.
   The gate's rig measured congruence with the gap at ZERO -- "exactly as the dev board is set" -- and never
   asked what a gutter does. It does this: the cards are `--board-gap` apart, so `bottom:-depth` on the upper
   card's layer and `top:-depth` on the lower card's put the two boxes A WHOLE GAP out of step. Measured on the
   default board (gap 24px): tops of 4174.6 and 4198.6, and the two halves of one photograph 24px misaligned.

   Centring both boxes on the GUTTER'S MIDLINE fixes it at every gap and keeps the arithmetic symmetric: each
   offset is `-(depth + gap/2)`, so both layers span from `gap/2 - depth` below the upper card's foot to
   `gap/2 + depth` past it, which is the same rectangle. At gap 0 it reduces to exactly what the rig measured.

   WHAT IT DOES NOT AND CANNOT FIX is that the gutter itself is still there. With a gap the two cards do not
   touch, so the mask's opaque middle is cut by `--board-gap` of page: the effect becomes ONE PICTURE BEHIND
   TWO PANELS rather than a boundary that stops existing. That is not a defect to hide, it is what the board's
   own geometry says -- an author who has chosen 24px gutters has chosen two objects rather than one surface --
   and it degrades continuously, which is why the seam is not REFUSED on a gapped board. The strip says so. */

/* THE MASK IS WHAT TURNS A STRIPE INTO A TRANSITION. Without it the picture is already continuous across the
   seam - that is the congruence - but its two FAR edges are hard lines, so the effect reads as a band laid
   over the board rather than as one card becoming the next. Each half ramps from its own card's ground at the
   outer edge to full strength at the seam, so the picture arrives out of one card and leaves into the other.

   `-webkit-mask-image` IS KEPT BESIDE THE UNPREFIXED PROPERTY. Safari 26 supports `mask-image` unprefixed
   and older WebKit does not; the pair costs one line and is what the gate's rig was measured with. */
.home-widget__seam--below-this-card{
  bottom:calc((var(--seam-depth, 20svh) + var(--board-gap, 0px) / 2) * -1);
  -webkit-mask-image:linear-gradient(to bottom, transparent 0, #000 50%);
          mask-image:linear-gradient(to bottom, transparent 0, #000 50%);
}

.home-widget__seam--above-this-card{
  top:calc((var(--seam-depth, 20svh) + var(--board-gap, 0px) / 2) * -1);
  -webkit-mask-image:linear-gradient(to bottom, #000 50%, transparent 100%);
          mask-image:linear-gradient(to bottom, #000 50%, transparent 100%);
}

/* BACKLOG ITEM 36 - A LAYER SITS AGAINST THE PADDING BOX, AND THE TWO OFFSETS ABOVE HAVE NO TERM FOR A
   BORDER. Found 2026-09-12 by item 22's own verification, fixed the same day. Measured on the running dev
   board at 1280x900 in Chromium AND WebKit, both cards `--surface` with a 1px border and the board's gap at
   0, so none of the gutter arithmetic above is involved:

     layer            card top   card bottom   layer top
     `--below-this-card`   900          1725      1544.0
     `--above-this-card`  1725          2550      1546.0     <- the same box, 2px apart

   An absolutely positioned box is placed against its containing block's PADDING box. So `bottom:-180` on the
   upper card starts from 1724 and `top:-180` on the lower card starts from 1726, and `(depth + gap/2)` is the
   whole of both offsets. SF Phase 1's congruence rig used UNBORDERED cards, which is why it measured exact,
   and the E2E seam test builds its own two cards carrying no border either - so nothing could see this.

   AND THE 2px IS THE SMALLER HALF OF IT. `.home-widget--seamed` states `overflow:hidden`, and overflow clips
   to the padding box, so the border area is the one strip of the card the layer can never paint: the two
   borders draw a LINE STRAIGHT ACROSS THE PICTURE at exactly the boundary the seam exists to erase. Made
   loud (`border-color:#f00`) it is unmistakable, and with a chequer frame on both cards it is ~28px of
   mosaic through the middle of the photograph.

   SO THE BORDER GOES, RATHER THAN THE OFFSETS LEARNING TO SUBTRACT IT. `calc()` cannot read
   `border-bottom-width`, and the alternative - the render telling the stylesheet how thick its own borders
   are - would put a stylesheet fact in C# for the seven shells AND the two frames. Zeroing it instead is
   also the only one of the two that fixes the line. Each rule reads ITS OWN card's edge and neither needs to
   know the other's: both layers then target the boundary between the two BORDER boxes, so they are congruent
   at any pair of border widths, which is what section 36.3 thought was unreachable.

   NOTHING ELSE MOVES. `*{box-sizing:border-box}`, so the card's border box is the same size afterwards -
   measured, card tops and bottoms identical in both engines, and the two layers land on [1545, 1905]
   together. A `--bare` card already states `border:0` and this is a no-op on it.

   THE SEAM WINS OVER A FRAME ON THE EDGE IT CROSSES, and that is the decision section 36.3 asked for. A
   frame says "this card is a closed object" and a seam says "these two cards are one surface"; they can only
   both be honoured somewhere other than this edge. `0,3,0` here beats `.home-widget.home-widget--frame-*`'s
   `0,2,0` by SPECIFICITY and not by position, which is the fragility 7d's own note records being bitten by. */
.home-widget.home-widget--seamed:has(> .home-widget__seam--below-this-card){
  border-bottom-width:0;
}

.home-widget.home-widget--seamed:has(> .home-widget__seam--above-this-card){
  border-top-width:0;
}

/* ---------------------------------------------------------------------------------------------------------
   ITEM 22 - HOW A FULL-WINDOW CARD ARRIVES (docs/sequence-transitions-plan.md, gate answered 2026-09-12 all
   seven on the recommendation, migration 080, build record docs/backlog-head.md section 22.7).

   Asked for as "configurable animated transitions between the pages in sequence mode... I want it to animate
   when I press up/down or scroll", and the measurement inverted the cost it was booked with: THERE IS NO
   SCRIPT HERE AND NO KEYBOARD HANDLER. Scroll-driven CSS is available in full in both engines, and on a
   board whose snapping is `strict` one press of the down key already pages exactly one card - so the browser
   scrolls, the snap engine pages, and the effect IS the scroll's own progress. A reader who pages fast plays
   it fast. `onscrollend` does not exist in WebKit, which is the measured argument against the scripted
   design rather than a preference against scripts (gate Q1a, Q5a).

   `entry` IS THE WHOLE AUTHORING RULE, AND THE SPELLING AN AUTHOR REACHES FOR FIRST GETS IT WRONG. Progress
   read on the card actually on screen, at every snap rest, on the board's real geometry:

     animation-range        the on-screen card at rest
     `cover`, i.e. OMITTED  0.479   the card the reader is looking at sits permanently half-way through
     `contain`              0       the effect never applies to the card you are on
     `entry`                1       arrived - and every other card reads exactly 0 or 1
     `exit`                 0       neutral, which is what a DEPARTING card wants

   So `entry` is the only per-card range that is an endpoint at every rest, and `animation-timeline:view()`
   with no range at all leaves the whole board permanently mid-transition. The 0.479 is the MASTHEAD's, too
   (0.500 at a 0px bar, 0.455 at 150px), which is why the "one animation, neutral in the middle" spelling is
   refused: it would hard-code 47.9% of a bar that can change.

   THE ARRIVING CARD ONLY (gate Q7a). A card carrying `arrive` over `entry` AND `depart` over `exit` reads 1
   on the on-screen card at every rest, which looks correct and is not: the later animation in the list wins
   the property outright, so `arrive` never paints at all. It ships looking right because `depart`'s neutral
   value is the same as `arrive`'s finished value. And the departing card leaving the window is already the
   motion.

   THE WHOLE THING IS INSIDE `prefers-reduced-motion: no-preference`, AND THE SITE'S BLANKET RULE CANNOT DO
   IT. The rule at the foot of this file is `*{transition:none!important;scroll-behavior:auto!important}`. It
   does apply - `scroll-behavior` computes to `auto` under reduce in both engines - but `transition:none`
   says nothing about `animation`, and a `view()` animation tracks the scroll IDENTICALLY under reduce: same
   progression, same rests, same mid-animation frame counts. Measured with this block's own selectors: under
   reduce the card and its contents report NO ANIMATION and `transform:none` in both engines, and without the
   query they animate. `scroll-behavior:auto` does not make an input-driven scroll instant either - the same
   ArrowDown still produced 17 distinct offsets - so this feature needs its own answer and this is it.
   --------------------------------------------------------------------------------------------------------- */

/* THE KEYFRAMES. Five names, and which of the two tokens below carries a given one is decided in C#
   (HomeBoardLayout.CardArrivalKeyframes / ContentArrivalKeyframes) so that this file holds no second copy of
   the vocabulary. Every name here is asserted to exist by BoardArrivalStylesheetTests, which reads the two
   switches and looks for each literal in this block - the arrangement CardArt already uses.

   `from` IS THE STATE A CARD HAS NOT ARRIVED IN and `to` IS NEUTRAL, because `entry` runs 0 to 1 as the card
   comes into the window. Every one of the five states its `to` explicitly rather than leaving the property
   to its own initial value: a keyframe at 100% is what makes a broken `animation-range` visible on the
   RESTING card - which is the state a guard can read - instead of only in a frame mid-scroll. */
@keyframes board-arrive-fade{ from{opacity:0} to{opacity:1} }
@keyframes board-arrive-dim{ from{filter:brightness(.28)} to{filter:brightness(1)} }
@keyframes board-arrive-blur{ from{filter:blur(9px)} to{filter:blur(0)} }
@keyframes board-arrive-slide{ from{transform:translateY(52px)} to{transform:translateY(0)} }
@keyframes board-arrive-zoom{ from{transform:scale(1.09)} to{transform:scale(1)} }

@media (prefers-reduced-motion: no-preference){

  /* THE TIMELINE IS THE CARD'S, AND IT IS NAMED FOR A MEASURED REASON RATHER THAN A TIDY ONE. The three
     effects that ride the card could have used a bare `view()`; the two that ride its CONTENTS cannot, and
     naming it once is what stops this block having two ideas of when a card arrives.

     `animation-timeline:view()` on a content block resolves against THAT BLOCK's own box. Measured at an
     offset where the card is 60.5% of the way into its entry, every one of its three blocks read 1.000 and
     carried an identity transform - a small block well inside the window has already finished entering,
     so the per-child spelling does not merely go out of step, IT IS OVER BEFORE THE CARD ARRIVES. With the
     name, all three read 0.605 and carried the same `translateY(20.5px)`, in Chromium and in WebKit.

     A NAME DECLARED HERE IS REFERENCEABLE BY THIS ELEMENT AND BY ITS DESCENDANTS, measured rather than
     assumed, in both engines - so no `timeline-scope` is needed anywhere. */
  .home-widget-grid > .home-widget--mode-window > .home-widget{
    view-timeline-name:--card-arrival;
  }

  /* THE THREE EFFECTS THAT DO NOT MOVE THE CARD'S BOX (fade, dim, blur) RIDE THE CARD, and the picture, the
     scrim and the seam band ride with them - which is the look rather than a side effect: "the arriving card
     rises out of the ground it is painted on", and a dim keeps a photograph's edges the whole way where a
     fade does not.

     `both` IS LOAD-BEARING. Without it a card past its entry range would paint its `from` state, so every
     card the reader has already scrolled past would sit at opacity 0. */
  .home-widget-grid > .home-widget--mode-window > .home-widget{
    animation:var(--board-card-arrival, none) linear both;
    animation-timeline:--card-arrival;
    animation-range:entry;
  }

  /* AND THE TWO THAT DO MOVE (slide, zoom) RIDE THE CARD'S CONTENTS, WHICH IS THIS FEATURE'S CENTRAL
     MEASUREMENT (gate Q2b, Q4a). A transform on the CARD does two things no test of its styles can see:

       what moves                       the lower card's seam layer      consequence
       nothing                          400/800 h400 w1280               congruent, the picture joins
       the CARD, translateY(-60px)      340/740 h400 w1280               the picture tears by exactly 60px
       the CARD, scale(0.92)            440/808 h368 w1178               broken in position AND in size
       the CONTENTS, either             400/800 h400 w1280               untouched, byte for byte

     `background-size` still computes to `cover` in every row, which is why this cannot be caught by reading
     a style: the layer resolves `cover` correctly against a box that is being painted somewhere else. And a
     transform on a card moves the snap position the board is snapping to - asking a mandatory container for
     offset 500 rests at 600 untransformed and at 540 with the card translated 60px up. TRANSFORMS ARE
     LAYOUT-NEUTRAL; THEY ARE NOT SNAP-NEUTRAL.

     The obvious objection to the fix does not hold: a view timeline is computed from its subject's LAYOUT
     box and not from where the pixels land, so a subject that moves itself does not feed back on its own
     input - 0 / 0.375 / 0.75 / 1 at the same four offsets whether it translates or not, in both engines.

     THREE SELECTORS, AND THE THIRD IS THE ONE THAT MAKES IT TOTAL. "The card's contents" is a stack of
     SIBLINGS here and not one wrapper element, so the animation lands on each of them - and on three shells
     of seven a rich block the author has not styled is `display:contents`, which generates no box at all. A
     one-level spelling was measured doing exactly what that predicts: the dissolved wrapper carried the
     animation at 0.605 and `transform:none`, and its paragraph carried NO ANIMATION, in both engines. So on
     the heritage, lift and join shells one block would have stood still while every other block moved.

     THE SPLIT IS THE CODEBASE'S OWN MARKER AND NOT A LIST OF SHELLS. `--laid-out` is exactly "this wrapper
     carries something of the block's own definition"; without it "the authored markup IS the content", which
     is the join shell's own words for why it dissolves. So a plain rich block's authored elements are the
     box on EVERY shell - identical to moving the wrapper where the wrapper is a box, and the only thing that
     works where it is not - and a laid-out one keeps its wrapper everywhere. Nothing here names a shell, so
     an eighth shell that dissolves inherits the answer instead of inheriting a hole.

     WHAT IT STILL CANNOT REACH, said plainly: a rich block whose stored HTML is a bare text node has no
     element to move, and on a dissolving shell it is an anonymous box no selector can name. It is the same
     hole the content lift has (see `--has-bg` above), and it is a property of CSS rather than of this rule.
     It was found by a guard rather than argued about: the reduced-motion E2E case was red on its own control
     arm until its fixture stated its block as markup.

     ZOOM SCALES EACH BLOCK ABOUT ITS OWN CENTRE, because there is no group box to scale. Slide is identical
     to a group transform - a translate is position-independent - and zoom differs only in that the blocks do
     not drift towards each other as they settle, which reads as a zoom and cannot pull a card's stack
     apart. */
  .home-widget-grid > .home-widget--mode-window > .home-widget > *:not(.home-widget__bg):not(.home-widget__scrim):not(.home-widget__seam):not(.content-block-rich),
  .home-widget-grid > .home-widget--mode-window > .home-widget > .content-block-rich--laid-out,
  .home-widget-grid > .home-widget--mode-window > .home-widget > .content-block-rich:not(.content-block-rich--laid-out) > *{
    animation:var(--board-content-arrival, none) linear both;
    animation-timeline:--card-arrival;
    animation-range:entry;
  }
}

/* ---------------------------------------------------------------------------------------------------------
   SCC PHASE 7d - THE FRAME A CARD WEARS AROUND ITSELF (plan section 23.13 Q10, migration 063).

   A frame COMPOSES ONTO a shell rather than replacing one, so both classes ride the same article and these
   two rules have to beat whichever of the seven is underneath.

   THE DOUBLED CLASS IS WHAT MAKES THAT TRUE BY SPECIFICITY RATHER THAN BY POSITION. `.home-widget--surface`
   and `.home-widget--shell-join` each state a `border` at 0,1,0 and `.home-widget--bare` states `border:0`,
   and the join band's copy sits seven hundred lines BELOW this block. A single class here would win only for
   as long as nobody moved either rule - the fragility section 23.3 records and 7m was bitten by.
   `.home-widget.home-widget--frame-*` weighs 0,2,0 and does not care where it lives.

   The render sets `--frame-color` and `--frame-weight` only when the author has chosen them, so the
   fallbacks below are the shipped defaults and a card that has only been given a frame KIND still paints.
   --------------------------------------------------------------------------------------------------------- */

/* The hairline keeps the board's corner radius, because it is drawn with `border-color` and a border IS
   clipped by `border-radius`. Its colour follows a theme switch with no server involvement at all; the
   chequer's cannot, and the rule below says why. */
.home-widget.home-widget--frame-hairline{
  border:1px solid var(--frame-color, var(--brass));
}

/* THE CHEQUER, AND WHY EVERY NUMBER HERE IS 2.

   `border-image-slice` counts in the SOURCE's own units. The source (HomeWidgetPartialModel.ChequerSource) is
   a 12-unit square holding a 6x6 board of two-unit squares, so slicing at 4 cuts corner tiles of 2x2 squares
   and edge tiles of 2x2 squares, leaving a middle of 4 units. The browser scales every tile by
   `border-width / slice`, so each square lands at `--frame-weight / 2` pixels and the frame is
   `--frame-weight` thick.

   PHASE 8 MADE IT TWO SQUARES DEEP, AND THE SOURCE HAD TO GROW FOR IT. 7d sliced an 8-unit board at 2, which
   put exactly ONE square along each edge - and a single alternating row reads as a zebra crossing rather than
   the mosaic pavement it is meant to be. Reported from the running site. Slicing the OLD source at 4 would
   have left a middle of zero and drawn nothing along the edges at all, which is why 12 and not 8: the middle
   strip has to be a whole and EVEN number of squares or the tiling breaks its own alternation when it repeats.

   The frame's THICKNESS is unchanged - the card's proportions do not move, it gains a second row instead.

   The source is a small SVG in a `data:` URI and not a gradient, and that is not a preference. A gradient's
   intrinsic size IS the border-image area, so slicing one yields four quadrant-sized bars rather than
   squares; only a source with a small intrinsic size tiles. It also means the two colours are BAKED INTO the
   URI by the render, because a `data:` URI is not part of this document and cannot read a custom property.

   `round` rather than `repeat` because a chequer has to meet its own corners. `repeat` centres the tiling and
   clips both ends, so an edge that is not a whole number of tiles long starts and finishes mid-square.
   `round` scales the tile to fit a whole number, and across a card hundreds of pixels wide that scaling is a
   fraction of one percent.

   AND A CHEQUERED CARD SQUARES ITS OWN CORNERS. `border-image` is NOT clipped by `border-radius` - the one
   cost Q10 accepted in exchange for a frame that composes with all seven shells. Without the zero below, the
   frame would render square over a ground still rounded to `--r` and the shell's background would show
   through four corner slivers. The board's radius still governs every other card on the board. */
.home-widget.home-widget--frame-chequer{
  border-style:solid;
  border-width:var(--frame-weight, 8px);
  border-color:transparent;
  border-image-slice:4;
  border-image-repeat:round;
  border-radius:0;
}

.content-block{
  position:relative;
  background:var(--ink);
  color:var(--on-dark);
  overflow:hidden;
  padding:0;
}

.content-block::after{
  content:"";
  position:absolute;
  inset:0;
  background:radial-gradient(120% 90% at 80% -10%,color-mix(in srgb, var(--brass) 18%, transparent),transparent 55%);
  pointer-events:none;
}

.content-block-grid{
  position:relative;
  display:grid;
  grid-template-columns:1.15fr .85fr;
  gap:56px;
  align-items:stretch;
  padding:60px 0 68px;
}

.content-block-grid--single{
  grid-template-columns:1fr;
}

.content-block h1{
  font-size:clamp(2.6rem,5.4vw,4.5rem);
  letter-spacing:-.01em;
  font-weight:500;
  font-kerning:normal;
  font-variant-ligatures:common-ligatures contextual;
  text-rendering:optimizeLegibility;
  font-synthesis:none;
}

.content-block h1 b,
.content-block h1 strong{
  font-weight:700;
}

.content-block h1 *{
  font-kerning:inherit;
  font-variant-ligatures:inherit;
  text-rendering:inherit;
  font-synthesis:none;
}

.content-block h1 em{
  font-style:italic;
  color:var(--brass-soft);
  font-weight:500;
}

.content-block-lead{
  font-size:1.12rem;
  color:color-mix(in srgb, var(--on-dark) 82%, transparent);
  max-width:34ch;
  margin:18px 0 22px;
}

/* F5/B2: uniform wrapper for each rendered rich-text content block. No typographic opinion — inline styling
   baked into the block HTML drives size/colour/weight; this only supplies vertical rhythm. */
/* SCC Phase 2: scoped to .home-widget, not to .home-widget--content-block. These rules are the vertical
   rhythm of a rich block, which is a property of the BLOCK and not of the card it happens to sit in — but
   they were written when a content block could only ever be in one kind of card, so they took that card's
   class as their scope. With the shell now a stored value, the same block in a surface or heritage shell
   would silently lose its margins. Every rendering of this partial emits `home-widget` (the editor preview
   included, which wraps the same article in .home-widget-preview), so the match set is unchanged for the
   feature shell and the specificity is identical at 0,2,0. */
/* BT Phase 2: the rich block's single `all` part — the block's BASE type (gate Q4). Any run the toolbar has
   styled keeps what the toolbar gave it, because an inline style on a child beats a property inherited from
   its parent; that is not a workaround, it is the relationship the card's Align already has to a block's.
   Every declaration is a token with no fallback, so a block that has never met the panel computes exactly
   what it computes today. */
.home-widget .content-block-rich{
  margin:0 0 16px;
  font-family:var(--tp-all-font);
  font-size:var(--tp-all-size);
  font-weight:var(--tp-all-weight);
  font-style:var(--tp-all-italic);
  text-decoration-line:var(--tp-all-underline);
  text-transform:var(--tp-all-case);
  color:var(--tp-all-color);
  line-height:var(--tp-all-line);
}
/* SF PHASES 3+4, GATE Q7 — A TRAILING MARGIN UNDER A CARD'S LAST BLOCK DOES NOT RENDER, AND NOW THAT IS
   TRUE OF ALL EIGHT BLOCK KINDS RATHER THAN OF ONE (plan §14.7, §14.8, gate answers §15.3, record §16).

   THIS RULE ALREADY EXISTED FOR RICH TEXT ALONE, so the project had already decided the question and had
   only written the answer down once. What that cost is measured: `spaceBelow:160` on the hero's LAST block
   is the entire reason the strongest card on the board did not fit a 1440 laptop (912px in an 825px window)
   or a 1920 desktop (1092.9 in 1005), and the same number on card 2 is the only reason that card exceeded a
   phone screen (993.2 in 844). Both cards land on EXACTLY their floor with the trailing margin gone. **The
   two cards that busted their window are precisely the two whose last block is not a rich block** — a
   statGroup and an events list — which is what makes this a rule that was half-applied rather than a
   coincidence.

   AND IT BUYS NOTHING WHERE IT PAINTS. The hero's stat block is the last child of a `justify-content:center`
   column, so there is nothing beneath it to be spaced away from; what the 160 actually did was offset the
   composition 80px above centre — 35px of air above the content and 195px below it.

   `!important`, AND IT IS THE ONE THING IN THIS PASS THAT IS NOT A PREFERENCE. The Layout panel writes
   spacing as an INLINE declaration on the block's wrapper, and an inline declaration cannot be outweighed by
   an author rule at any specificity — this project has paid for that sentence twice already (backlog §11's
   block Width, and §33's own band measures). So the generalised rule alone would have fixed the STYLESHEET's
   trailing margins (a separator's 22px, a table's 18px, this rule's own 16px) and left the two stored 160s —
   the only ones that actually cost a card its window — painting exactly as before. Gate Q7 was asked as "it
   does change what a stored 160 means", and this is that answer stated where the cascade can carry it.

   `:last-child` AND NOT AN INDEX THE RENDER COMPUTES, for a reason worth keeping: a block can be hidden, and
   several kinds emit nothing at all when they hold nothing renderable (a button group with no complete
   buttons, a table with no rows, a stat group with no items). The DOM's own last child is the only predicate
   that is right in every one of those cases, and it costs nothing to ask. */
.home-widget .content-block-rich:last-child,
.home-widget .content-block-cta:last-child,
.home-widget .content-block-meta:last-child,
.home-widget .content-block-separator:last-child,
.home-widget .content-block-table:last-child,
.home-widget .content-block-links:last-child,
.home-widget .content-block-charity:last-child,
.home-widget .events:last-child{
  margin-bottom:0!important;
}

/* SF PHASES 3+4, GATE Q8 — ON THE THREE DISSOLVING SHELLS, TICKING "CENTRE" WAS WORTH 16px OF VERTICAL
   SPACE, AND NOTHING ON SCREEN SAID SO (plan §14.9, gate answers §15.3, record §16).

   THE MECHANISM, MEASURED RATHER THAN REASONED. `--shell-join`, `--shell-lift` and `--shell-heritage` each
   `display:contents` a rich block's wrapper — and a `display:contents` box has no margin, so the 16px above
   is inert there. But the dissolve is narrowed to `:not(.content-block-rich--laid-out)`, and
   `RichLayoutClass` adds that class the moment a block has ANY layout or typography. So an author who
   touched a block's ALIGNMENT gave it back its wrapper, and with the wrapper came a margin the alignment
   control says nothing about. The join card's five gaps measured 14 / 30 / 30 / 14 / 30 on a shipping page:
   14 below an untouched block, 30 below one somebody had centred. More than a factor of two, from a control
   that does not mention space.

   IT IS BACKLOG §10 AND §11's FAMILY POINTED THE OTHER WAY — not a control that does nothing, but a control
   that quietly does something else as well. One declaration settles it: on these three shells the card's
   rhythm is its own grid `gap`, whether a block kept its wrapper or not, and touching alignment changes
   alignment.

   0,3,0 AND DELIBERATELY NOT `!important`, WHICH IS THE OPPOSITE CHOICE FROM THE RULE ABOVE AND FOR THE
   OPPOSITE REASON. The weight beats the 0,2,0 base outright rather than by source order (§33.5's
   instruction), and an inline `margin-bottom` from the Layout panel still wins — because here the author DID
   ask for space and should get it, where a trailing margin under a card's last block is a thing no author is
   asking for. */
.home-widget--shell-lift > .content-block-rich.content-block-rich--laid-out,
.home-widget--shell-heritage > .content-block-rich.content-block-rich--laid-out,
.home-widget--shell-join > .content-block-rich.content-block-rich--laid-out{
  margin-bottom:0;
}

/* F10: per-block line spacing. Applied ONLY when the block sets a spacing (the render partial adds the
   modifier class + --para-gap var together); unset blocks keep the theme's natural line-height. Uses
   line-height (not child margins) so the gap applies evenly between EVERY line — wrapped lines inside one
   paragraph AND hard line breaks — per the user's F10 decision. calc resolves 1em against each element's own
   font-size, so mixed-size text keeps a proportional rhythm with the same extra px between lines. */
/* BT Phase 2: TWO CONTROLS SAY THE SAME THING HERE AND ONE OF THEM HAS TO WIN. This rule is later in the
   file at the same weight as the base above, so on a spaced block the Text panel's line spacing would have
   been a control that is present, reachable and does nothing. Reading the part's token FIRST and keeping the
   paragraph gap as the fallback settles it: the panel wins when it says something, the F10 stepper paints
   when it does not, and both mean the same arithmetic (extra px on top of 1em) so they cannot disagree about
   what the number is. */
.home-widget .content-block-rich--spaced,
.home-widget .content-block-rich--spaced *{
  line-height:var(--tp-all-line,calc(1em + var(--para-gap, 0px)));
}

/* SCC Phase 2 (F4): fit-to-card. The generic form of what the deleted .motto-widget__fit-area / __text did
   today, so a rebuilt motto scales identically without the widget's own classes.

   Scoped at .home-widget so it out-specifies the .home-widget .content-block-rich margin rule above (0,2,0
   against 0,2,0, later in the file) — a bottom margin on a flex:1 box would shorten the very height the
   search measures against, and the block would settle a size smaller than it should every time.

   `flex:1` is inert unless the shell is a flex column (only `board` is), so an unfitted-looking block in a
   feature card is unaffected. It is `flex:1 1 0%` and NOT `flex:1 1 <n>px` on purpose: a pixel basis under
   `flex-direction:column` becomes a MINIMUM HEIGHT, which is how three boxes once shipped tall and empty
   with every selector satisfied ([[flex-basis-becomes-a-height]]). */
.home-widget .content-block-rich--fit{
  flex:1;
  min-height:0;
  display:flex;
  align-items:center;
  justify-content:center;
  padding:2px 4px;
  margin:0;
}

/* The measured element. It must never break inside a word: the search's width ceiling is computed from the
   longest WORD, and text that can break anywhere never overflows — which would make the ceiling meaningless
   and let the script pick the size at which a long word gets chopped in half. The motto shipped that bug
   once; the rule is what stops the rebuild inheriting it. */
.home-widget .content-block-rich__fit{
  width:100%;
  text-align:center;
  margin:0;
  overflow-wrap:normal;
  word-break:normal;
  hyphens:none;
}

/* Authored markup arrives with its own margins (a <p> carries one by default), and a margin inside the
   measured box is height the fit cannot see past. Zeroing them here keeps scrollHeight honest. */
.home-widget .content-block-rich__fit > *{
  margin:0;
}

.content-block-cta{
  display:flex;
  gap:14px;
  flex-wrap:wrap;
}

/* BT Phase 2: the six label properties `.btn` does not state. All tokens with no fallback, so while nothing
   is set every one is invalid at computed-value time and the button computes what it computes today —
   inherited for the five that inherit, and `none` for the decoration, which is what `.btn` already declares.
   The colour is NOT here: every rendered button carries an inline `color`, which no rule can outweigh, so
   the label's colour is read by that inline declaration instead (see the render partial). */
.content-block-cta .btn{
  font-family:var(--tp-label-font);
  font-style:var(--tp-label-italic);
  text-decoration-line:var(--tp-label-underline);
  text-transform:var(--tp-label-case);
  line-height:var(--tp-label-line);
}

/* Group-level "match widths": every button in the repeater takes the width of the WIDEST label.
   Equal `1fr` tracks inside a SHRINK-TO-FIT box resolve to the MAX of the tracks' max-content
   contributions, so the row is exactly as wide as it needs to be and each button is a share of that. A
   STRETCHED grid would instead fill the whole card and give two enormous buttons — the same rule, a
   completely different picture.

   ITEM 50: `display:inline-grid` DID NOT BUY THE SHRINK-TO-FIT, AND THE PARAGRAPH ABOVE USED TO CLAIM IT
   DID. This row's parent is `<article class="home-widget">`, which is `display:flex`, so the row is a FLEX
   ITEM — and a flex item's `inline-grid` is BLOCKIFIED to `grid` by the box-generation rules. Measured on
   the live home page before the fix: computed display `grid`, `grid-template-columns: 559px 559px`, row
   1132px. The buttons were equal, at half the card each, which is why the feature looked inert rather than
   broken and why three codec round-trips stayed green from the day it shipped.

   `width:fit-content` is what actually asks for shrink-to-fit, and it survives the blockification. The
   `margin-inline:auto` is not decoration: the block's layout control writes an inline
   `justify-content:center` on this element, and once the box shrinks to its content that property has no
   free space left to distribute, so centring has to move to the box. After the fix, on the same page:
   208.2px per button in Chromium and 215.1px in WebKit, both the width of the wider label.

   `display:grid` is therefore stated plainly — it is what the element computes to in the real document,
   and writing `inline-grid` would only re-hide the fact that this is a stretched-by-default box being
   deliberately shrunk. */
.content-block-cta--match{
  display:grid;
  grid-auto-flow:column;
  /* `minmax(0,1fr)` AND NOT `1fr`, and the difference only shows on a narrow card. `1fr` is
     `minmax(auto,1fr)`, whose auto minimum is the track's MIN-CONTENT — so once the row is clamped by
     `max-width:100%`, a button whose longest word is wider than its equal share floors at that word and
     its neighbour takes the remainder. Measured at a 361px card: 154.8 and 150.2, unequal, which is the
     whole promise broken on exactly the screens where it matters most. `minmax(0,…)` lets both tracks
     shrink to the same share. Verified in Chromium and WebKit at 361, 700 and 1132px. */
  grid-auto-columns:minmax(0,1fr);
  gap:14px;
  width:fit-content;
  max-width:100%;
  margin-inline:auto;
}

.content-block-cta--match > *{
  justify-content:center;
  text-align:center;
}

@media (max-width:560px){
  /* Below this a three-across row of equal buttons is narrower than its own text. Stack them, still
     matched: one column, so every button is the full width of the card. `width:100%` deliberately beats
     the `fit-content` above — here the stretch is the point. */
  .content-block-cta--match{
    display:grid;
    grid-auto-flow:row;
    width:100%;
  }
}

.content-block-meta{
  display:flex;
  gap:30px;
  margin-top:26px;
  padding-top:18px;
}

/* BT Phase 2: the two stat parts, which the Text panel now owns outright — the Stats suite that used to
   write them as inline spans is deleted in Phase 3, and the spans it already wrote are lifted into these
   tokens on read (LegacyStatStyleLift). `font-weight:bold` is the user agent's, on a bare <b class="k">, and
   is written down here for the first time so a token with no fallback cannot quietly un-bold it. */
.content-block-meta .k{
  display:block;
  font-family:var(--tp-primary-font,var(--serif));
  font-size:var(--tp-primary-size,1.5rem);
  font-weight:var(--tp-primary-weight,bold);
  font-style:var(--tp-primary-italic);
  text-decoration-line:var(--tp-primary-underline);
  text-transform:var(--tp-primary-case);
  color:var(--tp-primary-color);
  line-height:var(--tp-primary-line);
}

/* SF PHASES 3+4, GATE Q10 — A STYLESHEET FALLBACK THAT NEVER MOVED INVERTED A HIERARCHY AT 1361px, AND
   THE RULE WAS NEVER THE PROBLEM (plan §14.11, gate answers §15.3, record §16).

   MEASURED: `£74` on the charity card renders at `var(--tp-figure-size, 2.6rem)` — 41.6px at EVERY width —
   while the card's own heading falls from 59.4px to 25.5px under Phase 2's clamp. So on every phone, every
   tablet and most laptops the running total was bigger than the heading above it. §13.13.4 recorded the
   number and read it as the charity block's own rule; across seven widths the rule is fine and the
   HIERARCHY inverts, with an address.

   THE TOKEN WAS ALREADY FLUID AND THE FALLBACK WAS NOT, which is the whole shape of the defect. An author
   who opens the Text panel and types a size gets a `clamp()` from `ContentBlockFluidType`; an author who
   never opens it gets the number in the `var()`'s second argument, frozen. So a control nobody touched was
   the only thing on the card that did not scale — and it ships that way to everyone who never edits it.

   SIX OF THE EIGHTEEN, AND THE CUT IS AT DISPLAY SIZE. `2.6rem`, `1.9rem`, `1.5rem` twice, `1.28rem` and
   `1.2rem` are display type: big enough that freezing them is visible and shrinking them is safe. The twelve
   at 1.05rem and under are body copy, captions and labels, where candidate C's floor barely moves a number
   and the change would be noise.

   EVERY CLAMP HERE IS `ContentBlockFluidType`'s OWN OUTPUT FOR THAT SIZE, NOT A NUMBER SOMEBODY TYPED, and
   `FluidTypeSourceTests` asserts each one against the helper. A stylesheet is the one place this project
   cannot call the helper at render time, so the guard is what stops these six from becoming a second
   implementation of the formula the day the factor changes.

   AND EACH ONE IS SCOPED TO `.home-widget`, WHICH IS THE ANSWER TO A QUESTION ASKED AT THE GATE (§15.6).
   Three of the six sit on `.event` / `.event--empty`, which `Pages/Meetings/Index.cshtml` renders too, inside
   a live `@foreach` that happens to have no rows today because the diary holds no non-meeting events. Left
   unscoped, adding one Gala would have started /Meetings taking a change from a pass whose scope is the home
   board only — invisible until it wasn't. Scoping is additive: the shipped rule stays for everything
   rendering outside the board, and 0,3,0 against 0,2,0 wins on it without depending on source order. */
.home-widget .content-block-meta .k{
  font-size:var(--tp-primary-size,clamp(1.17rem,1.6667vw,2.025rem));
}

.content-block-meta .l{
  font-size:var(--tp-secondary-size,.72rem);
  letter-spacing:.16em;
  text-transform:var(--tp-secondary-case,uppercase);
  color:var(--tp-secondary-color,var(--brass-soft));
  font-family:var(--tp-secondary-font);
  font-weight:var(--tp-secondary-weight);
  font-style:var(--tp-secondary-italic);
  text-decoration-line:var(--tp-secondary-underline);
  line-height:var(--tp-secondary-line);
}

.board{
  /* B3 T7: theme-driven surface (was a hard-coded cream gradient). --surface is white on light themes
     (near-identical to the old cream) and a raised dark on the dark themes, so --slate text reads on both. */
  background:linear-gradient(180deg, var(--surface) 0%, color-mix(in srgb, var(--surface) 88%, var(--ink) 12%) 100%);
  color:var(--slate);
  /* SCC PHASE 7m — THE ONE CARD ON THE BOARD THAT REFUSED THE BOARD'S RADIUS.
     This was a literal `34px` (and `26px` in the 900px query below), which is 0,1,0 like `.home-widget` and
     later in the file, so it won: setting the board's corner radius to 0 squared every card except the
     festive board, and with the gap at 0 the cards could not butt up into a continuous surface.

     7b MEASURED THIS AND RECORDED IT RATHER THAN FIXING IT (plan §25.3), on Q9's grounds that today's look
     survives the deploy — the E2E even asserted the exception was still there, with a note saying to delete
     the assertion if it was ever made deliberate. The user reported the consequence on 2026-08-15 and that
     displaces Q9 here: a setting the author can see and set, which one card silently ignores, is worse than
     a card whose corners changed. Q9 is not overturned generally; this is the one exception it protected.

     WHAT IT COSTS, SAID PLAINLY: on the shipped default (--r:14px) the festive board card renders at 14px
     rather than 34px. The nearest preset to its old look is 32px. The narrow override is DELETED rather
     than pointed at the token, because --r is one number for the whole site and a second, narrower
     statement of it is the "two places saying the board's shape" defect §23.3 exists to stop. */
  border-radius:var(--r);
  padding:16px 24px 18px;
  position:relative;
  box-shadow:0 24px 48px -34px rgba(0,0,0,.55);
  border:2px solid color-mix(in srgb, var(--brass) 38%, transparent);
  display:flex;
  flex-direction:column;
  gap:8px;
  align-self:stretch;
}

.board::before{
  display:none;
}

.board .eyebrow{
  color:var(--claret);
  font-size:.86rem;
  line-height:1;
  letter-spacing:.06em;
  font-weight:700;
  margin:0;
  margin-bottom:0;
  max-width:none;
  white-space:nowrap;
}

.board-head{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:10px;
  margin-bottom:2px;
  padding-right:2px;
}

.board-menu-btn{
  flex-shrink:0;
  display:inline-flex;
  align-items:center;
  justify-content:center;
  gap:6px;
  font-size:.8rem;
  font-weight:600;
  letter-spacing:.01em;
  border-radius:999px;
  padding:7px 14px;
  box-shadow:0 2px 7px rgba(0,0,0,.08);
}

.board-menu-icon{
  width:.74rem;
  height:.74rem;
  display:inline-flex;
  opacity:.9;
}

.board-menu-icon svg{
  width:100%;
  height:100%;
  fill:currentColor;
}

.board-cuisine{
  font-family:var(--serif);
  font-size:2.4rem;
  text-align:center;
  color:var(--heading);
  line-height:1.04;
  margin:4px 0 6px;
}

.board-by{
  text-align:center;
  font-style:italic;
  font-family:var(--serif);
  color:var(--claret);
  font-size:1.02rem;
  margin:0 0 18px;
}

.board-rule{
  display:flex;
  align-items:center;
  justify-content:center;
  gap:12px;
  color:var(--brass-soft);
  margin:16px 0;
}

.board-rule span{
  height:1px;
  background:color-mix(in srgb, var(--brass) 60%, transparent);
  flex:1;
  max-width:60px;
}

.board-detail{
  text-align:center;
  font-size:.95rem;
  color:var(--slate-soft);
  margin:0;
}

.board--empty{
  justify-content:flex-start;
}

.board-empty-state{
  margin-top:18px;
  border:1px solid color-mix(in srgb, var(--brass-ink) 18%, transparent);
  border-radius:18px;
  /* B3 T7: faint theme wash (was a hard-coded white overlay that vanished on dark themes). */
  background:color-mix(in srgb, var(--brass) 7%, transparent);
  padding:18px 16px;
  display:grid;
  align-content:center;
  gap:10px;
  min-height:360px;
}

.board--empty .board-cuisine{
  font-size:2.2rem;
  margin-top:0;
}

.board--empty .board-by{
  margin:0;
}

.board--empty .board-detail{
  max-width:30ch;
  margin:0 auto;
}

.board-flyer-btn{
  display:block;
  width:100%;
  border:0;
  padding:0;
  margin:0;
  background:transparent;
  border-radius:26px;
  overflow:hidden;
  cursor:zoom-in;
  box-shadow:0 10px 28px rgba(0,0,0,.16);
  flex:0 0 auto;
}

.board-flyer-img{
  display:block;
  width:100%;
  height:auto;
  max-height:none;
  object-fit:contain;
  object-position:center;
  transition:transform .25s ease, filter .25s ease;
}

.board-flyer-btn:hover .board-flyer-img{
  transform:scale(1.01);
  filter:saturate(1.04);
}

.board-actions{
  display:grid;
  grid-template-columns:1fr 1fr;
  gap:12px;
  margin-top:auto;
  padding-top:8px;
}

.board-actions .btn{
  min-width:0;
  width:100%;
  justify-content:center;
  padding:9px 12px;
  font-size:.82rem;
  font-weight:600;
  letter-spacing:.01em;
  line-height:1.15;
  border-radius:999px;
  min-height:40px;
  gap:6px;
  white-space:nowrap;
}

/* B3 T7: theme-driven board actions (were hard-coded gold + cream/claret). Primary = brass fill with the
   derived --on-brass text; secondary = a ghost on the board surface with --heading text (auto-contrasts). */
.board-action-primary{
  background:var(--brass);
  color:var(--on-brass);
  border:1px solid color-mix(in srgb, var(--brass) 55%, transparent);
  box-shadow:0 2px 8px rgba(0,0,0,.1);
}

.board-action-primary:hover{
  background:var(--brass-soft);
  border-color:var(--brass-soft);
  color:var(--ink-deep);
}

.board-action-secondary{
  background:transparent;
  color:var(--heading);
  border:1px solid color-mix(in srgb, var(--brass) 45%, transparent);
}

.board-action-secondary:hover{
  background:color-mix(in srgb, var(--brass) 12%, transparent);
  color:var(--heading);
  border-color:color-mix(in srgb, var(--brass) 62%, transparent);
}

.board-action-icon{
  width:.74rem;
  height:.74rem;
  display:inline-flex;
  opacity:.9;
}

.board-action-icon svg{
  width:100%;
  height:100%;
  fill:currentColor;
}

.section-head{
  max-width:60ch;
  margin-bottom:40px;
}

.section-head h2{
  color:var(--heading);
  font-size:clamp(1.9rem,3.4vw,2.7rem);
  margin:0 0 0.5rem;
}

.section-head p{
  color:var(--slate-soft);
  margin:14px 0 0;
}

.whatson{
  display:grid;
  grid-template-columns:1.4fr .9fr;
  gap:40px;
  align-items:start;
}

.whatson-main .section-head{
  margin-bottom:24px;
}

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

.event{
  display:grid;
  grid-template-columns:84px 1fr auto;
  gap:20px;
  align-items:center;
  padding:20px 0;
  border-top:1px solid var(--line-light);
}

.event:last-child{
  border-bottom:1px solid var(--line-light);
}

/* ITEM 16, GATE Q1(a) AND Q2(a) — A DIARY ROW IS A TABLE IN DISGUISE, SO ITS PARTS START AT THEIR COLUMN'S
   EDGE AND STOP READING THE CARD'S ALIGNMENT (backlog §16.7).

   THE VALUE BEING OVERRIDDEN IS AUTHORED, NOT LEAKED. `ContentBlockWidgetInstances` stores the literal word
   `center` on the diary block AND on the card, and `ContentBlock.Align` documents `null` as "inherit the
   card" — so this is not a null being resolved, it is a word somebody typed. Measured, the ten text runs of
   a two-row diary landed on NINE distinct left edges at 390 and nine at 1440; these two declarations take
   both to three, and Q3's rule below takes them to two.

   NO `!important`, BECAUSE INHERITANCE IS NOT PART OF THE CASCADE. The `center` arrives on the `li` by
   inheritance from the `ul`'s inline style, and an inherited value loses to ANY declaration on the element
   itself — so 0,2,0 here is enough to beat an inline value on the parent. The same sentence
   `_ContentBlockWidget.cshtml:105` relies on in the other direction.

   THE STORED WORD KEEPS ITS OTHER JOB. Align also renders as `margin-inline` on the block, and the diary is
   a 760px box in a 1132px column, so start/center/end still move the panel across the card visibly. Nothing
   an author can set becomes inert.

   SCOPED UNDER `.home-widget` BECAUSE `.event` LEAVES THE BOARD. `Pages/Meetings/Index.cshtml:85` paints its
   "Also coming up" list with these same four classes and no wrapper, so an unscoped rule would silently
   redesign a second page — the third of the three that leave the board (§15.6). The `.date` rule is separate
   and not folded into the one above it because `.event .date` declares `text-align:center` on the element
   itself, which inheritance cannot outrank: it takes 0,3,0 to beat 0,2,0. */
.home-widget .event{
  text-align:left;
}

.home-widget .event .date{
  text-align:left;
}

/* BT PHASE 2 — THE FOUR EVENT PARTS READ THEIR OWN TOKENS (plan §3, backlog §12). A `font-size` on the block
   wrapper would move the ONE part that states nothing and leave four alone, which is backlog §10's defect
   rebuilt on purpose — inheritance is not part of the cascade. So each rule that paints a part reads that
   part's property and keeps its shipped value as the fallback, and a property the rule does not state today
   reads the token with NO fallback: an unsubstituted var() is invalid at computed-value time, which for an
   inherited property computes to the inherited value, i.e. exactly what it paints now. Writing a literal
   there would freeze it (a hard-coded family stops following the theme).

   `.event`/`.date`/`.what`/`.tag` are ALSO /Meetings' "Also coming up" list, which carries no wrapper and
   therefore no token — so every one of these rules falls through to its fallback there, unchanged. */
.event .date{
  font-family:var(--tp-date-font,var(--serif));
  text-align:center;
  line-height:var(--tp-date-line,1);
  color:var(--tp-date-color,var(--heading));
  font-size:var(--tp-date-size);
  font-weight:var(--tp-date-weight);
  font-style:var(--tp-date-italic);
  text-transform:var(--tp-date-case);
  text-decoration-line:var(--tp-date-underline);
}

/* The date is ONE part over TWO elements (mockup Plate C): the day and the month are the date to an author.
   Both restate the size, so both read the same token — with their own value as the fallback, which is what
   keeps 1.9rem over .7rem when nothing is set and flattens them to one size when something is. */
.event .date b{
  display:block;
  font-size:var(--tp-date-size,1.9rem);
  font-weight:var(--tp-date-weight,500);
}

/* SF PHASES 3+4, GATE Q10 — see the note beside `.home-widget .content-block-meta .k` for the whole of why;
   the clamp is `ContentBlockFluidType`'s own output for this size and `FluidTypeSourceTests` asserts it.
   ONE OF THE THREE THAT LEAVES THE BOARD: `.event` renders inside `Pages/Meetings/Index.cshtml`'s live
   `@foreach` as well, so the scope is what keeps /Meetings byte-identical (§15.6). */
.home-widget .event .date b{
  font-size:var(--tp-date-size,clamp(1.306rem,2.1111vw,2.565rem));
}

.event .date small{
  font-size:var(--tp-date-size,.7rem);
  letter-spacing:.14em;
  text-transform:var(--tp-date-case,uppercase);
  color:var(--tp-date-color,var(--brass-ink));
  font-weight:var(--tp-date-weight,600);
}

/* The BASE rule for the `title` part: it carries all eight, and the empty row's heading below narrows four
   of them. That split is what keeps a colour or a casing set on the title reaching BOTH rows from one
   declaration, while the empty row keeps its own serif 1.5rem when nothing is set. */
.event .what b{
  display:block;
  font-weight:var(--tp-title-weight,600);
  font-family:var(--tp-title-font,var(--sans));
  font-size:var(--tp-title-size);
  font-style:var(--tp-title-italic);
  text-decoration-line:var(--tp-title-underline);
  text-transform:var(--tp-title-case);
  color:var(--tp-title-color);
  line-height:var(--tp-title-line);
}

.event .what span{
  color:var(--tp-detail-color,var(--slate-soft));
  font-size:var(--tp-detail-size,.92rem);
  font-family:var(--tp-detail-font);
  font-weight:var(--tp-detail-weight);
  font-style:var(--tp-detail-italic);
  text-decoration-line:var(--tp-detail-underline);
  text-transform:var(--tp-detail-case);
  line-height:var(--tp-detail-line);
}

.event .tag{
  font-size:var(--tp-tag-size,.7rem);
  letter-spacing:.12em;
  text-transform:var(--tp-tag-case,uppercase);
  font-weight:var(--tp-tag-weight,600);
  color:var(--tp-tag-color,var(--claret));
  font-family:var(--tp-tag-font);
  font-style:var(--tp-tag-italic);
  text-decoration-line:var(--tp-tag-underline);
  line-height:var(--tp-tag-line);
  /* ITEM 16, GATE Q3(a) — the pill's inline padding and its border are named here because the phone rule
     that pulls the pill left has to be worth EXACTLY them and nothing else. A literal -11px there would be
     a second place saying what a chip's inset is, and it would drift silently the first time either number
     moved. Both substitute to the values this rule has always painted, so /Meetings is unchanged. */
  --tag-pad-x:10px;
  --tag-border-w:1px;
  border:var(--tag-border-w) solid color-mix(in srgb, var(--claret) 30%, transparent);
  padding:5px var(--tag-pad-x);
  border-radius:999px;
  white-space:nowrap;
}

.event--empty{
  grid-template-columns:1fr;
  gap:8px;
  padding:24px 0;
}

.event--empty .what{
  max-width:44ch;
}

.event--empty .what b{
  font-family:var(--tp-title-font,var(--serif));
  font-size:var(--tp-title-size,1.5rem);
  font-weight:var(--tp-title-weight,500);
  line-height:var(--tp-title-line,1.1);
  margin-bottom:6px;
}

/* SF PHASES 3+4, GATE Q10 — see the note beside `.home-widget .content-block-meta .k` for the whole of why;
   the clamp is `ContentBlockFluidType`'s own output for this size and `FluidTypeSourceTests` asserts it.
   THE SECOND OF THE THREE THAT LEAVES THE BOARD (§15.6). Its narrow-screen twin is in the 560px query near
   the end of this file and has to stay LATER than this rule: both weigh 0,3,1, a media query adds no
   specificity of its own, and source order is therefore what decides which one paints on a phone. */
.home-widget .event--empty .what b{
  font-size:var(--tp-title-size,clamp(1.17rem,1.6667vw,2.025rem));
}

.event--empty .what span{
  font-size:var(--tp-detail-size,1rem);
  line-height:var(--tp-detail-line,1.45);
}

.card{
  background:var(--surface);
  border:1px solid var(--line-light);
  border-radius:var(--r);
  padding:28px;
  transition:transform .18s, box-shadow .18s;
}

.card:hover{
  transform:translateY(-3px);
  box-shadow:0 22px 40px -26px rgba(0,0,0,.45);
}

/* SCC Phase 4c wrote `.content-block-charity__*` as a shared pair with the legacy Charity widget, so the
   widget and its rebuild painted from ONE definition; Phase 5b deleted the widget and the `.charity*` half
   with it. It is §17.2's reading reached for the second time and by the same road: `.charity`, `.charity-sub`
   and `.charity-list` are used by NOTHING else in this codebase (grep: one Razor file), so reusing them would
   have welded a class named after a deleted widget into the block family forever.

   TWO OF THE LEGACY RULES BELOW HAVE NEVER APPLIED, AND ONLY A COMPUTED-VALUE DUMP COULD SAY SO. `.charity p`
   weighs 0,1,1 and beats BOTH `.eyebrow` (0,1,0) and `.charity-sub` (0,1,0), and it is later in the file than
   either. So the Charity card's eyebrow has never been eyebrow-sized — it paints at .95rem with an 18px
   margin, not .72rem/14px — and `.charity-sub`'s own .9rem and 16px margin have never reached the screen at
   all; the caption is .95rem with an 18px margin like every other paragraph in the card. Its `color` is the
   only declaration it contributes, and `.charity p` sets that to the same token. This is [[shared-selectors-
   do-not-share-specificity]] read off getComputedStyle rather than derived, which is the only way it is safe
   to say — the first version of this comment was derived and got the eyebrow wrong.

   The rebuilt card therefore authors .95rem/18px INTO the eyebrow's rich block — it sits OUTSIDE the block's
   wrapper, so no rule here can reach it — while the caption and the empty line get theirs the way the legacy
   ones do.

   EVERY PAIR BELOW WEIGHS EXACTLY WHAT ITS LEGACY HALF WEIGHS, and that is §14.5's finding taken seriously
   rather than noted: `.content-block-charity` is written as the SCOPE that `.charity` is, so `.charity p` and
   `.content-block-charity p` are both 0,1,1 and the two `.big` rules are both 0,2,0. A bare
   `.content-block-charity__total` would have been 0,1,0 — enough today, and a rule nobody could see was
   load-bearing the first time something at 0,1,1 matched a paragraph in a card. */
/* BT PHASE 2 — THE FIGURE CARRIES ALL EIGHT AND FOUR OF THEM ARE PURE ARMOUR. `.content-block-charity p`
   below matches this paragraph too (the total IS a <p>), so without a declaration of its own here a caption
   set bold or uppercase would drag the £2,045 with it. This rule weighs 0,2,0 against that rule's 0,1,1, so
   stating each token here is what keeps the two parts separate. */
.content-block-charity .content-block-charity__total{
  font-family:var(--tp-figure-font,var(--serif));
  font-size:var(--tp-figure-size,2.6rem);
  color:var(--tp-figure-color,var(--claret));
  line-height:var(--tp-figure-line,1);
  margin:6px 0 4px;
  font-weight:var(--tp-figure-weight);
  font-style:var(--tp-figure-italic);
  text-decoration-line:var(--tp-figure-underline);
  text-transform:var(--tp-figure-case);
}

/* SF PHASES 3+4, GATE Q10 — see the note beside `.home-widget .content-block-meta .k` for the whole of why;
   the clamp is `ContentBlockFluidType`'s own output for this size and `FluidTypeSourceTests` asserts it.
   THIS IS THE ONE §14.11 MEASURED: 41.6px frozen against a heading falling to 25.5px, so the running total
   outranked its own heading below 1361px. It now floors at 24.7px, under the heading's 25.5 at every width
   the board is read at. */
.home-widget .content-block-charity .content-block-charity__total{
  font-size:var(--tp-figure-size,clamp(1.544rem,2.8889vw,3.51rem));
}

/* The `caption` part: the "Running total for 2026" line AND the empty line, which carries no class and
   matches the same rule — which is why the mockup names them as one part rather than inventing a class the
   render has deliberately refused to emit. */
.content-block-charity p{
  color:var(--tp-caption-color,var(--slate-soft));
  font-size:var(--tp-caption-size,.95rem);
  margin:0 0 18px;
  font-family:var(--tp-caption-font);
  font-weight:var(--tp-caption-weight);
  font-style:var(--tp-caption-italic);
  text-decoration-line:var(--tp-caption-underline);
  text-transform:var(--tp-caption-case);
  line-height:var(--tp-caption-line);
}

.content-block-charity__list{
  list-style:none;
  margin:0;
  padding:0;
  display:grid;
  gap:12px;
}

.content-block-charity__list li{
  border-top:1px solid var(--line-light);
  padding-top:10px;
}

.content-block-charity__list li:first-child{
  border-top:0;
  padding-top:0;
}

.content-block-charity__list b{
  display:block;
  color:var(--tp-update-title-color,var(--heading));
  font-weight:var(--tp-update-title-weight,bold);
  font-family:var(--tp-update-title-font);
  font-size:var(--tp-update-title-size);
  font-style:var(--tp-update-title-italic);
  text-decoration-line:var(--tp-update-title-underline);
  text-transform:var(--tp-update-title-case);
  line-height:var(--tp-update-title-line);
}

.content-block-charity__list span{
  color:var(--tp-update-detail-color,var(--slate-soft));
  font-size:var(--tp-update-detail-size,.88rem);
  font-family:var(--tp-update-detail-font);
  font-weight:var(--tp-update-detail-weight);
  font-style:var(--tp-update-detail-italic);
  text-decoration-line:var(--tp-update-detail-underline);
  text-transform:var(--tp-update-detail-case);
  line-height:var(--tp-update-detail-line);
}

.heritage{
  background:var(--ink-deep);
  color:var(--on-dark);
}

.heritage-grid{
  display:grid;
  grid-template-columns:1fr .9fr;
  gap:54px;
  align-items:center;
}

.heritage-grid--single{
  grid-template-columns:1fr;
}

.heritage h2{
  color:var(--on-dark);
  font-size:clamp(2rem,3.6vw,3rem);
}

.heritage p{
  color:color-mix(in srgb, var(--on-dark) 82%, transparent);
}

/* SCC Phase 4b wrote `.content-block-links` / `.content-block-link` as a shared pair with the legacy Links
   widget, so the widget and its rebuild painted from ONE definition; Phase 5b deleted the widget and the
   `.links-grid` / `.link` half with it.

   THIS IS THE OPPOSITE CASE TO `.events`, AND THE SAME RULE (§15.1). The Events block reaches for
   `.section-head` / `.event` / `.tag` unchanged because /Meetings paints its "Also coming up" list with them
   — they outlive the widget on their own. `.links-grid` and `.link` are used by NOTHING else in the codebase
   (grep: one Razor file and one test selector), so reusing them would have welded a class named after a
   deleted widget into the block family forever, which is exactly what §12.1 refused for `.join-band`.

   EVERY PAIR HERE WEIGHS THE SAME — 0,1,0 for the two container rules and 0,1,1 for the three descendant
   ones — so unlike the join band's 560px padding (§14.5) there is no specificity to qualify. That was read
   out of the computed values, not derived: the whole family is these five rules plus two grid-template
   overrides in the 900px and 560px queries, and nothing else in this stylesheet matches `.link`. */
.content-block-links{
  display:grid;
  grid-template-columns:repeat(3,1fr);
  gap:22px;
}

.content-block-link{
  display:block;
  text-decoration:none;
  background:var(--surface);
  border:1px solid var(--line-light);
  border-radius:var(--r);
  padding:24px;
  transition:transform .18s, border-color .18s;
}

.content-block-link:hover{
  transform:translateY(-3px);
  border-color:var(--brass);
}

/* BT Phase 2. `font-weight:bold` IS the shipped value and it is written down here for the first time: a bare
   <b> is bold by the USER AGENT, not by this stylesheet, so a token with no fallback would compute to the
   inherited weight and the links title would silently stop being bold the day this shipped.

   `text-decoration-line` is gate Q2's one named cost (plan §7.2). The title lives inside an anchor
   `.content-block-link` sets `text-decoration:none` on — but that is the ANCESTOR's declaration, and nothing
   propagates from it, so the <b>'s own line wins here and the underline paints. */
.content-block-link b{
  font-family:var(--tp-title-font,var(--serif));
  font-size:var(--tp-title-size,1.2rem);
  color:var(--tp-title-color,var(--heading));
  font-weight:var(--tp-title-weight,bold);
  font-style:var(--tp-title-italic);
  text-decoration-line:var(--tp-title-underline);
  text-transform:var(--tp-title-case);
  line-height:var(--tp-title-line);
}

/* SF PHASES 3+4, GATE Q10 — see the note beside `.home-widget .content-block-meta .k` for the whole of why;
   the clamp is `ContentBlockFluidType`'s own output for this size and `FluidTypeSourceTests` asserts it.
   Home board only — `.content-block-link` is the Links block's own anchor and renders nowhere else. */
.home-widget .content-block-link b{
  font-size:var(--tp-title-size,clamp(1.068rem,1.3333vw,1.62rem));
}

.content-block-link p{
  color:var(--tp-description-color,var(--slate-soft));
  font-size:var(--tp-description-size,.92rem);
  margin:8px 0 0;
  font-family:var(--tp-description-font);
  font-weight:var(--tp-description-weight);
  font-style:var(--tp-description-italic);
  text-decoration-line:var(--tp-description-underline);
  text-transform:var(--tp-description-case);
  line-height:var(--tp-description-line);
}

/* SCC Phase 2 wrote these four rules as a shared pair so the join band and its Content Block rebuild
   painted from ONE definition; Phase 5b deleted the widget and its half of each pair. */
.home-widget--shell-join{
  position:relative;
  overflow:hidden;
  background:
    radial-gradient(120% 120% at 18% 22%, color-mix(in srgb, var(--brass) 14%, transparent), transparent 44%),
    radial-gradient(140% 120% at 85% 78%, rgba(0,0,0,.35), transparent 56%),
    linear-gradient(145deg, #641324 0%, #5a1020 45%, #430a18 100%);
  color:var(--on-dark);
  text-align:center;
  border:1px solid color-mix(in srgb, var(--brass-soft) 26%, transparent);
  box-shadow:0 22px 48px -30px rgba(0,0,0,.65);
}

.home-widget--shell-join::before{
  content:"";
  position:absolute;
  inset:10px;
  border:1px solid color-mix(in srgb, var(--brass-soft) 22%, transparent);
  border-radius:20px;
  pointer-events:none;
}

/* SF PHASE 6 STEP 4, GATE Q7 - `--shell-join::after` IS RETIRED, AND ITS DRAWING IS NOW AUTHORED.
   The 440px brass circle that lived here is `CardArt.Circle`, painted on the background layer at a strength
   an author sets, and migration 073 gives every card wearing this shell exactly the circle at 12% it was
   already painting - so this deletion moves no pixel on the board it ships against.

   IT WAS WELDED AND UNREACHABLE, WHICH IS WHY IT HAD TO MOVE RATHER THAN STAY. No control named it, no
   author could move it, and a card that was not the invitation band could not have it - against the standing
   "I want it all to be editable via the homepage designer". Retiring it also fixes two smaller things at
   once: `::after` is free again for this shell, and the drawing stops being `display:none` below 560px (gate
   Q8, answered (a) - nobody chose that; it was a defensive rule from before the board had a layout mode, and
   a resolution-independent drawing is the one kind of decoration that costs a phone nothing). */

/* SCC PHASE 8, STEP 1 (plan §33.4) — THE SHELL'S CAPS BECOME TOKENS, AND NOTHING ELSE CHANGES YET.
   These two numbers are the band's whole typographic design and they were right for a 1132px card. They are
   also ABSOLUTE, which is why a full-window card's 2225px stage still shows a ~350px ribbon of text: `ch` is
   relative to the element's own font-size and knows nothing about the room it is in.

   So each becomes a custom property with today's value as its FALLBACK. On an ordinary card nothing moves —
   the fallback is the number that was already there — and a card that has been given a stage can widen them
   by setting the token. That is Q4's answer built as a seam rather than as a second hardcoded number, and it
   is deliberately the FIRST thing this phase does, alone, where the parity harness can see it. */
.home-widget--shell-join h2{
  color:#fff;
  font-size:clamp(2.2rem,4.7vw,5.1rem);
  letter-spacing:-.02em;
  line-height:.98;
  max-width:var(--band-heading-measure, 12.5ch);
  margin:0;
}

/* SCC Phase 3b — the three content rules the rebuild could NOT author inline, and the reason is the same for
   all three: EACH OF THEM CHANGES AT 560px. A block's styling lives in the block's own HTML (§13.2 chose that
   over adding settings, and the crestline, eyebrow, heading and divider are all authored that way) — but an
   inline style cannot hold a media query, so the band's measure, its call to action and its benefits row have
   to be reachable by a selector. They were shared with the legacy the same way the shell rules above were,
   and Phase 5b dropped the .join-band__ half with the widget.

   The rebuild's half is SCOPED TO THE SHELL rather than named as a bare class, because none of these means
   anything outside the claret band — brass-soft on claret, a 30ch measure and a 3-up divided row are that
   card's design, not a general vocabulary. And it is a new name rather than the legacy one: resolving the
   rebuild onto .join-band__copy would re-weld that CSS to the board forever, which is the option §6 Q3
   rejected. */
.home-widget--shell-join .join-copy{
  color:color-mix(in srgb, var(--on-dark) 90%, transparent);
  max-width:var(--band-copy-measure, 30ch);
  margin:0;
  font-size:clamp(1.02rem,1.5vw,1.18rem);
  line-height:1.5;
}

.home-widget--shell-join .join-cta{
  margin-top:4px;
  min-width:250px;
  border-radius:999px;
  font-size:1.02rem;
  letter-spacing:.08em;
  text-transform:uppercase;
  padding:15px 28px;
  box-shadow:0 10px 22px rgba(0,0,0,.28);
}

.home-widget--shell-join .join-benefits{
  margin-top:6px;
  width:min(100%, 760px);
  display:grid;
  grid-template-columns:repeat(3, minmax(0, 1fr));
  gap:0;
}

.home-widget--shell-join .join-benefits span{
  display:flex;
  align-items:center;
  justify-content:center;
  gap:.5rem;
  padding:9px 12px 0;
  color:var(--brass-soft);
  font-size:.86rem;
  font-weight:600;
  letter-spacing:.12em;
  text-transform:uppercase;
}

.home-widget--shell-join .join-benefits span + span{
  border-left:1px solid color-mix(in srgb, var(--brass-soft) 25%, transparent);
}

.surface{
  background:var(--surface);
  border:1px solid var(--line-light);
  border-radius:var(--r);
  padding:28px;
}

.site-foot{
  background:var(--ink-deep);
  color:color-mix(in srgb, var(--on-dark) 72%, transparent);
  padding:64px 0 30px;
}

.foot-grid{
  display:grid;
  grid-template-columns:1.4fr 1fr 1fr;
  gap:40px;
  margin-bottom:40px;
}

.foot-brand{
  display:flex;
  align-items:center;
  gap:14px;
  margin-bottom:16px;
}

.motto{
  font-family:var(--serif);
  font-style:italic;
  color:var(--brass-soft);
  font-size:1.15rem;
}

/* h2, not h4, since MJ Phase 8. These are the footer's own top-level headings and they sit alongside the
   page's h2 sections, so an h4 made EVERY page on the site skip two levels at the footer — the only
   heading-order defect the phase's audit found, and it was in shared chrome rather than on any MJ page.
   Nothing about the type changes: this rule is the whole of their appearance. */
.foot-col h2{
  font-family:var(--sans);
  font-size:.72rem;
  letter-spacing:.18em;
  text-transform:uppercase;
  /* B3 T7: brass-soft (the light accent tint) not brass — on the coloured-accent themes (Regalia,
     College, Ashlar, Laurel) brass is a saturated mid-tone that fell below AA on the near-black footer;
     brass-soft lands 9-14:1 in every palette while keeping each theme's hue. */
  color:var(--brass-soft);
  margin:0 0 14px;
}

.foot-col a{
  display:block;
  color:color-mix(in srgb, var(--on-dark) 72%, transparent);
  text-decoration:none;
  padding:5px 0;
}

.foot-col a:hover{
  color:var(--on-dark);
}

.foot-bottom{
  border-top:1px solid var(--line-dark);
  padding-top:22px;
  display:flex;
  justify-content:space-between;
  gap:16px;
  flex-wrap:wrap;
  font-size:.82rem;
  color:color-mix(in srgb, var(--on-dark) 60%, transparent);
}

.form-control,
.form-select,
textarea{
  border-radius:10px;
  border:1px solid rgba(0,0,0,.2);
}

.table{
  --bs-table-bg: transparent;
}

@media(max-width:900px){
  .nav{
    min-height:74px;
    flex-wrap:wrap;
    row-gap:10px;
    padding:10px 0;
  }
  .menu-toggle{display:inline-flex}
  .brand{
    flex:1;
    min-width:0;
  }
  .brand-name{
    font-size:.98rem;
  }
  .brand-name span{
    font-size:.58rem;
    letter-spacing:.17em;
  }
  .nav-links{
    display:none;
    width:100%;
    order:3;
    margin-left:0;
    flex-direction:column;
    align-items:stretch;
    justify-content:flex-start;
    gap:4px;
    background:var(--ink-deep);
    border:1px solid var(--line-dark);
    border-radius:12px;
    padding:10px 14px 14px;
  }
  .nav-links.show{display:flex}
  .nav-links a{
    width:100%;
    justify-content:flex-start;
    padding:10px 2px;
    border-bottom:1px solid var(--line-dark);
  }
  .nav-links .btn{
    width:100%;
    justify-content:center;
    margin-top:8px;
    padding:11px 16px;
  }
  /* ITEM 53 SLICE 3, AND THE FIRST DRAFT OF THIS RULE MADE THE PHONE SCROLL SIDEWAYS.

     It pulled the panel out to the header's right edge with `right:-52px`, reasoning that a panel anchored
     to the disc would sit tucked under the hamburger beside it. What it actually did was put 52px of panel
     PAST the document's right edge, and the browser net measured `scrollWidth > clientWidth` at both 320
     and 390 — a page that scrolls sideways on the surface this lodge is read on, found by the one check
     that was looking at the document rather than at the element.

     The panel is anchored to the disc and CLAMPED instead. `width:max-content` keeps it as narrow as its
     longest item, and the max-width is what guarantees it cannot reach the left edge at 320 either: the
     disc's own right edge is about 86px in from the screen's (the wrap's 16px gutter, the 46px hamburger
     and the nav's 24px gap), so a panel wider than the screen minus twice that has nowhere to go. */
  .usermenu[open] .usermenu__panel{
    right:0;
    min-width:0;
    width:max-content;
    max-width:calc(100vw - 110px);
  }
  .content-block-grid,.whatson,.heritage-grid{grid-template-columns:1fr;gap:40px}
  /* minmax(0,1fr), not a bare 1fr — see the note in the 560px query below.
     SCC Phase 7b: the count is now DERIVED from the board setting rather than restated here. It was a
     literal 2 beside a literal 3 in the base rule, which is a rule asserting a structure that the board is
     about to be able to stop having (§23.3). --board-columns-md is min(columns,2), computed once in
     HomeBoardLayout, so a two-column board keeps two here instead of silently growing one. */
  .home-widget-grid{grid-template-columns:repeat(var(--board-columns-md),minmax(0,1fr))}
  .home-widget--span-2,.home-widget--span-full{grid-column:1 / -1}
  .content-block-links{grid-template-columns:1fr 1fr}
  .foot-grid{grid-template-columns:1fr 1fr}
}

@media(max-width:560px){
  .wrap{padding:0 16px}
  .seal{width:40px;height:40px}
  .brand{gap:10px}
  .brand-name{font-size:.9rem}
  .brand-name span{
    font-size:.52rem;
    letter-spacing:.15em;
  }
  .nav-links{padding:8px 12px 12px}
  /* ITEM 53 SLICE 3. 44 and not 46, matching the seal's own step down two lines up — and it is the floor
     exactly, which is the number item 44 will measure this control against. */
  .usermenu__disc{width:44px;height:44px}
  /* A BARE 1fr CANNOT SHRINK BELOW ITS CONTENT. The base rule above is already
     repeat(3,minmax(0,1fr)); these two narrow queries were written before that and kept the bare unit,
     so at 360px the single track computed to 368.6px inside a 328px box and the PUBLIC HOME PAGE scrolled
     sideways on every one of the eleven themes. Same defect MJ Phase 7 fixed on the portal's grids and
     MJ Phase 8 fixed on the coming-soon badge; found here by Phase 8's 360px pass, on a page MJ does not
     own, and fixed rather than filed because the remedy is the token three lines up.

     SCC Phase 7b: written as repeat(var(--board-columns-sm),…) rather than as the bare track, so all three
     column counts are read the same way from the same derivation. The token is 1 for every board setting
     — the phone step does not depend on the desktop one — but a literal here would be the third of three
     places that each said what the board's shape is, which is what §23.3 is about.

     SCC PHASE 7m — AND THE PARAGRAPH ABOVE IS WHY THIS RULE DID NOT EXIST. 7b appended it AFTER the close
     that ended the paragraph before it, leaving four lines of prose and one orphaned close sitting at the
     top level of this query. CSS error recovery consumes an unrecognised prelude up to and including the
     first brace block, so the garbage swallowed `.home-widget-grid` and this whole rule was DROPPED — the
     phone board kept `--board-columns-md` from the 900px query above and rendered TWO 152px tracks at
     360px. Measured in Chromium at five widths, not reasoned (build record §28).

     NOTE FOR WHOEVER EDITS THIS COMMENT NEXT, because the first draft of the paragraph you are reading did
     the identical thing and the re-measurement is the only reason it did not ship: a comment cannot quote
     its own terminator, not even inside backticks. Say "close", not the two characters. */
  .home-widget-grid{grid-template-columns:repeat(var(--board-columns-sm),minmax(0,1fr))}
  .home-widget--span-2,.home-widget--span-full{grid-column:1}
  /* SCC PHASE 7e — THE PHONE ORDER, AND IT LIVES ONLY HERE (plan §23.13 Q14, §31). Q14 cut "an order per
     breakpoint" down to one integer per card, because a two-column board already reads close to the desktop
     one and the width where the desktop order genuinely reads wrong is this one. Rendering it inside this
     query alone is what makes the column inert at every other width by construction rather than by a rule
     somebody has to remember.

     999 IS "WHEREVER THE BOARD PUT IT", AND IT IS THE FALLBACK RATHER THAN A RENDERED VALUE. `order` sorts
     ascending and ties keep document order, so un-pinned cards sharing one number above every pinned one
     means: the pinned cards come first in their own sequence, then everything else in the board's order.
     Putting that number here rather than on each card is what keeps a card nobody has moved byte-identical
     to before migration 064 — HomeWidgetPartialModel emits --phone-order only for a card that has one — and
     it keeps the constant in one place. ContentBlockLayoutMode.MaxPhoneOrder (20) is what holds it above
     every value an author can reach. */
  .home-widget-grid > *{order:var(--phone-order, 999)}
  /* SF PHASE 1 - AND A SEAM IS ONLY A SEAM IN ONE OF THE BOARD'S TWO ORDERINGS (plan §6.5). The rule above
     is why: a pair of neighbours on the desktop board can be a pair of NON-neighbours here, which would put
     half a photograph at the foot of one card and the other half at the head of a card two screens away.
     Adjacency is computed for BOTH orderings server-side and a seam adjacent in only one carries this class.

     `display:none` is exact rather than approximate: the layers are absolutely positioned and contribute
     nothing to layout, so removing them changes nothing but what is painted. Every card on the dev board has
     `--phone-order` unset today, which is exactly why this had to be written now — the defect would have been
     invisible until somebody used a feature that already ships. */
  .home-widget__seam--phone-off{display:none}
  .content-block-links,.foot-grid{grid-template-columns:1fr}
  .content-block-meta{flex-wrap:wrap;gap:22px}
  .event{grid-template-columns:64px 1fr;grid-template-areas:"date what" "date tag"}
  .event .tag{justify-self:start;grid-area:tag}
  /* ITEM 16, GATE Q3(a) — A BORDERED CHIP'S BOX IS ALIGNED AND ITS INK IS NOT (backlog §16.7).
     `justify-self:start` above lands the pill's BORDER on the column edge, so its letters start one padding
     and one border further in — 11px — while the title directly above them starts at the edge. That was the
     third of the three edges Q1 leaves behind, and it is the only one a reader sees as a wrong indent rather
     than as a column.

     Pulling the pill out by exactly its own inset puts the ink on the title's edge and the border in the
     gutter, which is what a bordered chip is for. It also closes a drift the phone screenshot could not
     show: the pill is a fixed 77.5px at `justify-self:start`, so its centre was frozen at 171.8 from 320 to
     560 while the title it belongs to travelled 202 to 322 — 30px of gap on the narrowest phone and 150px
     on the largest. Aligned to the edge instead, the pill stops being pinned by anything but the column.

     ONLY HERE, AND ONLY ON THE BOARD. Above 560px the tag has a column of its own at the right-hand end of
     the row with no title over it, so there is no edge to join and the pull would just shrink the gutter.
     And `.home-widget` keeps it off /Meetings, whose copy of this list has no wrapper. */
  .home-widget .event .tag{margin-left:calc((var(--tag-pad-x) + var(--tag-border-w)) * -1)}

  /* ITEM 16, GATE Q6(a) — THE BOARD'S DIARY IS ONE ENTRY ON A PHONE, AND THE ROW IS TRIMMED WITH IT
     (backlog §16.9, §16.11).

     Q4(b) folded the card's height into item 16, and the padding its answer named turned out to be worth
     the whole overflow at 390px AND NOWHERE ELSE — the same mutation left the card 17px over at 360 and
     108px over at 320, because 769 is a `min-height` FLOOR on the wrapper rather than the content resting
     on it. Measured as natural content height the card had 13.8px of headroom with every block's padding
     deleted, so there was no middle setting to find and no arrangement of padding alone that fits a 320px
     phone. Of five candidates measured at 320/360/390/430, the ones that fit all four gave up a row.

     WITH `Pad` NOW FLUID the card reads 759.8 / 697.7 / 697.7 / 637.8 against its 769 floor on this rule
     alone, and 727.8 / 665.7 / 665.7 / 605.8 with the row trim below — comfortable at every phone width
     rather than exact at one.

     `nth-of-type` AND NOT `nth-child`, because the empty-state row is an `.event.event--empty` `li` and the
     count has to be of diary rows. A list with nothing in it renders ONE row and is untouched here.

     SCOPED, LIKE EVERY OTHER RULE IN THIS FAMILY. /Meetings' "Also coming up" list is the whole diary and
     hiding four fifths of it would be a different page's decision.

     AND THE TRIM EXCLUDES THE EMPTY ROW, WHICH IS NOT PEDANTRY — `.event--empty` is an `li` carrying BOTH
     classes, and its own narrow-screen rule twenty lines down is `padding:18px 0` at 0,1,0. A bare
     `.home-widget .event` weighs 0,2,0 and would have taken an empty diary from 18px to 10px on the board
     only, silently, in a state no measurement in this item ever looked at. The `:not()` says the trim is
     about diary ROWS. */
  .home-widget li.event:nth-of-type(n+2){display:none}
  .home-widget li.event:not(.event--empty){padding:10px 0;row-gap:8px}
  /* SCC Phase 5b: the .join-band half of each of these pairs went with the widget. The shell half is
     unchanged, at the same weight it always had, so nothing here renders differently.

     SCC PHASE 6 MADE THE FIRST LINE APPLY, WHICH IT NEVER HAD. It weighed 0,1,0 and lost to
     `.home-widget.home-widget--shell-join` above — 0,2,0 and outside any query — so the band was 54/28/44
     on every phone from the day this was written (it predates SCC: the 0,2,0 rule is in the file at HW
     Phase 4). The doubled class here is not symmetry with that rule for its own sake; it is the only weight
     that beats it, and §20.9's note explains why the rule above cannot simply be un-doubled instead.

     WHAT THE CHANGE IS WORTH, MEASURED, because §20.9's phrasing ("the band would change on every phone")
     reads like a threat and the truth is smaller: the band gains 24px of content width at 320px (254 against
     230) and nothing was broken without it — the board has no viewport overflow at any width and nothing
     inside the band escaped the band at 320, 360 or 390. So this was a taste question rather than a defect,
     and it was taken (user, 2026-08-15) on the grounds that somebody wrote these numbers for narrow screens
     and nobody has ever seen them. A stylesheet where a rule's weight is an accident is worse than either
     answer. */
  .home-widget.home-widget--shell-join{padding:42px 16px 30px}
  .home-widget--shell-join::before{inset:8px}
  /* SF PHASE 2, GATE Q7 — `.home-widget--shell-join h2{max-width:11ch}` STOOD HERE AND IT IS DELETED
     (docs/sample-front-end-plan.md §9.3, §10.2 Q7, build record §12).

     A BOX MEASURED IN ONE TYPE SCALE HOLDING ANOTHER, which is this phase's own thesis sitting on the board.
     `ch` resolves against the ELEMENT's font-size, and the h2's own size is the shell's
     `clamp(2.2rem,4.7vw,5.1rem)` — 35.2px at 390 — so `11ch` computed to 184.69px. The text inside it is the
     AUTHOR's, at 72px after SCC 7f's shrink routine had already run, whose widest word renders 351px. The
     heading was cut by 166px on every phone, and no amount of shrinking the type could fix it because the box
     was never measuring the type it held.

     Deleting the override lets `--band-heading-measure`'s .58 share of `--card-measure` hold at every width,
     so the box follows the CARD instead of a font nothing is set in — which is SCC Phase 8's whole seam, and
     the reason this is one removed declaration rather than a second token to keep in step. The gate recorded
     the coupling: candidate C keeps a 41.2px heading on a phone rather than shrinking it into a small box,
     so C only fixes card 5 WITH this deletion, which is what makes Q7 load-bearing rather than cosmetic. */
  .home-widget--shell-join .join-copy{max-width:28ch}
  .home-widget--shell-join .join-cta{min-width:0;width:100%}
  .home-widget--shell-join .join-benefits{grid-template-columns:1fr}
  .home-widget--shell-join .join-benefits span{justify-content:flex-start;padding:8px 0}
  .home-widget--shell-join .join-benefits span + span{
    border-left:0;
    border-top:1px solid color-mix(in srgb, var(--brass-soft) 25%, transparent);
  }
  /* SCC Phase 7m: the `border-radius:26px` that stood here is gone with the 34px it narrowed — see the note
     on `.board` above. The padding narrowing stays; that is a real narrow-screen correction and not a
     second opinion about the board's geometry. */
  .board{
    padding:16px 14px 14px;
  }
  /* SCC Phase 2: the same narrowing for the `board` SHELL. Its padding is the sum of the three boxes it
     replaces — heritage's 34px (unchanged at this width), the panel's 2px border and the board's own
     16/14/14 — so the numbers here are 52/50/50 rather than a second set of measurements to keep in step by
     hand. (Phase 3's parity gate found the border term missing from both sets.) */
  .home-widget--shell-board{
    padding:52px 50px 50px;
  }
  .home-widget--shell-board::before{
    border-radius:26px;
  }
  .board::before{
    display:none;
  }
  .board .eyebrow{
    font-size:.78rem;
    line-height:1;
    letter-spacing:.05em;
  }
  .board-menu-btn{
    font-size:.72rem;
    padding:6px 10px;
  }
  .board-actions{
    grid-template-columns:1fr;
    margin-top:4px;
  }
  .board-actions .btn{
    min-height:38px;
    padding:8px 10px;
    font-size:.8rem;
  }
  .board-empty-state{
    min-height:260px;
    margin-top:10px;
    padding:14px 12px;
  }
  .board--empty .board-cuisine{
    font-size:1.85rem;
  }
  .event--empty{
    padding:18px 0;
  }
  /* BT Phase 2: THE NARROW-SCREEN OVERRIDES READ THE TOKEN TOO. Without this an author's size would paint
     on a desktop and be silently reverted below 560px — a control that works everywhere except where it is
     hardest to notice. Their own values stay as the fallbacks. */
  .event--empty .what b{
    font-size:var(--tp-title-size,1.28rem);
  }
  /* SF PHASES 3+4, GATE Q10 — the sixth display fallback, and the THIRD that leaves the board (§15.6). It
     must stay after `.home-widget .event--empty .what b` in source order: a media query adds no specificity,
     so at 0,3,1 apiece the later rule is the one that paints below 560px. */
  .home-widget .event--empty .what b{
    font-size:var(--tp-title-size,clamp(1.0952rem,1.4222vw,1.728rem));
  }
  .event--empty .what span{
    font-size:var(--tp-detail-size,.95rem);
  }
}

@media(prefers-reduced-motion:reduce){
  *{transition:none!important;scroll-behavior:auto!important}
}
/* Admin landing page */
.admin-landing{
  background:var(--ink);
  min-height:100vh;
  color:var(--on-dark);
  padding:3rem 0 4rem;
}

.admin-header{
  margin-bottom:3rem;
  padding-bottom:2rem;
  border-bottom:1px solid var(--line-dark);
}

.admin-header h1{
  font-family:var(--serif);
  color:var(--on-dark);
  font-size:clamp(2rem,4vw,3rem);
  margin:0.25rem 0 0.5rem;
}

.admin-header-lead{
  color:var(--slate-soft);
  font-size:1.05rem;
  margin:0;
}

.admin-grid{
  display:grid;
  grid-template-columns:repeat(auto-fill,minmax(280px,1fr));
  gap:1.25rem;
}

.admin-card{
  display:flex;
  align-items:flex-start;
  gap:1rem;
  background:rgba(255,255,255,.06);
  border:1px solid var(--line-dark);
  border-radius:10px;
  padding:1.4rem 1.2rem;
  text-decoration:none;
  color:var(--on-dark);
  transition:background .2s, border-color .2s, transform .15s;
}

.admin-card:hover{
  background:rgba(255,255,255,.11);
  border-color:var(--brass);
  transform:translateY(-2px);
}

.admin-card--highlight{
  border-color:var(--brass);
  background:color-mix(in srgb, var(--brass) 12%, transparent);
}

.admin-card__icon{
  font-size:1.8rem;
  line-height:1;
  flex-shrink:0;
  margin-top:2px;
}

.admin-card__body h2{
  font-family:var(--serif);
  color:var(--on-dark);
  font-size:1.1rem;
  margin:0 0 0.35rem;
}

.admin-card__body p{
  margin:0;
  font-size:.88rem;
  color:var(--brass-soft);
  line-height:1.45;
}

.admin-card__arrow{
  margin-left:auto;
  color:var(--brass-soft);
  font-size:1.2rem;
  align-self:center;
  flex-shrink:0;
}

/* Admin sub-pages */
.admin-sub{
  padding:2rem 24px 4rem;
}

.admin-sub__back{
  margin-bottom:1.5rem;
}

.admin-sub__back .btn-ghost{
  color:var(--heading) !important;
  border-color:var(--line-light);
  background:var(--surface);
}

.admin-sub .btn-ghost{
  color:var(--heading) !important;
  border-color:var(--line-light);
  background:var(--surface);
}

.admin-sub__back .btn-ghost:hover{
  color:var(--heading) !important;
  border-color:var(--brass);
  background:var(--parchment);
}

.admin-sub .btn-ghost:hover{
  color:var(--heading) !important;
  border-color:var(--brass);
  background:var(--parchment);
}

.admin-sub h1{
  font-family:var(--serif);
  color:var(--heading);
  font-size:clamp(1.6rem,3vw,2.4rem);
  margin:0 0 0.75rem;
}

.admin-sub__lead{
  color:var(--slate-soft);
  margin:0 0 2rem;
  max-width:60ch;
}

.admin-sub__grid{
  display:grid;
  grid-template-columns:360px 1fr;
  gap:2rem;
  align-items:start;
}

@media(max-width:760px){
  .admin-sub__grid{grid-template-columns:1fr}
}

.admin-section-title{
  font-family:var(--serif);
  color:var(--heading);
  font-size:1.15rem;
  margin:2rem 0 1rem;
  padding-bottom:0.5rem;
  border-bottom:2px solid var(--brass);
  display:inline-block;
}

.admin-panel{
  background:var(--surface);
  border:1px solid var(--line-light);
  border-radius:10px;
  padding:1.5rem;
}

.admin-panel h2{
  font-family:var(--serif);
  color:var(--heading);
  font-size:1.1rem;
  margin:0 0 1rem;
}

.admin-panel h2 + h2,
.admin-panel * + h2{
  margin-top:1.75rem;
}

.admin-form{
  display:flex;
  flex-direction:column;
  gap:0.6rem;
}

.admin-form label, .admin-label{
  font-size:.83rem;
  font-weight:600;
  color:var(--heading);
  letter-spacing:.03em;
  margin-bottom:0;
}

.admin-input{
  width:100%;
  padding:.5rem .75rem;
  border:1px solid #ccc;
  border-radius:6px;
  font-family:var(--sans);
  font-size:.95rem;
  background:var(--surface);
  color:var(--heading);
  transition:border-color .2s;
}

.admin-input:focus{
  outline:none;
  border-color:var(--brass);
}

.admin-optional{
  font-weight:400;
  color:var(--slate-soft);
  font-size:.8rem;
}

.admin-empty{
  color:var(--slate-soft);
  font-style:italic;
  font-size:.9rem;
}

/* MB Phase 3 (/Admin/Bodies). Three small pieces the admin-* family did not have: a per-field error, a
   checkbox that sits beside its words, and the twelve-month grid a meeting pattern is chosen on.

   Every colour here is a THEME token, including the accent — .sp-tag on the notification pages reaches
   for a literal #9A6A1F for the same job, which is Heritage's brass painted onto all eleven palettes. A
   new page must not spread that. */
.admin-error{
  color:var(--bad-ink);
  font-size:.83rem;
  font-weight:500;
}

.admin-error:empty{
  display:none;
}

/* Specificity has to beat `.admin-form label`, which is 0,1,1 and would otherwise force these into the
   uppercase-ish field-label style meant for the labels ABOVE an input. */
.admin-check,
.admin-form .admin-check{
  display:flex;
  align-items:center;
  gap:.45rem;
  font-size:.9rem;
  font-weight:500;
  color:var(--heading);
  letter-spacing:normal;
}

.admin-check input{
  margin:0;
  accent-color:var(--brass-ink);
}

.admin-months{
  display:flex;
  flex-wrap:wrap;
  gap:.35rem 1rem;
}

.admin-months .admin-check{
  min-width:4.6rem;
}

/* Which body the right-hand panel is showing. A left rule rather than a fill, because the row sits on
   --surface and a tinted row would need a colour that works on all eleven palettes. */
.admin-list__item.is-selected{
  border-left:3px solid var(--brass);
  padding-left:.6rem;
}

/* Two panels in one cell of .admin-sub__grid. The grid gives each CHILD a cell, so a page with a list and
   an editor on the same side needs a box to stack them in — otherwise the second panel lands back under
   the narrow form column. */
.admin-stack{
  display:flex;
  flex-direction:column;
  gap:2rem;
}

/* MR Phase 1: a list row that carries more than one control. The row itself is already a flex with
   space-between, so the actions need their own box or the second form wraps under the first. */
.admin-list__actions{
  display:flex;
  align-items:center;
  gap:.6rem;
  flex-wrap:wrap;
  justify-content:flex-end;
}

.admin-inline-form{
  display:flex;
  align-items:center;
  gap:.4rem;
}

.admin-inline-form .admin-input{
  width:auto;
  min-width:7.5rem;
  margin:0;
}

/* Present to a screen reader, absent to the eye. Used where a control's purpose is obvious in context
   but a bare input would reach assistive technology with no label at all. */
.admin-visually-hidden{
  position:absolute;
  width:1px;
  height:1px;
  padding:0;
  margin:-1px;
  overflow:hidden;
  clip:rect(0 0 0 0);
  clip-path:inset(50%);
  white-space:nowrap;
  border:0;
}

.admin-group-label{
  font-size:.72rem;
  letter-spacing:.1em;
  text-transform:uppercase;
  color:var(--brass-ink);
  font-weight:700;
  margin:1rem 0 0.3rem;
}

.admin-list{
  list-style:none;
  padding:0;
  margin:0;
  display:flex;
  flex-direction:column;
  gap:0;
}

.admin-list__item{
  display:flex;
  justify-content:space-between;
  align-items:flex-start;
  gap:1rem;
  padding:.75rem 0;
  border-bottom:1px solid var(--line-light);
}

.admin-list__item:last-child{
  border-bottom:none;
}

.admin-list__info{
  display:flex;
  flex-direction:column;
  gap:2px;
  font-size:.9rem;
  min-width:0;
}

.admin-list__meta{
  font-size:.82rem;
  color:var(--slate-soft);
}

/* ITEM 64 SLICE 4 — THE NEUTRAL SIBLING, WRITTEN EIGHT DAYS AFTER ITS FIRST USE.

   `.admin-btn` was worn by twelve controls over six admin pages and HAD NO RULE HERE AT ALL. Only
   `.admin-btn-delete` did, and a class name that matches nothing fails in total silence: no build
   warning, no test, nothing on screen but a native browser button among the pill chips. The user found
   it on /Admin/Documents in a screenshot — the seventh release running where a picture found what no
   check did, which is why AdminClassRuleTests now sweeps the whole prefix.

   IT IS DECLARED BEFORE .admin-btn-delete AND THAT IS LOAD-BEARING. /Admin/Requests' decline button
   wears `admin-btn admin-btn-delete`; the two rules are the same specificity (0,1,0), so the later one
   wins. Writing this rule after the delete one would have repainted every destructive admin button in
   the neutral colours, silently, and only on the pages that happen to pair them.

   THE METRICS ARE THE SELECT'S, NOT THE DELETE BUTTON'S. Six of the twelve uses are the Search button
   inside .history-size-form, standing next to a select at .86rem/.32rem .5rem — and a button that does
   not sit level with the control it submits is the misalignment the screenshot actually showed.
   .admin-btn-delete's own .78rem/.25rem .7rem measures about 23px, which is right for a row of card
   actions and short beside a filter. Where the two DO sit together the heights are equalised by the
   scoped 44px rule further down this file, which is the half that makes them read as a pair. */
.admin-btn{
  background:var(--surface);
  color:var(--heading);
  border:1px solid var(--line-light);
  border-radius:8px;
  font-size:.86rem;
  font-weight:600;
  padding:.32rem .8rem;
  white-space:nowrap;
  flex-shrink:0;
  cursor:pointer;
  text-decoration:none;
}

.admin-btn:hover{
  border-color:var(--brass);
  color:var(--heading);
}

.admin-btn-delete{
  background:transparent;
  color:var(--claret);
  border:1px solid var(--claret);
  font-size:.78rem;
  padding:.25rem .7rem;
  white-space:nowrap;
  flex-shrink:0;
}

.admin-btn-delete:hover{
  background:var(--bad-ink);
  color:var(--bad-fill);
}

.admin-suggest-btn{
  background:var(--surface);
  color:var(--heading);
  border-color:var(--line-light);
}

.admin-suggest-btn:hover{
  background:var(--parchment);
  color:var(--heading);
  border-color:var(--brass);
}

.user-mgmt-top{
  display:flex;
  justify-content:space-between;
  align-items:flex-start;
  gap:1rem;
  margin-bottom:1.35rem;
}

.user-mgmt-top .admin-sub__lead{
  margin-bottom:0;
}

.user-mgmt-toolbar{
  display:flex;
  align-items:flex-start;
  gap:.65rem;
}

.user-mgmt-filter-form{
  display:flex;
  align-items:center;
  gap:.65rem;
}

.user-mgmt-search{
  min-width:220px;
  background:var(--surface);
}

.user-mgmt-filter{
  min-width:150px;
  background:var(--surface);
}

.user-mgmt-search-btn{
  white-space:nowrap;
}

.user-mgmt-create{
  position:relative;
}

.user-mgmt-create summary{
  list-style:none;
}

.user-mgmt-create summary::-webkit-details-marker{
  display:none;
}

.user-mgmt-add-btn{
  white-space:nowrap;
}

.user-mgmt-create-panel{
  position:absolute;
  right:0;
  top:44px;
  width:min(700px,92vw);
  z-index:50;
  border-radius:12px;
  box-shadow:0 16px 38px rgba(23,34,65,.18);
}

.user-mgmt-create-panel h2{
  margin-bottom:.75rem;
}

.user-mgmt-create-grid{
  display:grid;
  grid-template-columns:repeat(2,minmax(0,1fr));
  gap:.7rem .9rem;
}

.user-mgmt-input{
  background:var(--surface);
  border-color:#dde5f2;
  border-radius:10px;
  padding:.58rem .75rem;
}

/* NS Phase 2 / hook 3: the invitation toggle on the add-user panel. Inline row rather than a
   .form-field so the checkbox sits against its own label instead of below a block one. */
.user-mgmt-invite{
  display:flex;
  align-items:flex-start;
  gap:.5rem;
  margin:.75rem 0 .25rem;
  font-size:.9rem;
  line-height:1.45;
  cursor:pointer;
}

.user-mgmt-invite input[type="checkbox"]{
  margin-top:.2rem;
  flex:0 0 auto;
}

.user-mgmt-create-actions{
  display:flex;
  gap:.5rem;
  margin-top:.4rem;
}

.user-mgmt-primary-btn,
.user-mgmt-secondary-btn{
  border-radius:10px;
}

.user-mgmt-users{
  border-radius:14px;
}

.user-mgmt-users-head{
  display:flex;
  align-items:center;
  gap:.7rem;
  margin-bottom:1rem;
}

.user-mgmt-users-head h2{
  margin:0;
}

.user-mgmt-users-head p{
  margin:0;
  color:var(--slate-soft);
  font-size:.88rem;
}

.user-mgmt-users-icon{
  width:42px;
  height:42px;
  border-radius:50%;
  display:inline-flex;
  align-items:center;
  justify-content:center;
  background:var(--parchment-2);
}

.user-mgmt-table-wrap{
  border:1px solid #e6ebf5;
  border-radius:12px;
  overflow:visible;
}

.user-mgmt-table-head{
  display:grid;
  grid-template-columns:2.1fr 1.8fr 2.2fr 1fr 1.2fr 72px;
  gap:.65rem;
  padding:.78rem 1rem;
  border-bottom:1px solid var(--line-light);
  background:var(--parchment-2);
  font-size:.72rem;
  font-weight:700;
  letter-spacing:.08em;
  text-transform:uppercase;
  color:var(--slate-soft);
}

.user-mgmt-sort-link{
  display:inline-flex;
  align-items:center;
  gap:.28rem;
  text-decoration:none;
  color:var(--slate-soft);
}

.user-mgmt-sort-link:hover{
  color:var(--brass-ink);
}

.user-mgmt-sort-indicator{
  min-width:.72rem;
  font-size:.64rem;
  line-height:1;
}

.user-mgmt-list{
  margin:0;
}

.user-mgmt-item{
  display:grid;
  grid-template-columns:2.1fr 1.8fr 2.2fr 1fr 1.2fr 72px;
  align-items:center;
  gap:.65rem;
  padding:.9rem 1rem;
}

.user-mgmt-user{
  display:flex;
  align-items:center;
  gap:.68rem;
  min-width:0;
}

.user-mgmt-avatar{
  width:40px;
  height:40px;
  border-radius:999px;
  display:inline-flex;
  align-items:center;
  justify-content:center;
  background:#0f315e;
  color:#fff;
  font-weight:700;
  flex-shrink:0;
}

.user-mgmt-email-cell,
.user-mgmt-last-active{
  color:var(--slate-soft);
  font-size:.9rem;
  overflow-wrap:anywhere;
}

/* Item 29. The cell held a hardcoded em-dash and needed no rules of its own; it now holds a link into the
   activity trail filtered to that person, and a second line. Both values come from tokens this screen
   already paints with, so the column introduces no new colour to the eleven palettes. */
.user-mgmt-last-active a{
  color:var(--claret);
  text-decoration:none;
  border-bottom:1px solid rgba(111,36,53,.3);
}
.user-mgmt-last-active a:hover,
.user-mgmt-last-active a:focus-visible{border-bottom-color:var(--claret)}
.user-mgmt-last-active small{
  display:block;
  font-size:.72rem;
  color:var(--slate-soft);
  line-height:1.4;
}

/* /Admin/Audit's detail panel. The email log's .sp-detail is a grid of blocks; this one is a definition
   list, because every line of it is a label and a value and a <dl> is what that is. */
.sp-detail dl{
  margin:0;
  display:grid;
  grid-template-columns:auto 1fr;
  gap:6px 18px;
  font-size:.82rem;
}
.sp-detail dt{color:var(--slate-soft);font-weight:600}
.sp-detail dd{margin:0;color:var(--heading)}

.user-mgmt-role-badges{
  display:flex;
  flex-wrap:wrap;
  gap:.35rem;
}

.user-role-pill{
  display:inline-flex;
  align-items:center;
  padding:.12rem .52rem;
  border-radius:999px;
  font-size:.75rem;
  font-weight:600;
  border:1px solid #cfd8e7;
  color:#1D2126;
  background:#f6f8fc;
}

.user-role-pill--administrator{background:#eef3ff;border-color:#bed0ff;color:#1D2126}
.user-role-pill--steward{background:#f1fbe8;border-color:#c8e3a8;color:#1D2126}
.user-role-pill--treasurer{background:#fff6e8;border-color:#f0d3a5;color:#1D2126}
.user-role-pill--secretary{background:#f7ecff;border-color:#dfc3ff;color:#1D2126}
.user-role-pill--charity-steward{background:#ebfff6;border-color:#bdecd6;color:#1D2126}
.user-role-pill--almoner{background:#fff1f1;border-color:#f6cbcb;color:#1D2126}
.user-role-pill--worshipful-master{background:#eef7ff;border-color:#bfdaff;color:#1D2126}

.user-status-pill{
  display:inline-flex;
  align-items:center;
  border-radius:999px;
  padding:.15rem .58rem;
  font-size:.75rem;
  font-weight:600;
  border:1px solid transparent;
}

.user-status-pill--active{
  background:#eff9eb;
  color:#396c28;
  border-color:#cde7c3;
}

.user-status-pill--disabled{
  background:#fff2f3;
  color:#8f2632;
  border-color:#ebbdc4;
}

.user-mgmt-action-cell{
  display:flex;
  justify-content:center;
}

.user-mgmt-actions-menu{
  position:relative;
}

.user-mgmt-actions-menu summary{
  list-style:none;
  width:30px;
  height:30px;
  border-radius:6px;
  border:1px solid var(--line-light);
  background:var(--surface);
  display:flex;
  align-items:center;
  justify-content:center;
  font-size:1rem;
  color:var(--heading);
  cursor:pointer;
}

.user-mgmt-actions-menu summary::-webkit-details-marker{
  display:none;
}

.user-mgmt-actions-panel{
  position:absolute;
  right:0;
  top:34px;
  min-width:224px;
  border:1px solid #d6dfec;
  border-radius:8px;
  background:var(--surface);
  box-shadow:0 8px 18px rgba(0,0,0,.1);
  padding:.4rem;
  z-index:40;
  display:flex;
  flex-direction:column;
  gap:.15rem;
}

.user-mgmt-add-role{
  display:flex;
  align-items:center;
  gap:.3rem;
  margin-bottom:.2rem;
  padding-bottom:.3rem;
  border-bottom:1px solid #edf1f7;
}

.user-mgmt-add-role .admin-input{
  font-size:.82rem;
  padding:.3rem .42rem;
  background:var(--surface);
  border-color:#d6deea;
  border-radius:6px;
}

.user-mgmt-actions-panel .btn{
  border-radius:6px;
  padding:.3rem .55rem;
  font-size:.8rem;
  min-height:30px;
}

.user-mgmt-actions-panel .btn:hover{
  transform:none;
}

.user-mgmt-action-link{
  width:100%;
  text-align:left;
  border:none;
  background:transparent;
  color:var(--heading);
  padding:.34rem .42rem;
  border-radius:6px;
  font-size:.84rem;
}

.user-mgmt-action-link:hover{
  background:var(--parchment-2);
}

.user-mgmt-action-link--danger{
  color:var(--claret);
}

.user-mgmt-action-link--danger:hover{
  background:var(--bad-fill);
  color:var(--bad-ink);
}

.user-mgmt-pager{
  justify-content:space-between;
}

.user-mgmt-page-size-form{
  display:flex;
  align-items:center;
  gap:.45rem;
}

.user-mgmt-page-size-form label{
  font-size:.84rem;
  color:var(--slate-soft);
  margin:0;
}

.user-mgmt-page-size-form .admin-input{
  width:auto;
  min-width:76px;
}

@media(max-width:1100px){
  .user-mgmt-top{
    flex-direction:column;
  }
}

@media(max-width:980px){
  .user-mgmt-create-panel{
    position:static;
    width:100%;
    margin-top:.65rem;
  }
}

@media(max-width:860px){
  .user-mgmt-filter-form{
    flex-wrap:wrap;
  }
  .user-mgmt-table-head{display:none}
  .user-mgmt-item{
    grid-template-columns:1fr;
    align-items:flex-start;
    gap:.5rem;
  }
  .user-mgmt-action-cell{
    justify-content:flex-start;
  }
  .user-mgmt-actions-panel{
    right:auto;
    left:0;
  }
  .user-mgmt-create-grid{
    grid-template-columns:1fr;
  }
  .user-mgmt-pager{
    justify-content:flex-start;
  }
}

.raffle-admin-grid{
  display:grid;
  grid-template-columns:repeat(2,minmax(0,1fr));
  gap:1.25rem;
}

.raffle-draw-panel__head{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:.8rem;
  margin-bottom:.6rem;
}

.raffle-draw-panel__head h2{
  margin:0;
}

.raffle-popout-btn{
  white-space:nowrap;
}

.raffle-draw-panel.is-popout{
  position:fixed;
  inset:0;
  z-index:1200;
  width:100vw;
  height:100vh;
  max-width:none !important;
  margin:0 !important;
  border-radius:0;
  overflow:auto;
  padding:1.5rem;
  background:var(--parchment);
}

body.raffle-popout-open{
  overflow:hidden;
}

.raffle-admin-actions{
  display:flex;
  gap:.7rem;
  flex-wrap:wrap;
  margin:1rem 0 1.2rem;
}

.raffle-setup-table-wrap{
  margin-top:.65rem;
  overflow:auto;
}

.raffle-setup-table{
  width:100%;
  border-collapse:collapse;
  font-size:.88rem;
}

.raffle-setup-table th,
.raffle-setup-table td{
  padding:.45rem .35rem;
  text-align:left;
  border-bottom:1px solid var(--line-light);
}

.raffle-setup-table th{
  font-size:.75rem;
  letter-spacing:.07em;
  text-transform:uppercase;
  color:var(--slate-soft);
  font-weight:700;
}

.raffle-setup-actions{
  display:inline-block;
}

.raffle-setup-actions__toggle{
  list-style:none;
  display:inline-flex;
  align-items:center;
  justify-content:center;
  gap:8px;
  font-weight:600;
  font-size:.82rem;
  padding:.3rem .65rem;
  border-radius:999px;
  border:1px solid var(--line-light);
  background:var(--surface);
  color:var(--heading);
  cursor:pointer;
}

.raffle-setup-actions__toggle::-webkit-details-marker{
  display:none;
}

.raffle-setup-actions[open] .raffle-setup-actions__toggle{
  border-color:var(--brass);
  background:var(--parchment);
}

.raffle-setup-actions__panel{
  margin-top:.5rem;
  display:flex;
  flex-direction:column;
  align-items:flex-start;
  gap:.34rem;
  max-width:130px;
}

.raffle-setup-actions__form{
  display:flex;
  align-items:center;
  gap:.34rem;
  flex-wrap:nowrap;
  width:100%;
}

.raffle-setup-actions__input{
  width:48px;
  padding:.14rem .28rem;
  min-height:0;
  font-size:.78rem;
}

.raffle-setup-actions__row{
  display:flex;
  align-items:center;
  gap:.34rem;
  flex-wrap:nowrap;
}

.raffle-setup-actions__inline-form{
  margin:0;
}

.raffle-setup-btn{
  appearance:none;
  border:1px solid var(--line-light);
  border-radius:8px;
  background:var(--surface);
  color:var(--heading);
  font-size:.74rem;
  font-weight:600;
  line-height:1.1;
  padding:.16rem .42rem;
  cursor:pointer;
  white-space:nowrap;
}

.raffle-setup-btn:hover{
  border-color:var(--brass);
  background:var(--parchment);
}

.raffle-setup-btn--primary{
  background:var(--surface);
}

.raffle-setup-btn--danger{
  color:var(--claret);
  border-color:var(--claret);
}

.raffle-setup-btn--danger:hover{
  color:var(--bad-fill);
  background:var(--bad-ink);
  border-color:var(--bad-ink);
}

.raffle-setup-btn--neutral{
  color:var(--slate-soft);
}

.raffle-setup-btn--neutral:hover{
  color:var(--heading);
}

.raffle-machine{
  margin-top:1rem;
  padding:1.15rem 1rem 1rem;
  border:1px solid color-mix(in srgb, var(--brass-soft) 35%, transparent);
  border-radius:16px;
  background:
    radial-gradient(circle at 50% 44%, rgba(232,184,75,.18), transparent 48%),
    radial-gradient(circle at 18% 50%, rgba(232,184,75,.12), transparent 42%),
    radial-gradient(circle at 82% 50%, rgba(232,184,75,.12), transparent 42%),
    linear-gradient(140deg,#071631,#0D2554 50%, #081734);
  text-align:center;
  box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--brass-soft) 18%, transparent), 0 14px 28px rgba(7,19,42,.35);
}

.raffle-machine__frame{
  display:grid;
  grid-template-columns:auto minmax(0,1fr) auto;
  align-items:center;
  gap:.7rem;
}

.raffle-machine__emblem{
  width:42px;
  height:42px;
  opacity:.96;
  filter:drop-shadow(0 0 6px color-mix(in srgb, var(--brass-soft) 45%, transparent));
}

.raffle-machine__emblem img{
  width:100%;
  height:100%;
  object-fit:contain;
}

.raffle-machine__drum{
  width:min(360px,100%);
  margin:0 auto .6rem;
  padding:.6rem;
  border-radius:24px;
  background:linear-gradient(180deg,#1d2c4f,#0a1837);
  border:2px solid color-mix(in srgb, var(--brass-soft) 65%, transparent);
  box-shadow:inset 0 0 0 2px color-mix(in srgb, var(--brass-soft) 18%, transparent), 0 0 22px color-mix(in srgb, var(--brass-soft) 24%, transparent);
}

.raffle-machine__window{
  background:linear-gradient(180deg,#101010,#232323 48%,#121212);
  border:1px solid color-mix(in srgb, var(--brass-soft) 35%, transparent);
  border-radius:16px;
  padding:.55rem .8rem;
  font-family:var(--serif);
  font-size:2.85rem;
  letter-spacing:.18em;
  color:#F4D582;
  line-height:1;
  text-shadow:0 0 12px rgba(244,213,130,.62);
  box-shadow:inset 0 10px 20px rgba(255,255,255,.06), inset 0 -8px 12px rgba(0,0,0,.35);
}

.raffle-machine__status{
  margin:.2rem 0 0;
  color:#F2E4BF;
  font-family:var(--serif);
  font-size:2rem;
  font-weight:600;
}

.raffle-machine__sub{
  margin:.1rem 0 0;
  font-size:1.1rem;
  color:#D9C48E;
}

.raffle-machine.is-spinning .raffle-machine__window{
  animation:raffle-flicker .12s steps(2,end) infinite;
}

.raffle-machine.is-winning{
  animation:raffle-celebrate .6s ease-in-out 4;
  border-color:var(--brass-soft);
  box-shadow:0 0 0 4px color-mix(in srgb, var(--brass) 22%, transparent), 0 0 32px color-mix(in srgb, var(--brass-soft) 28%, transparent);
}

@keyframes raffle-flicker{
  0%,100%{transform:translateY(0)}
  50%{transform:translateY(1px)}
}

@keyframes raffle-celebrate{
  0%,100%{transform:translateY(0)}
  25%{transform:translateY(-4px)}
  50%{transform:translateY(0)}
  75%{transform:translateY(-2px)}
}

.raffle-history{
  display:grid;
  gap:1rem;
}

.raffle-draw-btn{
  width:100%;
  justify-content:center;
  gap:.55rem;
  padding:12px 20px;
  border-radius:12px;
  border:1px solid var(--brass-soft);
  background:linear-gradient(180deg,#F3DC9A,#D2A94E);
  color:#1A2336;
  font-size:1.12rem;
  font-family:var(--serif);
}

.raffle-draw-btn:hover{
  background:linear-gradient(180deg,#F6E6B4,#DEB564);
  color:#1A2336;
}

.raffle-draw-btn[disabled]{
  opacity:.72;
}

.raffle-result--latest.raffle-result--pending{
  opacity:0;
  transform:translateY(8px);
  pointer-events:none;
}

.raffle-result--latest.raffle-result--reveal{
  animation:raffle-result-reveal .5s ease-out;
}

@keyframes raffle-result-reveal{
  0%{opacity:0;transform:translateY(8px)}
  100%{opacity:1;transform:translateY(0)}
}

.raffle-history__item{
  border:1px solid var(--line-light);
  border-radius:10px;
  padding:1rem 1.1rem;
  background:var(--surface);
}

.raffle-history__item h3{
  margin:0 0 .25rem;
  font-size:1.05rem;
  color:var(--heading);
}

.widgets-admin-form{
  margin-top:1rem;
}

.widgets-layout{
  display:grid;
  gap:1.15rem;
}

.widgets-zone{
  border:1px solid var(--line-light);
  border-radius:14px;
  background:var(--surface);
  padding:1rem;
  /* SCC Phase 7b: a grid item's `min-width` is `auto`, which means it CANNOT SHRINK BELOW ITS CONTENT — so
     the moment the home board was laid out at a fixed 1132px, this zone grew to 1156px to hold it, took the
     whole admin page sideways with it, and the frame that was supposed to clip the board reported a width
     WIDER than the board. The scale then computed as 1 and the fixed-width surface silently did nothing.
     Same trap as the bare `1fr` tracks the 560px query had to fix. */
  min-width:0;
}

.widgets-zone__head{
  margin-bottom:.75rem;
}

.widgets-zone__head h2{
  margin:0 0 .25rem;
  font-size:1.15rem;
  color:var(--heading);
}

.widgets-zone__head p{
  margin:0;
  color:var(--slate-soft);
  font-size:.9rem;
}

/* SCC PHASE 7b — THE BOARD LAYOUT PANEL (§23.13 Q2/Q9). It reuses `.widgets-zone` for its frame, so the
   panel and the two drag zones are the same object to a reader of the page; only its rows are new.

   Every colour here is a theme token on a theme ground, which the crawler can measure: --heading on the
   zone's --surface, --slate-soft for the hints, and the selected segment is a --brass FILL with the DERIVED
   --on-brass on it (never --brass as text, which is 1.75:1 on College). */
.board-panel__rows{
  display:grid;
  gap:.85rem;
}

.board-panel__row{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:1rem;
  flex-wrap:wrap;
}

.board-panel__label{
  display:flex;
  flex-direction:column;
  gap:.15rem;
  color:var(--heading);
  font-size:.92rem;
  font-weight:600;
  min-width:0;
}

.board-panel__hint{
  color:var(--slate-soft);
  font-size:.78rem;
  font-weight:400;
  line-height:1.4;
}

.board-panel__field{
  display:inline-flex;
  align-items:center;
  gap:.4rem;
  flex:0 0 auto;
}

.board-panel__field .admin-input{
  width:6.5rem;
}

.board-panel__unit{
  color:var(--slate-soft);
  font-size:.8rem;
}

.board-panel__actions{
  margin-top:1rem;
}

/* The segmented control is native radios wrapped in their labels — the same shape as 7a's section-style
   chips, and for the same reason: arrow keys, focus and posting are the browser's job, and there is no
   hidden field for site.js to keep in step. `:checked +` paints the selection, so no class drifts either. */
.board-seg{
  display:inline-flex;
  border:1px solid var(--line-light);
  border-radius:8px;
  overflow:hidden;
  flex:0 0 auto;
}

/* BACKLOG §1: the same one-line repair as `.widget-shellchip`, applied here PREVENTATIVELY. This segment is
   on the board panel rather than in the edit dialog, so its escaping radio has never been seen to cost
   anything — but it is the identical defect, and the next positioned ancestor it finds is not this label. */
.board-seg__opt{
  position:relative;
  margin:0;
}

/* The radio is the control and the segment is its label, so the native input is taken out of the picture
   visually and left in it for the keyboard — the same shape 7a's section-style chips use. 1px rather than 0,
   and never display:none, because a zero-sized or hidden radio is one some browsers refuse to focus. */
.board-seg__opt input{
  position:absolute;
  width:1px;
  height:1px;
  opacity:0;
  pointer-events:none;
}

.board-seg__opt span{
  display:block;
  padding:.4rem .8rem;
  min-width:2.6rem;
  text-align:center;
  background:var(--surface);
  color:var(--heading);
  font-size:.88rem;
  cursor:pointer;
  border-left:1px solid var(--line-light);
}

.board-seg__opt:first-child span{
  border-left:0;
}

.board-seg__opt input:checked + span{
  background:var(--brass);
  color:var(--on-brass);
  font-weight:600;
}

.board-seg__opt input:focus-visible + span{
  outline:2px solid var(--brass-ink);
  outline-offset:-2px;
}

/* SCC PHASE 7b — THE DESIGNER STOPS BEING A SECOND STATEMENT OF THE BOARD'S SHAPE (§23.10, §23.13 Q13).
   This rule used to be `grid-template-columns:repeat(3, minmax(0, 1fr))` with a `gap:1rem` and a column
   guide drawn at 33.333%. Three was a literal here exactly as it was a literal in the two narrow queries on
   the real grid, so the moment columns became a setting the drag surface would have been drawing a board
   the site no longer had: set two columns, come back, and arrange cards against a picture of three.

   The home board now reads the same tokens the real one does (see .widgets-board--home below). This base
   rule keeps a fixed three only for the SCRATCH pad, which is a stash and does not claim to be the board. */
.widgets-board{
  display:grid;
  grid-template-columns:repeat(3, minmax(0, 1fr));
  gap:1rem;
  align-items:start;
  min-height:7rem;
  padding:.7rem;
  border:1px dashed #c4cfde;
  border-radius:10px;
  background:
    linear-gradient(0deg, rgba(238,242,248,.9), rgba(238,242,248,.9)),
    repeating-linear-gradient(
      to right,
      transparent 0,
      transparent calc(33.333% - .5rem),
      rgba(188,199,216,.45) calc(33.333% - .5rem),
      rgba(188,199,216,.45) calc(33.333% - .45rem)
    );
}

/* THE HOME ZONE IS DRAWN AT THE BOARD'S OWN WIDTH, SCALED TO FIT.
   Both admin boards were fluid `1fr` tracks with a 1rem gap, and a fluid track inside an admin panel is a
   lie about a real card: a third of a 900px admin column is 292px against the real board's 361px, and the
   gap was 16px against the site's 24px. Harmless while the shape was fixed and the author learnt it; not
   harmless once the author is CHOOSING the shape.

   So the drag surface is laid out at HomeBoardLayout.BoardContentWidth (1132px — `--maxw` minus `.wrap`'s
   24px each side, measured rather than chosen), with the board's real column count and its real gap in
   real pixels, and then scaled down by whatever fraction of 1132 the admin column actually has. Every
   width on it is then the width the visitor gets, times one constant.

   `box-sizing:content-box` is load-bearing: the file sets `*{box-sizing:border-box}`, so without it the
   .7rem padding and the dashed border would be subtracted from 1132 and the tracks would be 24px narrow.

   The scale itself is set by site.js on `--widgets-board-scale` (it needs a measurement, which CSS cannot
   take), and defaults to 1 — so before the script runs the surface is honest and merely too wide for its
   frame, which the frame clips, rather than being wrong about proportions. */
.widgets-board--home{
  box-sizing:content-box;
  width:var(--widgets-board-width, 1132px);
  grid-template-columns:repeat(var(--board-columns),minmax(0,1fr));
  gap:var(--board-gap);
  transform-origin:top left;
  transform:scale(var(--widgets-board-scale, 1));
  background:
    linear-gradient(0deg, rgba(238,242,248,.9), rgba(238,242,248,.9)),
    repeating-linear-gradient(
      to right,
      transparent 0,
      transparent calc(100% / var(--board-columns) - .5rem),
      rgba(188,199,216,.45) calc(100% / var(--board-columns) - .5rem),
      rgba(188,199,216,.45) calc(100% / var(--board-columns) - .45rem)
    );
}

/* A transform does not change layout, so the frame would keep the UNSCALED height and leave a gap under
   the board. site.js sets the frame's height from the scaled one; `overflow:hidden` is what makes the
   pre-script state (scale 1, too wide) clip instead of pushing the admin page sideways. */
.widgets-boardframe{
  overflow:hidden;
}

.widgets-board--scratch{
  grid-template-columns:repeat(2, minmax(0, 1fr));
  background:
    linear-gradient(0deg, rgba(250,252,255,.95), rgba(250,252,255,.95)),
    repeating-linear-gradient(
      to right,
      transparent 0,
      transparent calc(50% - .5rem),
      rgba(202,211,224,.42) calc(50% - .5rem),
      rgba(202,211,224,.42) calc(50% - .45rem)
    );
}

.widgets-card{
  position:relative;
  background:var(--surface);
  border:1px solid #c3cedd;
  border-radius:10px;
  padding:1rem 1.1rem 1.1rem;
  cursor:move;
  height:190px;
  display:flex;
  flex-direction:column;
  overflow:hidden;
  box-shadow:0 6px 16px rgba(13,26,49,.06);
  transition:border-color .16s ease, box-shadow .16s ease, transform .16s ease;
}

.widgets-card--narrow{
  grid-column:span 1;
}

.widgets-card--wide{
  grid-column:span 2;
}

.widgets-card--full{
  grid-column:1 / -1;
}

/* ---------------------------------------------------------------------------------------------------------
   SCC PHASE 7e — THE DESIGNER IS THE BOARD IN BOTH VIEWS (plan §23.10, §23.13 Q13).
   7b made this surface read the board's column count and gap so it could not draw a board the site no longer
   had. A layout mode is the same problem one step further on: a full-width card ignores the span stepper on
   the real board, so a drag surface that still drew it one column wide would be a picture of a card nobody
   has. These two rules are not part of sequence mode — they are true in the grid view as well, because they
   are true of the site in both.

   Written as `[data-widget-layout-mode="…"]` and never as `:not([data-widget-layout-mode])`: §26.1 found that
   Razor renders a null data attribute rather than dropping it, so every ordinary card carries an EMPTY one.
   Asking about the value is safe where asking about the attribute's presence would have matched everything.
   --------------------------------------------------------------------------------------------------------- */
.widgets-card[data-widget-layout-mode="bleed"],
.widgets-card[data-widget-layout-mode="window"]{
  grid-column:1 / -1;
}

/* SEQUENCE MODE — the same drag surface, presented as what a snapping board actually is: one card per
   window, with the ordinary rows still in between. It is a class on the board and nothing else, so every
   drag, drop and reorder handler is untouched by construction.

   The cards do NOT stand at their real height, and that is the same decision §23.10 made about the grid
   view: this is a plan, not a preview. A window is roughly 830px at a common laptop size, and six of those
   would put the save button four screens down. They stand TALLER than an ordinary card, which is what
   carries the meaning, and the chip says the rest. */
.widgets-board--sequence .widgets-card[data-widget-layout-mode="window"]{
  height:300px;
}

/* THE BRASS EDGE MARKS A SNAP STOP, which is the one thing sequence mode exists to show. It is drawn as a
   border-left rather than an outline so it takes part in the card's own rounding, and it is on the WINDOW
   mode alone: a full-width card is not a stop, because a stop is a card the scroll can come to rest on and
   only a window-tall one can be that. */
.widgets-board--sequence .widgets-card[data-widget-layout-mode="window"]{
  border-left:4px solid var(--brass);
}

/* ---------------------------------------------------------------------------------------------------------
   SF PHASE 1 — THE SEAM STRIP (plan section 6.7, gate Q5 on the recommendation, migration 071).

   THE ONE NEW SHAPE IN THE WHOLE PHASE. Everything inside it is borrowed: the two closed vocabularies are
   `.board-seg`, the same segmented radio group the board panel's Columns, Radius and Snap rows use; the
   picture row is the shared media dialog already on this page; and the derived line is the same "here is the
   number and here is what it means" sentence the Background panel and the block Text panel both print.

   IT EXISTS ONLY IN SEQUENCE VIEW AND IS IN THE DOM IN BOTH, and both halves of that matter. Sequence is the
   only presentation in which "between these two" is a PLACE — the grid lays the cards out in rows, where the
   gap between two of them is not one thing. But the strips post with the card layout, so a Grid view that had
   REMOVED them from the page would silently delete every seam on the board on every save from that view.
   `display:none` keeps them posted and takes them out of the grid's flow, which a grid item has to be for the
   board to draw the columns it draws today.
   --------------------------------------------------------------------------------------------------------- */
.widgets-board--home .widgets-seam{
  display:none;
}

.widgets-board--home.widgets-board--sequence .widgets-seam{
  display:block;
  grid-column:1 / -1;
  margin:6px 0;
  border:1px dashed var(--line-light);
  border-radius:6px;
  background:var(--parchment-2);
}

/* A SEAM THAT IS DOING SOMETHING IS DRAWN IN BRASS, because the strip's whole job in the closed state is to
   say whether anything happens here. Solid rather than dashed for the same reason: a dashed border reads as a
   placeholder, and a set seam is not one. */
.widgets-board--home.widgets-board--sequence .widgets-seam--set{
  border-style:solid;
  border-color:color-mix(in srgb, var(--brass) 55%, transparent);
  background:color-mix(in srgb, var(--brass) 8%, var(--surface));
}

/* A REFUSED STRIP DIMS AND KEEPS ITS SENTENCE (gate section 6.8). It does not disappear: a row that vanished
   would leave an author wondering whether the feature exists at all, which is the reasoning the Background
   panel's rows dim rather than hide under. */
.widgets-board--home.widgets-board--sequence .widgets-seam--off{
  opacity:.62;
}

.widgets-seam__head{
  display:flex;
  align-items:center;
  gap:.6rem;
  flex-wrap:wrap;
  width:100%;
  padding:.42rem .7rem;
  border:0;
  background:transparent;
  font-family:var(--sans);
  font-size:.82rem;
  color:var(--slate-soft);
  text-align:left;
  cursor:pointer;
}

.widgets-seam__head--static{
  cursor:default;
}

.widgets-seam--set .widgets-seam__head{
  color:var(--brass-ink, var(--claret));
}

.widgets-seam__what{
  margin-left:auto;
  font-weight:700;
}

.widgets-seam__what--why{
  font-weight:400;
  font-style:italic;
}

.widgets-seam__chev{
  color:var(--slate-soft);
}

.widgets-seam__body{
  border-top:1px solid var(--line-light);
  background:var(--surface);
  padding:10px 12px 12px;
  border-radius:0 0 5px 5px;
}

/* THE ROW IS THE SETTINGS PANEL'S ROW IN MINIATURE: a stacked label with its hint, then the control. It wraps
   because this surface is SCALED — the drag board is laid out at the site's 1132px content width and
   transformed down to whatever the admin column has (7b) — so a row that could not wrap would push the whole
   board wider and shrink the scale for everything on it. Backlog section 1b's sideways scroll was that
   mistake in the settings column; here it would be a board drawn at half size. */
.widgets-seam__row{
  display:flex;
  align-items:flex-start;
  gap:.7rem;
  flex-wrap:wrap;
  padding:.3rem 0;
}

.widgets-seam__label{
  display:flex;
  flex-direction:column;
  gap:.1rem;
  min-width:8rem;
  flex:1 1 8rem;
  font-family:var(--sans);
  font-size:.82rem;
  font-weight:600;
  color:var(--heading);
}

.widgets-seam__hint{
  font-weight:400;
  font-size:.74rem;
  color:var(--slate-soft);
}

.widgets-seam__pick{
  display:flex;
  align-items:center;
  gap:.45rem;
  flex-wrap:wrap;
  font-family:var(--sans);
  font-size:.8rem;
  color:var(--heading);
}

/* 48x32 AND THE LIBRARY'S 320px THUMBNAIL BEHIND IT, which is the same choice the Background panel's 64x44
   box made: the thumbnail already exists for every raster in the library, and neither the display derivative
   nor the original has any business being downloaded for a box this size. */
.widgets-seam__thumb{
  display:block;
  width:48px;
  height:32px;
  border-radius:3px;
  overflow:hidden;
  background:var(--parchment-2);
  flex:none;
}

.widgets-seam__thumb img{
  width:100%;
  height:100%;
  object-fit:cover;
  display:block;
}

.widgets-seam__from{
  color:var(--slate-soft);
}

.widgets-seam__btn{
  padding:.24rem .6rem;
  font-size:.76rem;
}

.widgets-seam__floor{
  margin:.5rem 0 0;
  font-family:var(--sans);
  font-size:.76rem;
  line-height:1.45;
  color:var(--slate-soft);
}

.widgets-seam__floor--warn{
  color:var(--claret);
}

/* THE ORDER HAS MOVED UNDER THE STRIPS, SO THEY SAY SO RATHER THAN PRETEND (plan section 6.8). A strip is a
   place in a SEQUENCE, and a drag has just changed that sequence — the row an author is looking at was drawn
   between two cards that may no longer be neighbours. The save recomputes adjacency from the posted positions
   and simply does not write a pair that is no longer a pair, so the data is safe either way; what this rule
   buys is that the author is not editing a control whose answer is about to be discarded. */
.widgets-seam--stale .widgets-seam__body,
.widgets-seam--stale .widgets-seam__chev{
  display:none;
}

.widgets-seam--stale .widgets-seam__what{
  font-weight:400;
  font-style:italic;
}

.widgets-card__status--mode{
  color:#5b4300;
  background:#faf0d2;
}

/* The view toggle. Two buttons rather than radios, unlike every other segmented control on this page,
   because this one posts NOTHING — it is a view of the surface and not a value the board stores, so there is
   no form field for a radio to be. */
.widgets-zone__head--split{
  display:flex;
  align-items:flex-start;
  justify-content:space-between;
  gap:1rem;
  flex-wrap:wrap;
}

.widgets-viewseg{
  display:inline-flex;
  border:1px solid #c3cedd;
  border-radius:999px;
  overflow:hidden;
  background:var(--surface);
  flex:none;
}

.widgets-viewseg__opt{
  border:0;
  background:transparent;
  color:var(--slate-soft);
  font-family:var(--sans);
  font-size:.82rem;
  font-weight:600;
  padding:.3rem .85rem;
  cursor:pointer;
}

.widgets-viewseg__opt.is-active{
  background:var(--brass);
  color:#1b1405;
}

.widgets-card__top{
  display:flex;
  align-items:center;
  justify-content:space-between;
  margin-bottom:.55rem;
}

.widgets-card__drag{
  border:0;
  background:transparent;
  color:var(--slate-soft);
  font-weight:700;
  letter-spacing:.12em;
  cursor:grab;
  padding:0;
}

.widgets-card__status{
  display:inline-flex;
  align-items:center;
  padding:.22rem .58rem;
  border-radius:999px;
  font-size:.74rem;
  font-weight:700;
  letter-spacing:.02em;
}

.widgets-card__status--enabled{
  color:#1f5e24;
  background:#e8f7e8;
  border:1px solid #bddfbe;
}

.widgets-card__status--hidden{
  color:#5f2222;
  background:#fceced;
  border:1px solid #efc4c7;
}

.widgets-card__toggle{
  display:flex;
  align-items:center;
  gap:.45rem;
  color:var(--slate);
  font-size:.88rem;
}

.widgets-card__delete{
  position:absolute;
  bottom:.6rem;
  right:.6rem;
  border:0;
  background:transparent;
  color:var(--slate-soft);
  cursor:pointer;
  padding:.25rem;
  border-radius:.25rem;
  display:flex;
  align-items:center;
  justify-content:center;
  transition:color .14s ease, background .14s ease;
  line-height:1;
}
.widgets-card__delete:hover{
  color:#c0392b;
  background:#fceced;
}

.widgets-card h2{
  font-family:var(--serif);
  color:var(--heading);
  font-size:1.2rem;
  margin:0 0 .3rem;
}

.widgets-card p{
  margin:0;
  color:var(--slate-soft);
  font-size:.92rem;
}

.widgets-card__description{
  display:-webkit-box;
  -webkit-box-orient:vertical;
  -webkit-line-clamp:3;
  overflow:hidden;
}

.widgets-card__meta{
  margin-top:auto !important;
  font-size:.76rem !important;
  color:var(--slate-soft);
}

.widgets-card.is-dragging{
  opacity:.55;
  border-color:var(--brass);
  box-shadow:0 10px 22px rgba(165,129,53,.22);
  transform:scale(1.01);
}

.widget-edit-dialog{
  border:none;
  border-radius:16px;
  padding:0;
  /* SCC Phase 7c: 1180 -> 1320. The block header gained a fourth chip and the hide eye, and at 1180 the
     blocks column was 884px — enough to squeeze "FIT TO CARD" onto two lines the moment a title was long.
     97vw still governs every window narrower than ~1360px, so nothing about the small end changes. */
  width:min(97vw, 1320px);
  max-height:92vh;
  /* BACKLOG §1: `clip`, not `hidden`. Both paint the same, but `hidden` makes a SCROLL CONTAINER — one with
     no scrollbar, which the browser will still scroll on its own to reveal a focused descendant, and which
     the author has then no way to scroll back. That is how the head row disappeared. `clip` creates no
     scroll container at all, so the worst a future overflow can do is go unseen rather than take the head
     and the action bar with it. The escaping radios that caused it are fixed at their own four rules. */
  overflow:clip;
  box-shadow:0 24px 60px rgba(0,0,0,.38);
}

.widget-edit-dialog::backdrop{
  background:rgba(0,0,0,.68);
  backdrop-filter:blur(3px);
}

/* WD (Phase 3): the two-column shell (mockup .dialog). The dialog no longer scrolls as one long page:
   head / body / preview / actions are grid rows, and only the body's two columns scroll — so the settings
   stay put beside the blocks they apply to, and Save is always on screen. */
.widget-edit-dialog__inner{
  position:relative;
  display:grid;
  grid-template-rows:auto minmax(0,1fr) auto;
  gap:0;
  padding:0;
  background:var(--surface);
  max-height:92vh;
  /* BACKLOG §1: `clip` for the reason on `.widget-edit-dialog` above — this is the grid whose head row was
     being scrolled out of reach, so it is the one that must not be scrollable. */
  overflow:clip;
}

.widget-edit-dialog__head{
  display:flex;
  align-items:flex-start;
  gap:.9rem;
  padding:1rem 1.2rem .8rem;
  border-bottom:1px solid rgba(148,163,184,.24);
}

.widget-edit-dialog__heading{
  display:grid;
  gap:.15rem;
}

.widget-edit-dialog__body{
  display:grid;
  grid-template-columns:296px minmax(0,1fr);
  min-height:0;
  overflow:hidden;
}

.widget-edit-dialog__settings{
  border-right:1px solid rgba(148,163,184,.24);
  background:var(--surface);
  padding:.9rem .85rem 1.1rem;
  /* BACKLOG §1b measured this box and it is NOT the one that scrolled sideways — `scrollWidth` equals
     `clientWidth` here even with an over-wide row on the board card, because `.widget-panelcard` has
     `overflow:hidden` and therefore an automatic minimum size of zero, so the card clips its own rows
     rather than widening this column's track. The clip that answers §1b is on the card, where the
     measurement put it. */
  overflow-y:auto;
  display:grid;
  gap:.7rem;
  align-content:start;
  /* Each card takes its CONTENT height, and the column scrolls. Without this the fixed-height column squeezed
     the cards instead of overflowing, and because a card clips its own body (overflow:hidden, for its rounded
     summary) the squeezed rows were simply UNREACHABLE - no scrollbar, no way to get at Visibility or Item
     limit. A scroll container's rows must be max-content, or it has nothing to scroll. */
  grid-auto-rows:max-content;
}

.widget-edit-dialog__blocks{
  padding:.9rem 1rem 1.1rem;
  overflow-y:auto;
  min-width:0;
}

/* WD (Phase 3): the mockup's `.stacklabel` — a rule that names what the column below it is. */
.widget-edit-dialog__stacklabel{
  display:flex;
  align-items:center;
  gap:.5rem;
  margin:.1rem 0 .6rem;
  font-size:.62rem;
  font-weight:700;
  letter-spacing:.1em;
  text-transform:uppercase;
  color:var(--slate-soft);
}

.widget-edit-dialog__stacklabel::after{
  content:"";
  flex:1;
  height:1px;
  background:rgba(148,163,184,.28);
}

/* WD (Phase 3): the preview lives in its own dialog. It started as a strip along the bottom of the editor and
   made it too cramped — the strip and the blocks column were competing for the same vertical space. Dark, so
   the previewed widget reads as "the page" rather than as more editor chrome. */
.widget-preview-dialog{
  border:none;
  border-radius:16px;
  padding:0;
  width:min(96vw, 1100px);
  max-height:92vh;
  overflow:hidden;
  background:#14120c;
  box-shadow:0 24px 60px rgba(0,0,0,.45);
}

.widget-preview-dialog::backdrop{
  background:rgba(0,0,0,.72);
  backdrop-filter:blur(3px);
}

.widget-preview-dialog__inner{
  display:grid;
  grid-template-rows:auto minmax(0,1fr) auto;
  max-height:92vh;
}

.widget-preview-dialog__head{
  display:flex;
  align-items:center;
  gap:.8rem;
  padding:.9rem 1.2rem .6rem;
}

.widget-preview-dialog__label{
  display:flex;
  align-items:center;
  gap:.6rem;
  margin:0;
  font-size:.62rem;
  font-weight:700;
  letter-spacing:.14em;
  text-transform:uppercase;
  color:var(--brass-ink);
}

.widget-preview-dialog__state{
  color:rgba(244,237,224,.55);
  letter-spacing:.06em;
}

.widget-preview-dialog__close{
  margin-left:auto;
  border:0;
  background:transparent;
  color:rgba(244,237,224,.65);
  font-size:1.6rem;
  line-height:1;
  cursor:pointer;
}

.widget-preview-dialog__close:hover{
  color:var(--on-dark);
}

.widget-preview-dialog__surface{
  display:grid;
  gap:.75rem;
  padding:.4rem 1.2rem 1rem;
  overflow-y:auto;
  /* The preview is a picture, not a page: the widget it renders carries real links, and clicking one inside
     the dialog would navigate away and lose unsaved edits. */
  pointer-events:none;
}

.widget-preview-dialog__note{
  margin:0;
  padding:.6rem 1.2rem 1rem;
  color:rgba(244,237,224,.5);
  font-size:.76rem;
}

/* The preview renders the real home partial, so it inherits the home rules; only its width is ours to set —
   a span-2 widget in a 3-column home grid is two thirds of the row, and the preview shows that proportion. */
.home-widget-preview{
  width:100%;
  margin:0 auto;
  border-radius:14px;
  overflow:hidden;
}

.home-widget-preview.home-widget--span-1{ max-width:24rem; }
.home-widget-preview.home-widget--span-2{ max-width:44rem; }

.widget-edit-dialog__inner h2{
  margin:0;
  font-size:1.45rem;
}

.widget-edit-dialog__name{
  margin:-.35rem 0 0;
  color:var(--slate-soft);
  font-size:.92rem;
}

.widget-edit-dialog__label{
  font-size:.8rem;
  font-weight:600;
  color:var(--slate-soft);
  text-transform:uppercase;
  letter-spacing:.05em;
}

.widget-edit-dialog__toggle{
  display:flex;
  align-items:center;
  gap:.55rem;
  color:var(--heading);
  font-size:.9rem;
  margin:.15rem 0;
}

.widget-edit-dialog__toggle input{
  width:1rem;
  height:1rem;
}

.widget-edit-dialog__toggle--pill{
  border:1px solid rgba(148,163,184,.35);
  border-radius:999px;
  padding:.38rem .72rem;
  background:var(--surface);
}

.widget-control-suite{
  display:flex;
  flex-wrap:wrap;
  gap:.45rem;
  align-items:center;
}

.widget-edit-dialog__inline-field{
  display:grid;
  gap:.2rem;
  align-items:center;
  border:1px solid rgba(148,163,184,.35);
  border-radius:10px;
  padding:.3rem .5rem;
  background:var(--surface);
}

.widget-edit-dialog__input--compact{
  width:4.5rem;
}

.widget-edit-dialog__input{
  width:100%;
}

/* WD (Phase 3): a plain column. It used to be a two-column grid whose first column held the settings block
   (pinned by `grid-row: 1 / span 120`); the settings now have their own scrolling sidebar, so the stack is
   just the blocks, in order. */
/* SCC PHASE 7c — `minmax(0, 1fr)` HERE AND ON THE TWO GRIDS BELOW, and it is the same finding as 7b's
   `min-width:auto` for the third time. An implicit `auto` grid column has a MINIMUM of min-content, so one
   block whose authored markup is wide (the History card's two-column figure) grew this track from 852px to
   945px — and every OTHER card grew with it, pushing the header's chips and the hide eye off the right-hand
   edge of a column that then scrolled sideways. The editors already carry `overflow:auto`; it could not do
   anything, because the box it was supposed to clip inside was being widened to fit it. Pinning the track is
   what makes that rule work. */
.widget-control-stack{
  display:grid;
  grid-template-columns:minmax(0, 1fr);
  gap:.6rem;
  align-content:start;
  scroll-snap-type:y proximity;
}

.widget-control-block{
  border:1px solid rgba(148,163,184,.35);
  border-radius:12px;
  padding:.55rem .62rem .62rem;
  display:grid;
  grid-template-columns:minmax(0, 1fr);
  gap:.48rem;
  background:var(--surface);
  scroll-snap-align:start;
}

/* WD (Phase 3): the mockup's `.panelcard` — one settings card, summary + body, in the sidebar. Every card
   is wired by the same generic disclosure in site.js, so a new card is markup. */
/* BACKLOG §1b, THE SECOND HALF — AND THE CARD IS THE SCROLL CONTAINER, WHICH THE BACKLOG HAD ON THE COLUMN.

   Measured with an over-wide row present, at 1385x1246:

       .widget-panelcard           w=266  scrollWidth=281  over=15  overflow-x:hidden   scrolls to 15
       .widget-panelcard__body     w=266  scrollWidth=281  over=15  overflow-x:visible  scrolls to  0
       .widget-settings-panel__rows w=243 scrollWidth=270  over=27  overflow-x:visible  scrolls to  0
       .widget-edit-dialog__settings                       over= 0                      scrolls to  0

   So the settings column never overflowed at all: this card has `overflow:hidden`, which gives a grid item
   an automatic minimum size of ZERO, so the rows grid overflows the card instead of widening the column's
   track. §1.2's sentence one box further in — AN `overflow:hidden` BOX IS STILL A SCROLL CONTAINER, one with
   no scrollbar — and the browser scrolled it by those 15px the moment focus reached a segment at the
   right-hand end of one of those rows, sliding every control in the card under the pointer.

   `clip` paints identically (the hidden was here to clip the summary button to the radius, which clip does)
   and creates no scrolling box at all, so the worst a future over-wide row can do is go unseen rather than
   move the controls while they are being used. It is stated WITH the wrap on `.widget-settings-row` and
   never instead of it: clip hides an overflow rather than removing one, which is why the guard measures
   every row against its column as well as asserting this. */
.widget-panelcard{
  padding:0;   /* the page-wide `section{padding:74px 0}` would otherwise pad the card open */
  border:1px solid rgba(148,163,184,.35);
  border-radius:12px;
  background:var(--surface);
  overflow:clip;
}

.widget-panelcard__summary{
  display:flex;
  align-items:center;
  gap:.5rem;
  width:100%;
  border:0;
  background:var(--surface);
  color:var(--slate);
  padding:.55rem .7rem;
  font-size:.78rem;
  font-weight:700;
  letter-spacing:.06em;
  text-transform:uppercase;
  cursor:pointer;
  text-align:left;
}

.widget-panelcard__summary-icon{
  color:var(--brass-ink);
  font-size:.9rem;
  line-height:1;
}

.widget-panelcard__chevron{
  margin-left:auto;
  font-size:.9rem;
  line-height:1;
  color:var(--slate-soft);
  transition:transform .16s ease;
}

.widget-panelcard.is-collapsed .widget-panelcard__chevron{
  transform:rotate(-90deg);
}

.widget-panelcard__body{
  display:block;
  padding:.7rem;
}

.widget-panelcard.is-collapsed .widget-panelcard__body{
  display:none;
}

/* One column: the sidebar is 296px, so the old auto-fit multi-column row grid would never fit two. */
.widget-settings-panel__rows{
  display:grid;
  gap:.6rem;
}

/* BACKLOG §1b: `flex-wrap:wrap` IS THE WHOLE FIX, AND IT IS GENERAL RATHER THAN A MEASUREMENT.

   A one-line flex row's min-content width is its label PLUS its control, and `.widget-settings-panel__rows`
   is a grid whose implicit track has an automatic minimum of exactly that — so ONE row that cannot fit sets
   the width of every row in its card. 7e's phone-order row is that row: its stepper shows the WORD "Board
   order" rather than a digit, so label + stepper is ~270px inside a 243px column, and the whole "On the
   board" card was stretched to 270 while the other three sat at 243. The 27px went past the settings column,
   which is `overflow-y:auto` and therefore a scroll container on BOTH axes — so focusing a segment at the
   right-hand end of one of those rows made the browser scroll the column sideways and every control in it
   slid 6px under the pointer. Reported from the running site as clipping ("widget settings don't quite fit")
   and found by Phase 8's own Q5 guard as movement; one defect, seen with and without focus.

   WITH WRAP THE ROW'S MIN-CONTENT BECOMES ITS WIDEST CHILD, so no row can ever widen the column again
   whatever label a later phase adds — which is why this and not a shorter word for "Board order", and not
   `min-width:0` on the segments (those rows all fit, and `.widget-alignseg` is `overflow:hidden`, so
   shrinking them would clip a label). Nothing that fits today moves: wrap only acts on a line that would
   otherwise overflow. */
.widget-settings-row{
  display:flex;
  flex-wrap:wrap;
  align-items:center;
  justify-content:space-between;
  gap:.75rem;
  padding:.45rem .5rem;
  border:1px solid rgba(148,163,184,.22);
  border-radius:10px;
  background:var(--surface);
}

.widget-settings-row__label{
  display:flex;
  align-items:center;
  gap:.55rem;
  color:var(--heading);
  font-size:1rem;
  font-weight:500;
}

.widget-settings-row__label--stack{
  align-items:flex-start;
  min-width:132px;
}

.widget-settings-row__icon{
  color:var(--slate-soft);
  font-size:.92rem;
  width:1.1rem;
  text-align:center;
}

.widget-settings-row--field{
  align-items:flex-start;
  grid-column:1 / -1;
}

/* WD (Phase 3): in the 296px sidebar there is no room for label-beside-field, so a field row stacks its
   label above its control (the mockup's `.srow`). The switch rows stay side-by-side — a toggle is small
   enough to sit at the end of its own label. */
.widget-edit-dialog__settings .widget-settings-row--field{
  flex-direction:column;
  /* BACKLOG §1b: the wrap above is for ROW-direction rows, and it is turned off again here rather than left
     to do nothing. A COLUMN-direction flex container with `wrap` breaks into a second COLUMN when its height
     runs out, which is a different behaviour entirely and one no field row wants — and a field row is immune
     to the defect the wrap answers, because it already stacks its label above its control. */
  flex-wrap:nowrap;
  gap:.35rem;
}

.widget-edit-dialog__settings .widget-settings-row__label--stack{
  min-width:0;
}

.widget-edit-dialog__settings .widget-settings-row--field > *:not(.widget-settings-row__label){
  width:100%;
}

.widget-settings-row__field-input{
  width:100%;
  max-width:100%;
}

/* SCC Phase 7a: the muted line under a field that has to explain itself (the link id's does). Same weight and
   colour as .widget-bg-picker__caption, which is the settings column's existing caption — stated as its own
   class rather than borrowed, because that one names the theme the swatches come from and this one is a
   general row caption. */
.widget-settings-row__caption{
  margin:0;
  color:var(--slate-soft);
  font-size:.82rem;
  line-height:1.4;
}

.widget-settings-row__caption code{
  font-size:.95em;
}

/* SCC Phase 7e, GENERALISED IN PHASE 8 (gate Q5): a row whose control still works but has stopped deciding
   anything — the column span under a full-width layout mode, and the stage's measure and arrangement on a
   card that has no stage. DIMMED AND NOT DISABLED, which is the decision: the stored value is what the card
   goes back to when the condition comes true again, so the author has to be able to set it, and taking the
   control away would lose a value that is about to matter. The note beside it says which of those two is
   happening; this only makes it look like what the note says.

   ONE DECLARATION IS THE WHOLE MECHANISM, and that is not thrift. §26.3 hides a settings card whose every
   row is hidden and it runs on `style.display`, so anything here that touched `display` could empty a card,
   hide it, and bring it back on the next pick. `opacity` cannot. The class was `--overridden` in 7e, when
   there was one subject and it was an override; it is renamed because "does not apply" is the general case
   and an override is one reading of it. */
.widget-settings-row--not-applicable{
  opacity:.55;
}

/* ---------------------------------------------------------------------------------------------------------
   SCC PHASE 7a — THE SECTION STYLE CHIP GRID (plan §23.13 Q1).

   Seven small paintings of the seven shells, in the settings column at 296px. `auto-fill,minmax(84px,1fr)` and
   not a fixed track count: the sidebar is one width today and the dialog is not exempt from the trap the
   members' portal grid hit twice and EveryShell_KeepsItsContentInsideItsCard_AtEveryWidth was written for
   ([[empty-grid-track-hides-overflow]] is the same shape one level down).

   THE GROUND IS NOT WRITTEN HERE. Each mini carries data-widget-shell and takes --widget-edit-shell-bg/-fg
   from the one table further down this file, the same one the block editors read. What IS written here is
   everything a shell has BEYOND its ground — the dashed edge of `bare`, the hairline of `surface`, the lift of
   `card`, the inset brass panel of `board`, the radial wash of `feature`, the inset frame of `join` — because
   those are what tell the seven apart at 44px, and there is no way to derive them from a colour pair.
   --------------------------------------------------------------------------------------------------------- */
.widget-shellgrid{
  display:grid;
  grid-template-columns:repeat(auto-fill, minmax(84px, 1fr));
  gap:.5rem;
  width:100%;
}

/* BACKLOG §1 — THE CHIP IS THE CONTAINING BLOCK FOR ITS OWN HIDDEN RADIO, AND THIS LINE IS THE WHOLE FIX.

   `.widget-shellchip__input` is `position:absolute` (below), and absolute means "relative to the nearest
   POSITIONED ancestor" — which was not this label, and not the settings column either. It was
   `.widget-edit-dialog__inner`, 296px away and three scroll boxes up. An absolutely positioned box whose
   containing block is an ancestor of a scroller is NOT clipped by that scroller, so seven 1px radios landed
   651px below the bottom of the dialog and pushed `inner.scrollHeight` from 1146 to 1797.

   That is what made the edit dialog eat its own head row. `.widget-edit-dialog__inner` is `overflow:hidden`,
   and an `overflow:hidden` box is still PROGRAMMATICALLY scrollable — so the moment focus reached one of the
   escaped radios (tab to a shell chip, an alignment segment, a colour swatch), the browser scrolled the grid
   to bring it into view, the 75px head went off the top, and there was no scrollbar to bring it back. Hence
   "some widget settings": only a widget type offering chips, alignment or a layout mode has any to escape.

   `.widget-switch` had this right already. The same one-line repair is on `.widget-alignseg__opt`,
   `.widget-rich__swatches` and `.board-seg__opt`. */
.widget-shellchip{
  position:relative;
  display:block;
  margin:0;
  padding:.35rem;
  border:1px solid rgba(148,163,184,.3);
  border-radius:9px;
  background:var(--surface);
  text-align:center;
  cursor:pointer;
}

.widget-shellchip:has(.widget-shellchip__input:checked){
  border-color:var(--brass);
  box-shadow:0 0 0 2px rgba(165,129,53,.28);
}

/* The radio is the control and the chip is its label, so the native input is taken out of the picture
   visually and left in it for the keyboard: arrow keys move between chips, space selects, and the focus ring
   below is drawn on the painting the author is actually looking at. */
.widget-shellchip__input{
  position:absolute;
  width:1px;
  height:1px;
  opacity:0;
  pointer-events:none;
}

.widget-shellchip__input:focus-visible ~ .widget-shellchip__mini{
  outline:2px solid var(--brass);
  outline-offset:2px;
}

.widget-shellchip__mini{
  position:relative;
  display:flex;
  flex-direction:column;
  justify-content:center;
  gap:3px;
  height:44px;
  padding:6px;
  overflow:hidden;
  border-radius:5px;
  background:var(--widget-edit-shell-bg, var(--ink));
  color:var(--widget-edit-shell-fg, var(--on-dark));
}

/* Three bars standing in for a heading and two lines of copy — currentColor, so each mini's text colour is
   the shell's own on-ground colour and a shell whose pair is wrong is visible as bars that vanish. */
.widget-shellchip__mini i{
  display:block;
  height:3px;
  border-radius:2px;
  background:currentColor;
  opacity:.55;
}

.widget-shellchip__mini i:first-child{
  width:70%;
  height:5px;
  opacity:.9;
}

.widget-shellchip__mini i:last-child{
  width:45%;
}

/* `bare` paints NO ground, so the mini drops the table's value and shows the chip through — which is the
   truest thing a 44px painting can say about a shell whose whole definition is the absence of one. The dashed
   edge is the drawing's, not the shell's: it marks the area the section would occupy. */
.widget-shellchip__mini[data-widget-shell="bare"]{
  background:none;
  border:1px dashed rgba(148,163,184,.6);
  color:var(--slate-soft);
}

.widget-shellchip__mini[data-widget-shell="surface"],
.widget-shellchip__mini[data-widget-shell="card"]{
  border:1px solid var(--line-light);
}

.widget-shellchip__mini[data-widget-shell="card"]{
  box-shadow:0 6px 10px -7px rgba(0,0,0,.55);
  transform:translateY(-1px);
}

/* `board` is the inset panel the motto paints inside its card, and the mini draws it the same way the shell
   does — an inset ::before with the brass border, over the heritage ground the table gives it. The bars sit
   above it and indented, because on the real card they sit inside the panel. */
.widget-shellchip__mini[data-widget-shell="board"]{
  background:var(--ink-deep);
}

.widget-shellchip__mini[data-widget-shell="board"]::before{
  content:"";
  position:absolute;
  inset:5px;
  border:1px solid color-mix(in srgb, var(--brass) 38%, transparent);
  border-radius:6px;
  background:var(--surface);
}

.widget-shellchip__mini[data-widget-shell="board"] i{
  position:relative;
  z-index:1;
  margin-left:8px;
}

.widget-shellchip__mini[data-widget-shell="feature"]::after{
  content:"";
  position:absolute;
  inset:0;
  background:radial-gradient(120% 90% at 80% -10%, color-mix(in srgb, var(--brass) 30%, transparent), transparent 55%);
}

.widget-shellchip__mini[data-widget-shell="feature"] i{
  position:relative;
  z-index:1;
}

/* The band's ground is its own claret gradient rather than a token (the shell authors it in hex), so the mini
   states the two ends of it. The table's `join` entry is the middle stop and stays the fallback. */
.widget-shellchip__mini[data-widget-shell="join"]{
  background:linear-gradient(145deg, #641324 0%, #430a18 100%);
  border:1px solid color-mix(in srgb, var(--brass-soft) 26%, transparent);
}

.widget-shellchip__mini[data-widget-shell="join"]::before{
  content:"";
  position:absolute;
  inset:4px;
  border:1px solid color-mix(in srgb, var(--brass-soft) 30%, transparent);
  border-radius:6px;
}

/* The join band centres its one column, and the mini says so — it is the only shell whose difference from its
   neighbours is a LAYOUT rather than a ground, which is exactly the thing Q1 decided not to warn about in
   words. */
.widget-shellchip__mini[data-widget-shell="join"] i{
  position:relative;
  z-index:1;
  margin:0 auto;
}

.widget-shellchip__name{
  display:block;
  margin-top:.3rem;
  color:var(--heading);
  font-size:.68rem;
  font-weight:650;
  letter-spacing:.01em;
}

/* F9: the Visibility row's roles checkbox list (shown when scope = Specific roles) */
.widget-visibility-roles{
  display:flex;
  flex-direction:column;
  gap:.35rem;
  width:100%;
}

.widget-visibility-roles__option{
  display:flex;
  align-items:center;
  gap:.5rem;
  color:var(--heading);
  font-size:.95rem;
}

.widget-visibility-roles__empty{
  margin:0;
  color:var(--slate-soft);
  font-size:.9rem;
}

.widget-bg-picker{
  display:grid;
  gap:.45rem;
  width:100%;
}

/* B3 (T6): the dimmable body (swatches + custom hex + caption); the inherit note sits outside it so it
   stays fully legible while the body is greyed. */
.widget-bg-picker__body{
  display:grid;
  gap:.45rem;
}

.widget-bg-picker__custom{
  display:flex;
  align-items:center;
  gap:.45rem;
}

.widget-bg-picker__custom .widget-settings-row__field-input{
  max-width:220px;
}

.widget-bg-picker__clear{
  padding:.35rem .65rem;
  line-height:1.2;
}

/* B3 (T5): muted caption naming the theme the swatches come from (§8.2). */
.widget-bg-picker__caption{
  margin:0;
  color:var(--slate-soft);
  font-size:.82rem;
}

/* B3 (T6): explanatory note shown only while "Inherit theme style" is on (§8.2). Fixed Heritage
   parchment/brass literals (decision D3: admin chrome stays readable whichever theme is active). */
.widget-bg-picker__inherit-note{
  display:none;
  margin:0;
  padding:.5rem .65rem;
  border:1px solid #C7A24A;
  border-radius:8px;
  background:rgba(246, 242, 233, .7);
  color:var(--slate-soft);
  font-size:.82rem;
  line-height:1.4;
}

/* While "Inherit theme style" is ticked (dialog carries this class, toggled by site.js), the custom
   background is ignored at render (HomeWidgetPartialModel), so the picker body is disabled and the
   note is revealed. */
.widget-edit-dialog--block-inherit-theme .widget-bg-picker__body{
  opacity:.38;
  pointer-events:none;
}

.widget-edit-dialog--block-inherit-theme .widget-bg-picker__inherit-note{
  display:block;
}

/* Border Control Styles */
.widget-border-control{
  display:grid;
  grid-template-columns:repeat(auto-fit, minmax(170px, 1fr));
  gap:.45rem .6rem;
  width:100%;
}

.widget-border-control__section{
  display:grid;
  gap:.3rem;
  padding:.35rem .4rem;
  border:1px solid rgba(148,163,184,.24);
  border-radius:8px;
  background:var(--surface);
}

.widget-border-control__label{
  font-size:.68rem;
  font-weight:700;
  color:var(--slate);
  letter-spacing:.06em;
  text-transform:uppercase;
}

.widget-border-control__buttons{
  display:flex;
  flex-wrap:wrap;
  gap:.22rem;
}

/* F10 (P2): .widget-style-preset shares this look — the button size/shape pickers (per-card and in the
   button style suite) read as the same family of small toggle buttons as the border side/style pickers.
   WD (Phase 2b): the separator block's style/width segments and its ornament glyph shortcuts join the same
   family — they are the same affordance, so they should not be a second look. */
.btn-border-side,
.btn-border-style,
.btn-separator-choice,
.btn-separator-glyph,
.btn-layout-align,
.btn-layout-width,
.widget-style-preset{
  appearance:none;
  border:1px solid rgba(100,116,139,.35);
  border-radius:5px;
  background:var(--surface);
  color:var(--slate);
  padding:.24rem .42rem;
  font-size:.7rem;
  font-weight:600;
  cursor:pointer;
  transition:border-color .15s ease, background .15s ease, box-shadow .15s ease;
  line-height:1.1;
}

.btn-border-side:hover,
.btn-border-style:hover,
.btn-separator-choice:hover,
.btn-separator-glyph:hover,
.btn-layout-align:hover,
.btn-layout-width:hover,
.widget-style-preset:hover{
  border-color:var(--brass);
  background:var(--parchment);
}

.btn-border-side.is-active,
.btn-border-style.is-active,
.btn-separator-choice.is-active,
.btn-layout-align.is-active,
.btn-layout-width.is-active,
.widget-style-preset.is-active{
  border-color:var(--brass);
  background:var(--brass);
  color:var(--on-brass);
  box-shadow:0 0 0 2px rgba(165,129,53,.18);
}

/* F10 (P2): container for a row of style-preset buttons. */
.widget-style-presets{
  display:flex;
  flex-wrap:wrap;
  gap:.22rem;
}

.widget-border-control__weight{
  display:flex;
  align-items:center;
  gap:.22rem;
}

.widget-border-control__weight-buttons{
  display:flex;
  gap:.2rem;
}

.btn-border-weight-dec,
.btn-border-weight-inc{
  appearance:none;
  border:1px solid rgba(100,116,139,.35);
  border-radius:4px;
  background:var(--surface);
  color:var(--slate);
  width:26px;
  height:26px;
  padding:0;
  font-size:.8rem;
  font-weight:600;
  cursor:pointer;
  transition:border-color .15s ease, background .15s ease;
  display:flex;
  align-items:center;
  justify-content:center;
  line-height:1;
}

.btn-border-weight-dec:hover,
.btn-border-weight-inc:hover{
  border-color:var(--brass);
  background:var(--parchment);
}

.btn-border-weight-dec:active,
.btn-border-weight-inc:active{
  border-color:var(--brass);
  background:var(--brass);
  color:var(--on-brass);
}

.widget-border-control__weight-input{
  width:3rem;
  padding:.24rem .34rem;
  border:1px solid rgba(100,116,139,.35);
  border-radius:5px;
  background:var(--surface);
  color:var(--slate);
  font-size:.7rem;
  text-align:center;
}

.widget-border-control__weight-input:focus{
  outline:none;
  border-color:var(--brass);
  box-shadow:0 0 0 2px rgba(165,129,53,.14);
}

.widget-border-control__weight-unit{
  font-size:.68rem;
  color:var(--slate-soft);
  font-weight:600;
}

.widget-border-control__colors{
  display:flex;
  align-items:center;
  flex-wrap:wrap;
  gap:.25rem;
}

.widget-border-color-swatch{
  appearance:none;
  width:22px;
  height:22px;
  border-radius:4px;
  border:2px solid transparent;
  padding:0;
  cursor:pointer;
  transition:border-color .15s ease, box-shadow .15s ease;
}

.widget-border-color-swatch:hover{
  box-shadow:0 0 0 1px rgba(0,0,0,.12);
}

.widget-border-color-swatch.is-active{
  border-color:var(--slate);
  box-shadow:0 0 0 1px rgba(0,0,0,.12), inset 0 0 0 3px #fff;
}

.widget-border-color-input{
  width:0;
  height:0;
  opacity:0;
  position:absolute;
  pointer-events:none;
}

.btn-border-color-custom{
  appearance:none;
  border:1px solid rgba(100,116,139,.35);
  border-radius:4px;
  background:var(--surface);
  color:var(--slate);
  padding:.32rem .5rem;
  font-size:.72rem;
  font-weight:600;
  cursor:pointer;
  transition:border-color .15s ease, background .15s ease;
  line-height:1.1;
}

.btn-border-color-custom:hover{
  border-color:var(--brass);
  background:var(--parchment);
}

.widget-switch{
  position:relative;
  display:inline-flex;
}

.widget-switch input{
  position:absolute;
  opacity:0;
  width:1px;
  height:1px;
}

.widget-switch__track{
  width:2.45rem;
  height:1.38rem;
  border-radius:999px;
  background:#e5e7eb;
  border:1px solid #d1d5db;
  display:inline-block;
  position:relative;
  transition:all .16s ease;
}

.widget-switch__track::after{
  content:"";
  position:absolute;
  top:1px;
  left:1px;
  width:1.06rem;
  height:1.06rem;
  border-radius:50%;
  background:#fff;
  box-shadow:0 1px 2px rgba(0,0,0,.2);
  transition:transform .16s ease;
}

/* General sibling (~), not adjacent (+): a bool checkbox that posts an unchecked value carries a
   companion `<input type="hidden" value="false">` BETWEEN the checkbox and the track, which breaks a
   `+` match and leaves the track stuck in its off state (B3 T6 bug report). `~` matches the track
   whenever a preceding-sibling input is checked, so it works with or without the hidden companion. */
.widget-switch input:checked ~ .widget-switch__track{
  background:#d6b466;
  border-color:#b9933a;
}

.widget-switch input:checked ~ .widget-switch__track::after{
  transform:translateX(1.04rem);
}

.widget-span-stepper{
  display:inline-flex;
  align-items:center;
  border:1px solid #d1d5db;
  border-radius:10px;
  overflow:hidden;
  background:var(--surface);
}

.widget-span-stepper__btn{
  width:2rem;
  height:2rem;
  border:none;
  background:var(--parchment-2);
  color:var(--heading);
  font-size:1.2rem;
  line-height:1;
  cursor:pointer;
}

.widget-span-stepper__btn:hover{
  background:var(--parchment-2);
}

.widget-span-stepper__value{
  min-width:2rem;
  text-align:center;
  font-size:1.03rem;
  font-weight:600;
  color:var(--heading);
}

/* SCC Phase 7e: the phone order's bottom stop reads "Board order" rather than a number, because zero is not
   a position. A 2rem box built for one or two digits would wrap it, so the one stepper that shows a word
   gets a wider value box — the modifier is on the value and not on the stepper, since it is the CONTENT that
   is unusual and not the control. */
.widget-span-stepper__value--wide{
  min-width:6.2rem;
  font-size:.92rem;
}

.widget-settings-panel__divider{
  margin:.25rem 0 .15rem;
  border-top:1px solid #d1d5db;
}

.widget-settings-panel__section-title{
  margin:0;
  color:var(--slate-soft);
  font-size:.94rem;
  font-weight:700;
  letter-spacing:.06em;
  text-transform:uppercase;
}

/* WD (Phase 1): the compact Add-a-block toolbar (mockup `.addbar`). One bordered row of small icon+label
   pills that wraps as block kinds are added, replacing the three framed launchpad buttons the user called a
   poor use of space. Pills are rendered per kind from the registry-shaped list in Widgets.cshtml. */
.widget-addbar{
  display:flex;
  align-items:center;
  flex-wrap:wrap;
  gap:.42rem;
  margin:0 0 .8rem;
  padding:.45rem .55rem;
  border:1px solid rgba(148,163,184,.34);
  border-radius:10px;
  background:linear-gradient(180deg,#ffffff 0%,#faf9f4 100%);
}

.widget-addbar__lead{
  margin-right:.1rem;
  color:var(--slate-soft);
  font-size:.62rem;
  font-weight:700;
  letter-spacing:.1em;
  text-transform:uppercase;
}

.widget-addbar__btn{
  display:inline-flex;
  align-items:center;
  gap:.36rem;
  border:1px solid rgba(148,163,184,.5);
  border-radius:8px;
  background:var(--surface);
  color:var(--heading);
  font:inherit;
  font-size:.8rem;
  padding:.3rem .6rem;
  cursor:pointer;
  white-space:nowrap;
  transition:border-color .12s ease, background .12s ease;
}

.widget-addbar__btn:hover:not(:disabled){
  border-color:var(--brass);
  background:color-mix(in srgb, var(--brass) 10%, var(--surface));
}

.widget-addbar__btn:disabled{
  opacity:.45;
  cursor:not-allowed;
}

.widget-addbar__icon{
  font-size:.95rem;
  line-height:1;
}

/* The "new block type" dot; SCC's kinds arrive flagged. */
.widget-addbar__dot{
  width:6px;
  height:6px;
  border-radius:50%;
  background:var(--brass);
  margin-left:.1rem;
}

.widget-addbar__note{
  width:100%;
  margin:.1rem 0 0;
  color:var(--slate-soft);
  font-size:.7rem;
}

.widget-addbar__btn:focus-visible,
.widget-repeater-add-btn:focus-visible{
  outline:2px solid rgba(165,129,53,.5);
  outline-offset:2px;
}

.widget-control-block.is-dragging{
  opacity:.58;
  border-color:var(--brass);
  box-shadow:0 10px 22px rgba(165,129,53,.2);
}

/* WD (Phase 1): the handle is now the ONLY way to drag a block (site.js sets draggable on mousedown here and
   clears it on dragend), so its grab cursor finally means something. user-select:none keeps a press on the
   handle from starting a text selection in the block below it. */
.widget-control-block__drag{
  justify-self:end;
  cursor:grab;
  border:1px solid var(--line-light);
  border-radius:6px;
  background:var(--surface);
  padding:.1rem .35rem;
  color:var(--slate-soft);
  user-select:none;
  -webkit-user-select:none;
}

.widget-control-block__drag:active{
  cursor:grabbing;
}

/* SCC Phase 7c: it WRAPS now. The header carries a fourth chip and the hide eye, and squeezed it broke
   "FIT TO CARD" onto two lines inside its own pill — a chip on a second ROW reads as a chip; a chip whose
   label has folded reads as a mistake. The spacer keeps the controls right-aligned on the first row and the
   wrap only happens when they genuinely do not fit. */
.widget-control-block__head{
  display:flex;
  flex-wrap:wrap;
  justify-content:flex-start;
  align-items:center;
  gap:.42rem;
  min-height:2rem;
}

/* A chip's label never folds: it either fits on the row or the row wraps and it fits on the next one. */
.widget-control-block__toggle-chip{
  white-space:nowrap;
}

.widget-control-block__title{
  font-size:.8rem;
  font-weight:700;
  letter-spacing:.06em;
  text-transform:uppercase;
  color:var(--slate);
  white-space:nowrap;
  overflow:hidden;
  text-overflow:ellipsis;
}

/* WD (Phase 3): the mockup's `.bk` kind caption. The title beside it becomes the block's own content as soon
   as there is any, so this is what keeps the header saying WHAT the block is. */
.widget-control-block__kind{
  font-size:.6rem;
  font-weight:700;
  letter-spacing:.07em;
  text-transform:uppercase;
  color:var(--slate-soft);
  white-space:nowrap;
}

.widget-control-block__kind::before{
  content:"\00b7";
  margin-right:.28rem;
}

.widget-control-block__head-spacer{
  flex:1 1 auto;
  min-width:.2rem;
}

.widget-control-block__body{
  display:grid;
  grid-template-columns:minmax(0, 1fr);
  gap:.5rem;
}

.widget-control-block__collapse{
  width:1.7rem;
  height:1.7rem;
  border:1px solid rgba(148,163,184,.45);
  border-radius:999px;
  background:var(--surface);
  color:var(--slate-soft);
  cursor:pointer;
  line-height:1;
  font-size:.92rem;
  padding:0;
  transition:transform .15s ease, border-color .15s ease, background .15s ease;
}

.widget-control-block__collapse:hover{
  border-color:var(--brass);
  background:color-mix(in srgb, var(--brass) 10%, var(--surface));
}

.widget-control-block.is-collapsed .widget-control-block__body{
  display:none;
}

.widget-control-block.is-collapsed .widget-control-block__collapse{
  transform:rotate(-90deg);
}

.widget-control-block__remove{
  width:1.7rem;
  height:1.7rem;
  border:1px solid rgba(148,163,184,.4);
  border-radius:999px;
  background:var(--surface);
  color:var(--slate-soft);
  font-weight:700;
  line-height:1;
  cursor:pointer;
  padding:0;
}

@media(max-width:980px){
  .widget-edit-dialog__body{
    grid-template-columns:1fr;
    overflow-y:auto;
  }

  .widget-edit-dialog__settings{
    border-right:0;
    border-bottom:1px solid rgba(148,163,184,.24);
    overflow-y:visible;
  }

  .widget-edit-dialog__blocks{
    overflow-y:visible;
  }

}

.widget-control-panel{
  border:1px solid rgba(148,163,184,.28);
  border-radius:9px;
  padding:.48rem;
  background:var(--surface);
}

.widget-control-block__toggle-chip{
  appearance:none;
  border:1px solid rgba(148,163,184,.45);
  border-radius:999px;
  background:var(--surface);
  color:var(--slate);
  font-size:.72rem;
  font-weight:700;
  letter-spacing:.04em;
  text-transform:uppercase;
  line-height:1;
  padding:.28rem .58rem;
  cursor:pointer;
  transition:border-color .15s ease, color .15s ease, background .15s ease, box-shadow .15s ease;
}

.widget-control-block__toggle-chip:hover{
  border-color:var(--brass);
  color:var(--heading);
  background:color-mix(in srgb, var(--brass) 10%, var(--surface));
}

.widget-control-block__toggle-chip[aria-expanded="true"]{
  border-color:var(--brass);
  color:var(--on-brass);
  background:var(--brass);
  box-shadow:0 0 0 2px rgba(165,129,53,.16);
}

.widget-control-block__toggle-chip-label{
  display:inline-block;
  transform:translateY(1px);
}

.widget-control-block__remove:hover{
  border-color:#dc2626;
  color:#dc2626;
  background:#fff5f5;
}

.widget-rich{
  display:grid;
  gap:.45rem;
}

.widget-rich__tools{
  display:flex;
  flex-wrap:wrap;
  gap:.35rem;
  align-items:center;
}

.widget-rich__tools select{
  border:1px solid var(--line-light);
  border-radius:8px;
  padding:.2rem .35rem;
  background:var(--surface);
}

.widget-rich__size-input{
  border:1px solid var(--line-light);
  border-radius:8px;
  padding:.2rem .35rem;
  background:var(--surface);
  width:4rem;
  text-align:center;
}

/* BACKLOG §1: the swatch row contains its own hidden colour input. See the note on `.widget-shellchip` —
   `.widget-rich__swatch-input` is absolute, and unpositioned this row let it escape the settings column. */
.widget-rich__swatches{
  position:relative;
  display:flex;
  flex-wrap:wrap;
  gap:4px;
  padding:.2rem 0;
}

.widget-rich__swatches--compact{
  gap:6px;
}

.widget-rich__swatch{
  width:1.25rem;
  height:1.25rem;
  border-radius:4px;
  border:2px solid transparent;
  cursor:pointer;
  padding:0;
  transition:border-color .1s, transform .1s;
}

.widget-rich__swatch:hover{
  border-color:var(--brass);
  transform:scale(1.15);
}

.widget-rich__swatch.is-active{
  border-color:#fff;
  box-shadow:0 0 0 2px var(--brass);
}

/* TD Phase 4 (T7): one of the site's OWN kept colours rather than one of the theme's roles. Same size and same
   behaviour — it is offered for the same reasons — with a dotted ring so the two kinds are not mistaken for each
   other inside a toolbar that has no room for a heading. Only the rows that carry the token key can mark this;
   in the content-block editors the kept colours are told apart by their position after the roles. */
.widget-rich__swatch--custom{border-style:dotted;border-color:var(--slate-soft)}
.widget-rich__swatch--custom:hover{border-style:solid}
/* The gap does the work the ring cannot at this size — see the .tm-rt-swatch--custom note. */
.widget-rich__swatch:not(.widget-rich__swatch--custom) + .widget-rich__swatch--custom{margin-left:7px}

.widget-rich__swatch-add{
  width:1.25rem;
  height:1.25rem;
  border:1px solid var(--line-light);
  border-radius:4px;
  background:var(--surface);
  color:var(--slate-soft);
  padding:0;
  line-height:1;
  font-weight:700;
  cursor:pointer;
}

.widget-rich__swatch-input{
  position:absolute;
  width:1px;
  height:1px;
  opacity:0;
  pointer-events:none;
}

.widget-rich__tools .btn{
  min-width:2rem;
  padding:.22rem .45rem;
  line-height:1.1;
}

/* F5/B2: rich-text blocks render uniformly on the home page (a plain .content-block-rich wrapper; inline styling in
   the block HTML drives typography). The editor is WYSIWYG-matched to that: one plain base style, no per-slot
   (eyebrow/heading/body) styling, and 1:1 font sizing. Uses the same font/colour as the home content-block
   text (var(--sans) on the ink surface) so what you style is what renders. */
/* Painted stand-in for the editor's selection while a toolbar field (font size / font select) holds
   focus — the browser stops drawing the native highlight then, even though the selection is still live
   (site.js mirrors savedRange through the CSS Custom Highlight API under this name). System Highlight
   colours match the native selection appearance. */
::highlight(rich-editor-saved-selection){
  background-color:Highlight;
  color:HighlightText;
}

/* SCC PHASE 6 — THE EDITOR'S GROUND FOLLOWS THE CARD'S SHELL, WHICH IS WHAT THE COMMENT ABOVE ALREADY
   PROMISED AND PHASE 2 QUIETLY BROKE.

   That comment says the editor is "WYSIWYG-matched" to the home render and uses "var(--sans) on the ink
   surface". It was true when a Content Block had exactly ONE look and its partial hardcoded
   `home-widget--content-block`. SCC Phase 2 turned the look into a stored value with SEVEN entries and the
   editor was never told: six of the seven are not ink, so authoring the Events or Links card — a `surface`
   card, white with --slate text — meant styling on a black editor for a card that renders on white. The
   promise inverted itself and nothing in the suite could see it, because no test compares the editor's
   ground to the card's.

   The pairs below are the second statement of a shell's ground, and that is worth naming rather than hiding:
   the first is the shell's own rule earlier in this file. They are written as the SAME TOKENS rather than the
   same values, so a theme switch moves both, and a shell that changes its ground has exactly one other place
   to be corrected — this block. A JS colour table was the alternative and it would have frozen the values at
   the moment the dialog opened.

   SCC PHASE 7a MADE IT A TABLE WITH TWO CONSUMERS RATHER THAN A SECOND ONE WITH THREE. The Section style chip
   grid needs all seven grounds AT ONCE, and painting the chips from their own hexes — which is what the gate
   artifact's miniatures do, and it named the drift risk out loud — would have been the THIRD statement of what
   a shell looks like. So the selector is the bare attribute rather than `.widget-edit-dialog[…]`: the dialog
   carries `data-widget-shell` and each chip carries its own, custom properties inherit, and a chip inside the
   dialog shadows the dialog's value for itself. One table, one place to correct, two readers.

   `join` is the one literal, and it is the band's own: its ground is a three-stop claret gradient authored in
   hex (not themed), and a 64px editor cannot show a gradient, so it takes the middle stop.

   `board` is not --ink-deep even though it composes onto `heritage`: the blocks sit on the INSET PANEL, which
   is why `.home-widget--shell-board > *` sets --slate. The editor follows the surface the text lands on, not
   the outermost box — and so does the chip, for the same reason. */
[data-widget-shell="surface"],
[data-widget-shell="card"],
[data-widget-shell="board"]{
  --widget-edit-shell-bg:var(--surface);
  --widget-edit-shell-fg:var(--slate);
}

[data-widget-shell="bare"]{
  --widget-edit-shell-bg:var(--parchment);
  --widget-edit-shell-fg:var(--slate);
}

[data-widget-shell="heritage"]{
  --widget-edit-shell-bg:var(--ink-deep);
  --widget-edit-shell-fg:var(--on-dark);
}

[data-widget-shell="join"]{
  --widget-edit-shell-bg:#5a1020;
  --widget-edit-shell-fg:var(--on-dark);
}

/* SCC Phase 7a: `feature` had no entry until now, because it was reachable as the FALLBACK — the ground a
   card with no stored shell gets, and the one every widget type that is not a Content Block gets, since those
   cards carry no shell input. A chip cannot fall back, so the value is written down; the fallbacks below stay
   because "no attribute at all" is still a case the dialog has. */
[data-widget-shell="feature"]{
  --widget-edit-shell-bg:var(--ink);
  --widget-edit-shell-fg:var(--on-dark);
}

.widget-rich__editor{
  min-height:64px;
  padding:.5rem .7rem;
  border:1px solid var(--line-light);
  border-radius:10px;
  background:var(--widget-edit-content-bg, var(--widget-edit-shell-bg, var(--ink)));
  color:var(--widget-edit-content-fg, var(--widget-edit-shell-fg, var(--on-dark)));
  overflow:auto;
  font-family:var(--sans);
  font-size:1rem;
  line-height:1.5;
}

.widget-rich__editor:focus{
  outline:none;
  border-color:var(--brass);
  box-shadow:0 0 0 3px rgba(165,129,53,.16);
}

/* F10 (P2): live preview of the block's line spacing inside the contenteditable — mirrors the home
   render's .content-block-rich--spaced rule (site.js sets the class + --para-gap var from the spacing stepper). */
.widget-rich__editor--spaced,
.widget-rich__editor--spaced *{
  line-height:calc(1em + var(--para-gap, 0px));
}

/* F10 (P2): the paragraph-spacing stepper row in the rich style panel. */
.widget-rich__spacing{
  display:flex;
  align-items:center;
  gap:.3rem;
  margin-top:.4rem;
}

.widget-rich__spacing-label{
  font-size:.68rem;
  font-weight:700;
  color:var(--slate);
  letter-spacing:.06em;
  text-transform:uppercase;
}

.widget-rich__spacing-unit{
  font-size:.7rem;
  color:var(--slate);
}

/* SCC Phase 2 (F4): the fit-to-card panel in the block editor. --slate on the panel ground rather than a
   fixed grey: an admin screen is themed like every other, and a hardcoded colour here is the defect class
   [[theme-contrast-crawler]] exists to catch. */
.widget-rich__fit{
  display:flex;
  align-items:center;
  gap:.5rem;
  font-size:.8rem;
  color:var(--slate);
  cursor:pointer;
}

.widget-rich__fit input{
  width:auto;
  margin:0;
  flex:0 0 auto;
}

.widget-rich__fit-hint{
  margin:.35rem 0 0 1.6rem;
  font-size:.7rem;
  line-height:1.45;
  color:var(--slate-soft);
}

/* BACKLOG ITEM 6: the link panel's own two classes. The ROWS are the layout panel's
   (.widget-layout-control__row / __label / .widget-layout-seg), deliberately borrowed rather than restated —
   they are generic by construction, the panel is a stack of label-plus-control rows like that one, and a
   second set of identical declarations is the drift this project has spent whole phases deleting.

   What is NOT borrowed is the field width: an .admin-input carries `width:100%`, which in a flex row beside a
   5.4rem label is not a width at all. The basis makes it share the row.

   THERE IS NO `min-width:0` HERE AND THAT IS A MEASUREMENT RATHER THAN AN OVERSIGHT. It was written first,
   on the usual reasoning that a flex item's automatic minimum is its content size and a long stored address
   would push the row wider than the column. Measured at the column's floor with a 78-character URL in the
   box, the field is 230.2px inside a 243px row with `min-width:auto` and 230.2px with `min-width:0` — the
   same number, because an <input>'s automatic minimum comes from its intrinsic sizing and not from its
   value, and the select's longest option is shorter than the basis. A declaration that changes no pixel is
   one more thing for the next reader to believe. */
.widget-rich__link-input{
  flex:1 1 11rem;
}

.widget-rich__link-remove{
  font-size:.7rem;
  padding:.2rem .5rem;
}

/* Disabled rather than hidden, so the row does not appear and disappear as the caret moves — the panel keeps
   the same four rows whether there is a link under the caret or not, which is what the mockup drew. */
.widget-rich__link-remove:disabled{
  opacity:.45;
  cursor:default;
}

/* The hint doubles as the panel's refusal line, so a refused address says why in the place the author was
   already reading rather than in an alert. Colour only — the text is the message. */
.widget-rich__fit-hint.is-refusal{
  color:var(--bad-ink);
  font-weight:600;
}

/* Per-slot editor styling (eyebrow/heading/body) removed in F5/B2: all rich editors are now uniform and
   WYSIWYG-match the plain home render. Authors apply any styling explicitly via the font toolbar (it is baked
   inline into the block HTML and renders identically on the home page). */

.widget-edit-dialog__actions-spacer{
  flex:1 1 auto;
}

.widget-edit-dialog__actions{
  display:flex;
  align-items:center;
  justify-content:flex-end;
  gap:.6rem;
  padding:.75rem 1.2rem;
  border-top:1px solid rgba(148,163,184,.24);
  background:var(--surface);
}

.widget-block-buttons{
  border:1px solid var(--line-light);
  border-radius:10px;
  padding:.7rem;
  display:grid;
  gap:.7rem;
  margin-top:.4rem;
}

.widget-block-buttons__row{
  display:grid;
  gap:.35rem;
  padding:.45rem;
  border:1px solid rgba(148,163,184,.3);
  border-radius:8px;
}

/* Mirrors the homepage .content-block-cta layout: a compact wrapping row of content-sized cards, so the editor
   shows the buttons at the same relative widths they render at (cards used to sit in a rigid 3-column
   grid that stretched each to a third of the dialog regardless of the button text). */
.widget-block-buttons__list{
  display:flex;
  flex-wrap:wrap;
  gap:.6rem;
  align-items:flex-start;
}

.widget-block-button-card{
  border:1px solid rgba(148,163,184,.35);
  border-radius:10px;
  padding:.38rem;
  background:var(--surface);
  display:grid;
  gap:.3rem;
  flex:0 1 auto;
  max-width:100%;
}

.widget-block-button-card.is-dragging{
  opacity:.55;
  border-color:var(--brass);
  box-shadow:0 10px 24px rgba(165,129,53,.2);
}

.widget-block-button-card__controls{
  display:flex;
  align-items:center;
  justify-content:flex-end;
  gap:.45rem;
}

.widget-block-button-card__drag{
  cursor:grab;
  border:1px solid var(--line-light);
  border-radius:6px;
  background:var(--surface);
  padding:.1rem .35rem;
  color:var(--slate-soft);
}

.widget-block-button-card__colors{
  display:grid;
  gap:.5rem;
}

.widget-block-button-card__settings{
  display:grid;
  gap:.45rem;
  padding:.34rem;
  border:1px solid rgba(148,163,184,.3);
  border-radius:8px;
  background:var(--surface);
}

.widget-block-button-card__text-preview{
  border-radius:999px;
  padding:12px 22px;
  font-size:.92rem;
  font-weight:600;
  line-height:1.2;
  text-align:center;
  border:1.5px solid transparent;
  box-shadow:none;
  /* Content-sized like the rendered .content-block-cta button (overrides the shared width:100% input rule);
     field-sizing lets the pill track its text as you type. Browsers without field-sizing fall back
     to the input's default width — still compact. */
  width:auto;
  field-sizing:content;
  min-width:9ch;
  max-width:100%;
}

.widget-block-button-card__text-preview::placeholder{
  color:inherit;
  opacity:.75;
}

.widget-block-button-card__swatch-group{
  display:grid;
  gap:.28rem;
}

.widget-block-button-card__swatch-group span{
  font-size:.75rem;
  color:var(--slate-soft);
}

.widget-block-button-card__colors label{
  display:flex;
  align-items:center;
  gap:.3rem;
  font-size:.75rem;
  color:var(--slate-soft);
}

.widget-block-buttons__toolbar{
  display:flex;
  align-items:center;
  justify-content:flex-start;
  gap:.6rem;
  flex-wrap:wrap;
}

.widget-repeater-add-btn{
  appearance:none;
  border:1px solid rgba(148,163,184,.55);
  border-radius:999px;
  background:var(--surface);
  color:var(--heading);
  font-size:.82rem;
  font-weight:700;
  letter-spacing:.03em;
  text-transform:uppercase;
  line-height:1;
  padding:.45rem .8rem;
  cursor:pointer;
  transition:border-color .15s ease, color .15s ease, background .15s ease, box-shadow .15s ease;
}

.widget-repeater-add-btn:hover{
  border-color:var(--brass);
  color:var(--heading);
  background:color-mix(in srgb, var(--brass) 10%, var(--surface));
  box-shadow:0 0 0 2px rgba(165,129,53,.14);
}

.widget-block-buttons__hint{
  font-size:.78rem;
  color:var(--slate-soft);
}

.widget-stat-repeater{
  border:1px solid var(--line-light);
  border-radius:10px;
  padding:.7rem;
  display:grid;
  gap:.7rem;
  margin-top:.4rem;
}

.widget-stat-repeater__list{
  display:flex;
  flex-wrap:wrap;
  gap:.65rem;
  align-items:flex-start;
}

/* Content-sized like the rendered .content-block-meta items: cards hug their stat text (the toolbar sets the
   practical floor) instead of growing to split the full dialog width between them. */
.widget-stat-repeater-card{
  border:1px solid rgba(148,163,184,.35);
  border-radius:10px;
  padding:.55rem;
  background:var(--surface);
  display:grid;
  gap:.45rem;
  min-width:0;
  flex:0 1 auto;
  max-width:320px;
}

.widget-stat-repeater-card__toolbar{
  display:flex;
  gap:.35rem;
  align-items:center;
  flex-wrap:wrap;
}

.widget-stat-repeater-card__controls{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:.45rem;
}

.widget-stat-repeater-card__settings{
  display:grid;
  gap:.35rem;
  padding:.42rem;
  border:1px solid rgba(148,163,184,.3);
  border-radius:8px;
  background:var(--surface);
}

.widget-stat-repeater-card__toolbar .btn{
  min-width:34px;
  padding:.2rem .35rem;
}

.widget-stat-repeater-card__color{
  width:34px;
  height:32px;
  border:1px solid var(--line-light);
  border-radius:8px;
  padding:0;
  background:var(--surface);
  cursor:pointer;
}

.widget-stat-repeater-card__swatch{
  width:20px;
  height:20px;
  border:1px solid rgba(148,163,184,.45);
  border-radius:6px;
  cursor:pointer;
}

.widget-stat-repeater-card__swatch.is-active{
  border:2px solid #a58135;
  box-shadow:0 0 0 2px rgba(165,129,53,.3);
}

.widget-stat-repeater-card__inputs{
  display:grid;
  gap:.35rem;
}

/* SCC Phase 6: the same chain as .widget-rich__editor. This one took a custom background but fell back to a
   hardcoded #fff — so on the four dark seeds a stat input was a white box in a dark dialog, and on a
   `heritage` card it was a white box standing in for an ink-deep one. Two different wrong answers in two
   editors for the same question, which is the argument for one chain rather than a default per control. */
.widget-stat-repeater-card__input{
  border:1px solid var(--line-light);
  border-radius:8px;
  padding:.45rem .55rem;
  background:var(--widget-edit-content-bg, var(--widget-edit-shell-bg, var(--ink)));
  color:var(--widget-edit-content-fg, var(--widget-edit-shell-fg, var(--on-dark)));
  min-height:38px;
  outline:none;
}

.widget-style-suite{
  display:grid;
  gap:.52rem;
}

.widget-style-suite__row{
  display:grid;
  gap:.3rem;
}

.widget-style-suite__label{
  font-size:.72rem;
  text-transform:uppercase;
  letter-spacing:.08em;
  color:var(--slate);
  font-weight:700;
}

.widget-style-suite__swatches{
  display:flex;
  flex-wrap:wrap;
  gap:6px;
  align-items:center;
}

.widget-style-suite__check{
  display:flex;
  align-items:center;
  gap:.45rem;
  font-size:.82rem;
  color:var(--slate);
  font-weight:500;
  cursor:pointer;
}

.widget-style-suite__check input{
  width:auto;
  margin:0;
}

.widget-style-suite__actions{
  display:flex;
  flex-wrap:wrap;
  gap:.4rem;
}

.widget-style-suite__select{
  border:1px solid var(--line-light);
  border-radius:8px;
  padding:.28rem .42rem;
  background:var(--surface);
  color:var(--heading);
  width:fit-content;
}

.widget-stat-repeater-card__input--primary{
  font-family:var(--serif);
  font-size:1.05rem;
  font-weight:600;
}

.widget-stat-repeater-card__input--secondary{
  font-size:.82rem;
  letter-spacing:.08em;
  text-transform:uppercase;
}

/* Placeholders must stay SHORT (site.js uses "e.g. 1929" style): this ::before text contributes to the
   card's intrinsic width now that stat cards are content-sized, so a wordy placeholder makes an empty
   card far wider than the filled ones. The min-width below keeps a truly empty input clickable. */
.widget-stat-repeater-card__input.is-empty::before,
.widget-stat-repeater-card__input:empty::before{
  content:attr(data-placeholder);
  color:rgba(100,116,139,.75);
}

.widget-stat-repeater-card__input{
  min-width:8ch;
}

.widget-stat-repeater-card__input:focus{
  border-color:var(--brass);
  box-shadow:0 0 0 2px rgba(165,129,53,.2);
}

.widget-stat-repeater-card__remove{
  justify-self:end;
  border:1px solid var(--line-light);
  border-radius:999px;
  width:30px;
  height:30px;
  background:var(--surface);
  color:var(--slate-soft);
  cursor:pointer;
}

.widget-stat-repeater-card__remove:hover{
  color:var(--heading);
  border-color:var(--brass);
}

.widget-edit-dialog__close{
  margin-left:auto;
  border:0;
  background:transparent;
  color:var(--slate-soft);
  font-size:1.6rem;
  line-height:1;
  cursor:pointer;
}

.widget-edit-dialog__close:hover{
  color:var(--heading);
}

@media(max-width:760px){
  .widgets-zone{padding:.75rem}
  .widgets-board{grid-template-columns:1fr}
  .widgets-board--scratch{grid-template-columns:1fr}
  /* SCC Phase 7b: below 760px the fixed 1132px surface would scale to about 0.6 and put the card labels
     under 8px, so the designer stops being a scale model of the board and goes back to being a list. The
     author is not choosing proportions on a phone; they are re-ordering cards. */
  .widgets-board--home{
    box-sizing:border-box;
    width:auto;
    grid-template-columns:1fr;
    gap:1rem;
    transform:none;
  }
  .widgets-boardframe{overflow:visible;height:auto !important}
  .widgets-card--wide,
  .widgets-card--full{grid-column:1}
  .raffle-admin-grid{grid-template-columns:1fr}
  .raffle-draw-panel__head{flex-direction:column;align-items:flex-start}
  .raffle-machine__frame{grid-template-columns:1fr}
  .raffle-machine__emblem{display:none}
  .raffle-machine__status{font-size:1.6rem}
  .raffle-machine__sub{font-size:1rem}
  .widget-block-buttons__list{grid-template-columns:1fr}
  .widget-block-buttons__toolbar{flex-direction:column;align-items:flex-start}
  .widget-stat-repeater-card{flex:1 1 100%;max-width:none;min-width:0}
}

/* Festive Board admin rows */
.fb-row{
  background:var(--surface);
  border:1px solid var(--line-light);
  border-radius:10px;
  padding:1.5rem;
  margin-bottom:1.25rem;
}

.fb-row--next{
  border-color:var(--brass);
  background:var(--parchment);
}

.fb-row--past{
  opacity:.75;
}

.fb-row__header{
  margin-bottom:1.25rem;
}

.fb-row__date{
  font-family:var(--serif);
  color:var(--heading);
  font-size:1.1rem;
  margin:.25rem 0 0;
}

.fb-row__cuisine{
  color:var(--brass-ink);
  font-weight:600;
  margin:.2rem 0 0;
  font-size:.95rem;
}

.fb-badge{
  display:inline-block;
  font-size:.68rem;
  letter-spacing:.1em;
  text-transform:uppercase;
  font-weight:700;
  font-family:var(--sans);
  padding:.2rem .6rem;
  border-radius:3px;
  background:var(--parchment-2);
  color:var(--slate);
}

.fb-badge--next{
  background:var(--brass);
  color:var(--on-brass);
}

.fb-badge--past{
  background:transparent;
  color:var(--slate-soft);
  border:1px solid var(--line-light);
}

.fb-row__docs{
  display:grid;
  grid-template-columns:1fr 1fr;
  gap:1.5rem;
}

@media(max-width:640px){
  .fb-row__docs{grid-template-columns:1fr}
}

.fb-doc-col__label{
  font-weight:700;
  color:var(--heading);
  font-size:.85rem;
  letter-spacing:.03em;
  margin:0 0 .6rem;
}

.fb-doc-col__empty{
  font-size:.85rem;
  color:var(--slate-soft);
  font-style:italic;
  margin:0 0 .6rem;
}

.fb-doc-col__preview{
  display:block;
  margin-bottom:.6rem;
  text-decoration:none;
}

.fb-doc-col__preview img{
  display:block;
  width:100%;
  max-height:220px;
  object-fit:contain;
  border:1px solid #ddd;
  border-radius:5px;
  background:#f5f5f5;
}

.fb-doc-col__preview span{
  display:block;
  font-size:.75rem;
  color:var(--brass-ink);
  margin-top:4px;
  text-align:center;
}

.fb-upload-form{
  margin-top:.6rem;
  display:flex;
  flex-direction:column;
  gap:.4rem;
}

.fb-file-input{
  font-size:.82rem;
}

.fb-upload-btn{
  font-size:.82rem;
  padding:.35rem .9rem;
  align-self:flex-start;
}

/* Enquiries */
.admin-enquiries{
  display:flex;
  flex-direction:column;
  gap:1rem;
  max-width:720px;
}

.admin-enquiry{
  background:var(--surface);
  border:1px solid var(--line-light);
  border-radius:8px;
  padding:1.2rem;
}

.admin-enquiry__header{
  display:flex;
  justify-content:space-between;
  align-items:baseline;
  gap:1rem;
  margin-bottom:.3rem;
}

.admin-enquiry__date{
  font-size:.8rem;
  color:var(--slate-soft);
}

.admin-enquiry__contact{
  font-size:.85rem;
  color:var(--slate-soft);
  margin-bottom:.5rem;
}

.admin-enquiry__message{
  margin:0;
  font-size:.95rem;
}

/* NS Phase 2 / Q11: mark-handled and the reply composer. The card sits on a WHITE surface, so nothing
   here may use .btn-ghost — it forces --on-dark and would be invisible. */
.admin-enquiry__actions{
  display:flex;
  align-items:center;
  flex-wrap:wrap;
  gap:.75rem;
  margin-top:.9rem;
}

.admin-enquiry__action{
  background:none;
  border:0;
  padding:0;
  font:inherit;
  font-size:.85rem;
  font-weight:600;
  color:var(--brass-ink);
  cursor:pointer;
  text-decoration:underline;
  text-underline-offset:3px;
}

.admin-enquiry__action:hover{
  color:var(--heading);
}

.admin-enquiry__note{
  font-size:.8rem;
  color:var(--slate-soft);
}

.admin-enquiry__badge{
  display:inline-block;
  margin-right:.5rem;
  padding:.1rem .5rem;
  border-radius:999px;
  background:var(--line-light);
  color:var(--slate);
  font-size:.72rem;
  font-weight:600;
  letter-spacing:.03em;
  text-transform:uppercase;
}

.admin-enquiry__reply{
  margin-top:.75rem;
  border-top:1px solid var(--line-light);
  padding-top:.75rem;
}

.admin-enquiry__reply textarea{
  width:100%;
  margin:.35rem 0 .5rem;
}

/* Inner page hero banner */
.page-hero{
  background:var(--ink);
  color:var(--on-dark);
  padding:64px 0 56px;
  border-bottom:3px solid var(--brass);
}

.page-hero h1{
  font-family:var(--serif);
  color:var(--on-dark);
  font-size:clamp(2rem,4vw,3.2rem);
  margin:0.6rem 0 0.9rem;
}

.page-hero .page-hero__lead{
  color:color-mix(in srgb, var(--on-dark) 78%, transparent);
  font-size:1.05rem;
  max-width:58ch;
  margin:0 0 0.5rem;
}

.page-hero--light{
  background:var(--parchment-2);
  color:var(--heading);
  border-bottom:3px solid var(--brass);
}

.page-hero--light h1{
  color:var(--heading);
}

.page-hero--light .page-hero__lead{
  color:var(--slate-soft);
}

.page-hero--light .eyebrow{
  color:var(--brass-ink);
}

/* ---------------------------------------------------------------------------------------------------------
   IB PHASE 5 - A PICTURE BEHIND THE BAND (migration 070, docs/image-background-plan.md section 16, gate
   Q9-Q14 answered 2026-08-23 - five on the recommendation and Q13 on the runner-up).

   TWO REAL CHILD ELEMENTS, for the card's reasons taken whole: `.page-hero` already owns `background`, a
   `filter` on the band would filter its own words, and a picture that can be blurred without blurring the
   heading has to be a separate box.

   AND THE STACKING IS SECTION 16.5, WHICH WAS PREDICTED BEFORE IT WAS WRITTEN. `.page-hero` has no
   containing block and nothing lifts its children, so an absolutely positioned scrim SIBLING paints in step
   8 where in-flow content paints in step 7 - the band would render EMPTY, which is exactly what was
   reported from the running site for the events card (section 15.5). The cure is the one that generalises:
   the layers go BEHIND the content (`isolation:isolate` here plus `z-index:-1` below) and nothing is
   lifted, so nothing can be missed. A `> * { z-index:1 }` lift would work on this band today - its content
   is a single `.wrap` - and is deliberately not what holds it, because "works on the shape we have today"
   is how the History card lost three of its five blocks.

   `overflow:hidden` is load-bearing rather than tidy (section 16.8 risk 5): the band is full-bleed and a
   `contain` fit letterboxes, so without it a letterboxed picture paints over the 3px brass rule underneath.
   --------------------------------------------------------------------------------------------------------- */
.page-hero--has-bg{
  position:relative;
  isolation:isolate;
  overflow:hidden;
}

.page-hero__bg,
.page-hero__scrim{
  position:absolute;
  inset:0;
  pointer-events:none;
  z-index:-1;
}

/* The fit is a CLASS and the URL is inline - the card's line between the two, kept: a vocabulary's lengths
   belong where a designer can see them, and only what is genuinely per-instance rides the element.
   `background-size` and `background-repeat` travel together because "tile" is both at once. */
.page-hero__bg{
  background-repeat:no-repeat;
}

.page-hero__bg--fit-cover{
  background-size:cover;
}

.page-hero__bg--fit-contain{
  background-size:contain;
}

.page-hero__bg--fit-tile{
  background-size:auto;
  background-repeat:repeat;
}

/* THE SCRIM, AND ITS TINT IS NOT A PER-TREATMENT RULE - WHICH IS GATE Q10 IN THE STYLESHEET. A band with a
   photograph behind it is a DARK band, so the tint is `--ink` and there is no `.page-hero--light` arm here
   at all. That is not an omission: `PageBannerTokens.BandClasses` never emits `--light` and `--has-bg`
   together, so an arm for it would be a rule nothing can match, and a reader would take its existence as
   evidence that a pale scrim is reachable. The four light bands are also outside the route vocabulary
   today; the rule is written now anyway, because the day one of them is included is the day it has to
   already be true.

   The alpha is the only part the band carries, and the tint is a THEME TOKEN rather than a hex so it follows
   a twelve-palette switch - the same arrangement, for the same reason, as the card's. */
.page-hero__scrim{
  background:color-mix(in srgb, var(--ink) calc(var(--card-scrim, 1) * 100%), transparent);
}

/* ---- the admin band (_AdminPageHeader.cshtml) ----------------------------------------------------
   Every page under /Admin renders the same header through one partial. The rules below are the ONLY
   place its back link and action row are styled, which is the point: the admin area used to have five
   different headers and five different (or missing) ways back to the hub.

   Accents here are var(--brass-soft), not var(--brass). This band is var(--ink), and --brass is a dark
   hue on College (a navy), Regalia (a crimson) and Ashlar (a slate) — brass-on-ink measures 1.75:1 on
   College. --brass is safe as a FILL, because ThemeStyleComposer.ComputeOnBrass picks the text against
   it; it is not safe as text on a dark surface. */
.page-hero--admin{
  padding:44px 0 40px;
}

.page-hero__back{
  margin:0 0 .85rem;
}

.page-hero__back-link{
  display:inline-flex;
  align-items:center;
  gap:.45rem;
  min-height:44px;              /* a thumb target on a phone (WCAG 2.5.5) */
  padding:.3rem .9rem .3rem .7rem;
  border:1px solid color-mix(in srgb, var(--brass-soft) 42%, transparent);
  border-radius:999px;
  background:color-mix(in srgb, var(--on-dark) 7%, transparent);
  color:var(--brass-soft);
  font-size:.9rem;
  letter-spacing:.04em;
  text-decoration:none;
}

.page-hero__back-link:hover,
.page-hero__back-link:focus-visible{
  border-color:var(--brass-soft);
  background:color-mix(in srgb, var(--on-dark) 14%, transparent);
  color:var(--on-dark);
}

.page-hero__back-arrow{
  font-size:1.05rem;
  line-height:1;
}

.page-hero__actions{
  display:flex;
  flex-wrap:wrap;
  gap:.6rem;
  margin-top:.9rem;
}

.page-hero__actions a,
.page-hero__actions button{
  display:inline-flex;
  align-items:center;
  gap:.4rem;
  min-height:44px;
  padding:.3rem 1rem;
  border:1px solid color-mix(in srgb, var(--brass-soft) 42%, transparent);
  border-radius:999px;
  background:transparent;
  color:var(--brass-soft);
  font-family:inherit;
  font-size:.9rem;
  cursor:pointer;
  text-decoration:none;
}

.page-hero__actions a:hover,
.page-hero__actions button:hover,
.page-hero__actions a:focus-visible,
.page-hero__actions button:focus-visible{
  border-color:var(--brass-soft);
  background:color-mix(in srgb, var(--on-dark) 12%, transparent);
  color:var(--on-dark);
}

@media (max-width:640px){
  /* Admin/FestiveBoard/Tonight is worked standing up in a hall on a phone. A full-height ceremonial band
     would push the collections list below the fold, so the band shrinks rather than the page losing it. */
  .page-hero{padding:34px 0 28px}
  .page-hero--admin{padding:26px 0 22px}
}

/* An admin body that follows the band should not also pay for the band's top padding. */
.page-hero--admin + .admin-sub{
  padding-top:2.25rem;
}

.page-body{
  padding:56px 0 80px;
}

/* Branded form */
.form-block{
  display:flex;
  flex-direction:column;
  gap:1.2rem;
}

.form-block--row{
  display:grid;
  grid-template-columns:1fr 1fr;
  gap:1.2rem;
}

@media(max-width:600px){
  .form-block--row{grid-template-columns:1fr}
}

.form-field{
  display:flex;
  flex-direction:column;
  gap:5px;
}

.form-field.span-full{
  grid-column:1 / -1;
}

.form-field label{
  font-size:.83rem;
  font-weight:600;
  color:var(--heading);
  letter-spacing:.03em;
}

.form-field input,
.form-field textarea,
.form-field select{
  padding:.6rem .85rem;
  border:1.5px solid #ccc;
  border-radius:6px;
  font-family:var(--sans);
  font-size:.97rem;
  color:var(--heading);
  background:var(--surface);
  transition:border-color .2s;
  width:100%;
}

.form-field input:focus,
.form-field textarea:focus,
.form-field select:focus{
  outline:none;
  border-color:var(--brass);
  background:var(--surface);
}

.form-field .val-msg{
  font-size:.8rem;
  color:var(--claret);
}

.form-field .hint{
  font-size:.8rem;
  color:var(--slate-soft);
}

.notice{
  padding:1rem 1.25rem;
  border-radius:8px;
  border-left:4px solid var(--brass);
  background:var(--parchment-2);
  color:var(--heading);
  font-size:.95rem;
  margin-bottom:1.5rem;
}

.notice--success{
  border-left-color:#3a7a45;
  background:#edf7ef;
  color:#1e4a26;
}

/* A notice that reports something the page has just UNDONE — /Members/Meals releasing an unfinished card
   payment. Built from the fixed status triple rather than a literal pair like --success above it: those
   three are deliberately opaque and deliberately un-themed (see the :root note) because a status colour
   composited over eleven different card grounds is how "Paid" once measured 2.04:1 on the dark seeds. */
.notice--warn{
  border-left-color:var(--warn-line);
  background:var(--warn-fill);
  color:var(--warn-ink);
}

/* Branded table */
.data-table{
  width:100%;
  border-collapse:collapse;
  font-size:.93rem;
}

.data-table thead th{
  font-family:var(--sans);
  font-size:.72rem;
  font-weight:700;
  letter-spacing:.12em;
  text-transform:uppercase;
  color:var(--brass-ink);
  border-bottom:2px solid var(--brass);
  padding:.6rem .75rem;
  text-align:left;
}

.data-table tbody tr{
  border-bottom:1px solid var(--line-light);
}

.data-table tbody tr:last-child{
  border-bottom:none;
}

.data-table tbody td{
  padding:.7rem .75rem;
  color:var(--slate);
  vertical-align:top;
}

.data-table tbody tr:hover td{
  background:color-mix(in srgb, var(--brass) 6%, transparent);
}

.data-table--wrap{
  overflow-x:auto;
}

/* Members hub */
.members-grid{
  display:grid;
  grid-template-columns:repeat(auto-fill,minmax(260px,1fr));
  gap:1.25rem;
  margin-top:2.5rem;
}

.member-card{
  background:var(--surface);
  border:1px solid var(--line-light);
  border-radius:10px;
  padding:1.5rem;
  display:flex;
  flex-direction:column;
  gap:0.75rem;
  text-decoration:none;
  color:var(--heading);
  transition:box-shadow .2s, border-color .2s;
}

.member-card:hover{
  border-color:var(--brass);
  box-shadow:0 4px 18px rgba(0,0,0,.08);
}

.member-card__icon{
  font-size:1.8rem;
  line-height:1;
}

.member-card__title{
  font-family:var(--serif);
  font-size:1.15rem;
  color:var(--heading);
  margin:0;
}

.member-card__meta{
  font-size:.88rem;
  color:var(--slate-soft);
  margin:0;
}

.member-card .btn{
  align-self:flex-start;
  margin-top:auto;
}

/* Members sub-pages */
.info-card{
  background:var(--parchment);
  border:1px solid var(--line-light);
  border-left:4px solid var(--brass);
  border-radius:8px;
  padding:1.25rem 1.5rem;
  margin-bottom:1.75rem;
}

.info-card p{
  margin:0 0 .35rem;
  font-size:.95rem;
}

.info-card p:last-child{margin:0}

.info-card strong{color:var(--heading)}

.bookings-list{
  display:flex;
  flex-direction:column;
  gap:.75rem;
}

.booking-row{
  background:var(--surface);
  border:1px solid var(--line-light);
  border-radius:8px;
  padding:1rem 1.25rem;
}

.booking-row__title{
  font-weight:600;
  color:var(--heading);
  font-size:.95rem;
}

.booking-row__meta{
  font-size:.82rem;
  color:var(--slate-soft);
  margin-top:3px;
}

/* ================================================================================================
   ITEM 32 -- the members' updates list. The .nfy- family.

   What it replaces: .notifications-toolbar and .notification-row, which were a shared ruleset with
   .booking-row (site.css:7761-7772) and an absolutely positioned Delete. The shared half is left
   exactly where it is for /Members/Bookings; only the notification-specific rules are gone.

   The row is a fixed white surface, NOT a theme token, so its quiet controls cannot use --ink or
   --on-dark without going invisible on one theme or another -- the trap recorded in
   [[theme-render-mapping-tokens]]. They are quiet text until hovered: a red button on every row of a
   list somebody reads is louder than the list.
   ================================================================================================ */
.nfy-lead{font-size:.92rem;color:var(--slate);margin:0 0 .85rem}
.nfy-lead b{color:var(--heading)}

/* The chips. Links, not buttons, so the back button works and a filtered view is a URL a member can
   keep -- and so the page needs no script at all. */
.nfy-chips{display:flex;flex-wrap:wrap;gap:.35rem;margin:0 0 .9rem;padding:0;list-style:none}
.nfy-chip{
  display:inline-flex;align-items:center;gap:.3rem;
  border:1px solid var(--line-light);background:var(--surface);color:var(--slate);
  border-radius:999px;padding:.24rem .68rem;font-size:.78rem;font-weight:600;
  text-decoration:none;line-height:1.3;
}
.nfy-chip:hover{border-color:var(--brass);color:var(--claret)}
.nfy-chip:focus-visible{outline:2px solid var(--brass);outline-offset:2px}
.nfy-chip__n{font-weight:500;color:var(--slate-soft);font-variant-numeric:tabular-nums}
.nfy-chip--on{background:var(--brass-soft);border-color:var(--brass-ink);color:var(--heading)}
.nfy-chip--on .nfy-chip__n{color:var(--heading)}

.nfy-bar{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:.4rem;margin:0 0 .75rem}
.nfy-bar__btn{
  border:1px solid var(--line-light);background:var(--surface);color:var(--heading);
  border-radius:7px;padding:.4rem .85rem;font:inherit;font-size:.8rem;font-weight:600;cursor:pointer;
}
.nfy-bar__btn:hover{border-color:var(--brass);color:var(--claret)}
/* The destructive one of the pair. Claret on the label alone: a red block beside an identical
   outlined button would make the benign one look like the afterthought, and this page's rows already
   settle that a quiet control is the right weight for a list somebody reads. */
.nfy-bar__btn--danger{color:var(--claret)}
.nfy-bar__btn--danger:hover{border-color:var(--claret)}
.nfy-bar__btn:focus-visible{outline:2px solid var(--brass);outline-offset:2px}

/* The time bands. An h2 for the outline, drawn small -- a member scanning for last week's summons
   wants a signpost, not a second page title. */
.nfy-band{
  font-family:var(--serif);font-size:.82rem;font-weight:600;letter-spacing:.04em;text-transform:uppercase;
  color:var(--slate-soft);margin:1.15rem 0 .5rem;padding-bottom:.25rem;border-bottom:1px solid var(--line-light);
}
.nfy-band:first-of-type{margin-top:0}

.nfy-row{
  display:flex;align-items:flex-start;gap:.75rem;
  background:var(--surface);border:1px solid var(--line-light);border-left:3px solid transparent;
  border-radius:8px;padding:.85rem 1rem;margin:0 0 .6rem;
}
/* Unread is a tint AND a brass edge AND the word "Unread." in the markup. The first two are colour
   alone, which WCAG 1.4.1 does not accept on its own. */
.nfy-row--unread{background:color-mix(in srgb, var(--brass) 5%, var(--surface));border-left-color:var(--brass-ink)}

.nfy-row__go{
  display:flex;align-items:flex-start;gap:.75rem;flex:1;min-width:0;
  color:inherit;text-decoration:none;border-radius:5px;
}
.nfy-row__go:focus-visible{outline:2px solid var(--brass);outline-offset:2px}
a.nfy-row__go:hover .nfy-row__title{color:var(--claret);text-decoration:underline}
.nfy-row__icon{
  width:34px;height:34px;flex:0 0 34px;border-radius:999px;
  display:grid;place-items:center;background:var(--parchment-2);font-size:16px;
}
.nfy-row--unread .nfy-row__icon{background:color-mix(in srgb, var(--brass) 20%, var(--surface))}
.nfy-row__body{flex:1;min-width:0}
.nfy-row__title{display:block;font-weight:600;color:var(--heading);font-size:.95rem}
.nfy-row__meta{display:block;font-size:.76rem;color:var(--slate-soft);margin-top:2px}
.nfy-row__msg{display:block;margin:.35rem 0 0;font-size:.9rem;color:var(--slate)}

/* In the flow rather than absolutely positioned, which is what lets a row carry two controls without
   the title needing a padding-right the size of the widest possible pair. */
.nfy-row__acts{display:flex;align-items:center;gap:.15rem;flex:0 0 auto;padding-top:.1rem}
.nfy-act{
  border:0;background:none;padding:.2rem .4rem;margin:0;
  font:inherit;font-size:.74rem;font-weight:600;line-height:1;
  color:var(--slate-soft);cursor:pointer;border-radius:5px;white-space:nowrap;
}
.nfy-act:hover{color:var(--claret)}
.nfy-act:focus-visible{outline:2px solid var(--brass);outline-offset:2px}
.nfy-act__sep{color:var(--line-light);font-size:.74rem}

.nfy-more{margin:.9rem 0 0;text-align:center;font-size:.85rem}
.nfy-more a{color:var(--brass-ink);font-weight:600;text-decoration:none}
.nfy-more a:hover{text-decoration:underline}

.nfy-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}

/* On a phone the two controls drop under the words rather than squeezing the title into a column. */
@media (max-width:560px){
  .nfy-row{flex-wrap:wrap}
  .nfy-row__acts{flex-basis:100%;justify-content:flex-end;padding-top:.35rem}
}

/* Login page */
.login-wrap{
  max-width:440px;
  margin:0 auto;
}

.login-hint{
  font-size:.83rem;
  color:var(--slate-soft);
  margin-bottom:1.5rem;
}

/* THE HONEYPOT — item 39 slice 3 on /Account/Register, and item 48 slice 2 on /Contact.
   NOT display:none and NOT visibility:hidden. Both are read by naive robots as "skip this", which is
   exactly the population this field exists to catch; a hidden field a robot ignores catches nothing. It is
   moved off-screen instead, and it carries tabindex="-1" and aria-hidden in the markup so that a person
   using a keyboard or a screen reader never reaches it either.

   Renamed from .reg-hp when the contact form took the same defence: a class prefixed "reg-" on a page that
   is not registration is the name-that-lies shape this project keeps paying for. */
.hp-field{
  position:absolute;
  left:-9999px;
  width:1px;
  height:1px;
  overflow:hidden;
}

/* THE SPAM CHALLENGE — item 48 slice 3.

   The widget itself is an iframe Cloudflare sizes and styles; this block owns only the space around it and
   the sentence a visitor with no JavaScript is shown instead.

   min-height RESERVES THE WIDGET'S ROW BEFORE THE SCRIPT ARRIVES. Turnstile's script is async, so without
   a reservation the Send button sits under the message box, moves 65px down the moment the widget renders,
   and lands under whatever the visitor's finger was already travelling towards. 65px is the flexible
   widget's own height; the box is allowed to grow past it and never to be shorter.

   IT COLLAPSES TO NOTHING WHEN NOTHING IS DRAWN, which is the ordinary case: the partial renders this
   element only while the door is armed, so a disarmed form has no empty reserved band in it. */
.captcha-field{
  min-height:65px;
  margin:0;
}

.captcha-field__note{
  margin:0;
  font-size:.85rem;
  line-height:1.6;
  color:var(--slate);
}

/* PAGE-LEVEL ERRORS ON A BRANDED FORM — item 48 slice 2.
   .val-summary shipped with item 39 and NOTHING IN THIS STYLESHEET MATCHED IT: the one sentence the
   throttle exists to say rendered as an unstyled bullet above the form. Keyed off
   .validation-summary-errors and not :not(:empty) for the reason .fbm-errors gives — a valid ModelOnly
   summary still renders a <ul> with a hidden <li>, so an :empty test is false for it and every clean load
   would grow an empty claret box. */
.val-summary.validation-summary-errors{
  background:rgba(111,36,53,.06);
  border:1px solid rgba(111,36,53,.3);
  border-radius:8px;
  padding:.9rem 1.25rem;
  margin:0 0 1.5rem;
  color:var(--claret);
  font-size:.92rem;
}

.val-summary ul{margin:0;padding-left:1.1rem}

/* Checkbox/radio in branded forms */
.form-check-row{
  display:flex;
  align-items:center;
  gap:.6rem;
}

.form-check-row label{
  font-size:.93rem;
  color:var(--slate);
  cursor:pointer;
}

/* LH Phase 3 — four rules stood here, on .history-entries and .history-entry, and painted nothing
   (plan §2 and §7). No asterisk is spelled in this note on purpose: a CSS comment ends at its FIRST
   terminator, and this project has already lost the rule after a comment that contained one. They
   drew the History page BEFORE the timeline markup replaced them, and no element has carried either
   class since. The timeline below is what /History and /Admin/History both render, through
   _HistoryTimeline.cshtml and _HistoryEntry.cshtml, and its names all begin .hist- instead. */

/* ── History timeline ──────────────────────────────────────────────────── */
.hist-timeline{
  position:relative;
  display:flex;
  flex-direction:column;
  gap:0;
  padding-left:0;
}

.hist-timeline::before{
  content:"";
  position:absolute;
  left:84px;
  top:0;
  bottom:0;
  width:2px;
  background:linear-gradient(180deg,transparent,var(--brass) 6%,var(--brass) 94%,transparent);
  opacity:.4;
}

.hist-entry{
  display:grid;
  grid-template-columns:84px 1fr;
  gap:0 2rem;
  padding:0 0 3.5rem;
  position:relative;
}

.hist-entry__marker{
  display:flex;
  flex-direction:column;
  align-items:flex-end;
  padding-top:.2rem;
  position:relative;
}

.hist-entry__marker::after{
  content:"";
  position:absolute;
  right:-13px;
  top:.55rem;
  width:10px;
  height:10px;
  border-radius:50%;
  background:var(--brass);
  border:2px solid var(--parchment);
  box-shadow:0 0 0 2px var(--brass);
  z-index:1;
}

.hist-era{
  font-family:var(--sans);
  font-size:.68rem;
  font-weight:700;
  letter-spacing:.1em;
  text-transform:uppercase;
  color:var(--brass-ink);
  text-align:right;
  line-height:1.3;
  padding-right:1.25rem;
}

.hist-entry__body{
  padding-top:.1rem;
}

.hist-entry__body h2{
  font-family:var(--serif);
  font-size:clamp(1.3rem,2.2vw,1.7rem);
  color:var(--heading);
  margin:0 0 1rem;
  line-height:1.2;
}

.hist-entry__body p{
  color:var(--slate);
  line-height:1.8;
  margin:0 0 .9rem;
  max-width:68ch;
}

.hist-entry__body p:last-child{
  margin-bottom:0;
}

/* SCC Phase 6, the second of the two /History defects (plan §6 Q4). The attribution below measured
   4.10:1 on Grammarians — #505054 on #C4B6B6 — and the ground is what was wrong, not the text.

   A DERIVED TOKEN'S FLOOR IS A PROMISE ABOUT A FIXED LIST OF GROUNDS. `--slate-soft` is not emitted as
   the theme stores it: ThemeStyleComposer.ComputeReadableAccent darkens it in 1/64 steps until it clears
   4.5:1 against the worst of --surface, --parchment and --parchment-2, and returns the FIRST value that
   does — so by construction it lands with almost no headroom (on Grammarians it clears --parchment at
   4.60:1). This rule then invented a FOURTH ground by washing `transparent`, which means whatever the page
   happens to paint; 7% of a saturated red over parchment costs about 0.5 of ratio and the floor had 0.10.
   `.hist-verse` below is the control: same token, same role, same page, on `var(--parchment-2)` — a ground
   on the list — and it passes on all eleven.

   So the wash names its base. That is also this stylesheet's own majority idiom — roughly twenty brass
   washes already say `, var(--surface)` — and washing `transparent` is precisely what stops a rule from
   knowing its own ground. Measured on all eleven palettes: worst case 4.90 (Alabaster) against 4.11 today,
   and 7.05 on Grammarians. THE TIDY-LOOKING CANDIDATE WAS WORSE THAN TODAY — washing --parchment-2 fails
   on Alabaster (4.30), College (4.39) and Laurel (4.27) — which is why this was measured rather than
   reasoned.

   IT IS A MEASUREMENT AND NOT A GUARANTEE, and the honest half of the fix is the other file: /History is
   now in ThemeContrastSweepPlaywrightTests' standing guard, which is the gap that let this ship. Five more
   light palettes pass here by under half a point (Laurel 5.01, Alabaster 4.90), so a twelfth palette is a
   real possibility and the guard is what will say so. */
.hist-quote{
  border-left:3px solid var(--brass);
  margin:1.25rem 0;
  padding:.9rem 1.25rem;
  background:color-mix(in srgb, var(--brass) 7%, var(--surface));
  border-radius:0 6px 6px 0;
}

.hist-quote p{
  font-family:var(--serif);
  font-style:italic;
  font-size:1.05rem;
  color:var(--heading);
  margin:0 0 .5rem !important;
  line-height:1.7;
}

.hist-quote footer{
  font-size:.8rem;
  color:var(--slate-soft);
  font-style:normal;
  letter-spacing:.03em;
}

.hist-verse{
  border:1px solid color-mix(in srgb, var(--brass) 35%, transparent);
  background:var(--parchment-2);
  border-radius:8px;
  padding:1.25rem 1.5rem;
  margin:1.25rem 0;
  text-align:center;
}

.hist-verse p{
  font-family:var(--serif);
  font-style:italic;
  font-size:1.05rem;
  color:var(--heading);
  line-height:1.85;
  margin:0 0 .75rem !important;
}

.hist-verse footer{
  font-size:.8rem;
  color:var(--slate-soft);
  letter-spacing:.04em;
}

.hist-alumni{
  display:grid;
  grid-template-columns:repeat(3,1fr);
  gap:1rem;
  margin:1.25rem 0;
}

@media(max-width:700px){
  .hist-alumni{grid-template-columns:1fr}
}

.hist-alumnus{
  background:var(--surface);
  border:1px solid var(--line-light);
  border-top:3px solid var(--brass);
  border-radius:0 0 8px 8px;
  padding:1rem 1.1rem 1.1rem;
}

.hist-alumnus__dates{
  font-size:.72rem;
  font-weight:700;
  letter-spacing:.1em;
  text-transform:uppercase;
  color:var(--brass-ink);
  margin:0 0 .3rem !important;
}

.hist-alumnus__name{
  font-family:var(--serif);
  font-size:1.05rem;
  color:var(--heading);
  margin:0 0 .5rem;
}

.hist-alumnus__bio{
  font-size:.88rem;
  color:var(--slate-soft);
  line-height:1.6;
  margin:0 !important;
}

.hist-event{
  border-radius:8px;
  padding:1rem 1.25rem;
  margin:1.1rem 0;
  display:grid;
  grid-template-columns:auto 1fr;
  gap:.5rem 1.1rem;
  align-items:baseline;
}

.hist-event--dark{
  background:var(--ink);
  color:var(--on-dark);
}

.hist-event--gold{
  background:color-mix(in srgb, var(--brass) 10%, transparent);
  border:1px solid color-mix(in srgb, var(--brass) 35%, transparent);
}

.hist-event__date{
  font-family:var(--sans);
  font-size:.72rem;
  font-weight:700;
  letter-spacing:.1em;
  text-transform:uppercase;
  white-space:nowrap;
  margin:0 !important;
}

.hist-event--dark .hist-event__date{ color:var(--brass-soft); }
.hist-event--gold .hist-event__date{ color:var(--heading); }

.hist-event__desc{
  font-size:.93rem;
  line-height:1.65;
  margin:0 !important;
}

.hist-event--dark .hist-event__desc{ color:color-mix(in srgb, var(--on-dark) 85%, transparent); }
.hist-event--gold .hist-event__desc{ color:var(--slate); }

.hist-motto{
  font-family:var(--serif);
  font-size:1.3rem;
  color:var(--heading);
  margin:1rem 0 0 !important;
  display:flex;
  align-items:baseline;
  gap:.75rem;
  flex-wrap:wrap;
}

.hist-motto span{
  font-size:.9rem;
  color:var(--slate-soft);
  font-style:normal;
  font-family:var(--sans);
}

@media(max-width:620px){
  .hist-timeline::before{ left:60px; }
  .hist-entry{ grid-template-columns:60px 1fr; gap:0 1rem; }
  .hist-entry__marker::after{ right:-9px; }
  .hist-era{ font-size:.62rem; padding-right:1rem; }

  /* SCC Phase 6, the first of the two /History defects MJ Phase 8 left here (plan §6 Q4).
     The date/description pair stacks below this breakpoint, and it is the ONLY thing that removes
     the defect rather than moving it.

     `.hist-event` is `auto 1fr`, and BOTH tracks have a floor: the date is `white-space:nowrap`
     (103.08px for "October 1966") and a bare `1fr` is `minmax(auto,1fr)`, so its min-content is the
     description's longest word (108.97px). With the gap, the padding and the border that is a
     271.65px MINIMUM, which becomes the min-content of the `1fr` track in `.hist-entry` above, and
     `.hist-entry__body` therefore stops shrinking at 271.64px on every viewport.

     MJ recorded this as "4px at 360px" and 4px is not the defect, it is the WIDTH IT WAS MEASURED AT.
     The body's right edge sits at 363.64px whatever the viewport, so the escape is 3.64px at 360 and
     44px at 320 — one floor read at one width. Anything narrower than 379.64px overflows.

     Stacking is what makes the floor go away instead of shrinking it: with one column the minimum is
     max(103.08, 108.97) + 42 = 150.97px, and no edit to the date or the prose can push it back over.
     Shaving the padding or the gap would have bought pixels against THIS text and handed the next
     author a page that breaks when they lengthen a date. The breakpoint is this query's own 620px,
     reused rather than invented: below it this page is already one narrow column of prose, which is
     what the `60px 1fr` line above says. */
  .hist-event{ grid-template-columns:1fr; }
}

/* Section with parchment bg */
.section--parchment{
  background:var(--parchment-2);
}

/* Meetings page list */
.meetings-list{
  display:flex;
  flex-direction:column;
  gap:0;
  margin-bottom:3rem;
}

.meeting-row{
  display:grid;
  grid-template-columns:110px 1fr auto;
  gap:1rem;
  align-items:center;
  padding:1rem 0;
  border-bottom:1px solid var(--line-light);
}

.meeting-row:first-child{
  border-top:1px solid var(--line-light);
}

.meeting-row__date{
  font-family:var(--serif);
  color:var(--heading);
  font-size:.95rem;
  font-weight:500;
}

.meeting-row__type{
  font-size:.93rem;
  color:var(--slate);
}

.meeting-row__type small{
  display:block;
  font-size:.77rem;
  color:var(--slate-soft);
  margin-top:2px;
}

.meeting-row .tag{
  font-size:.68rem;
  letter-spacing:.1em;
  text-transform:uppercase;
  font-weight:700;
  font-family:var(--sans);
  padding:.2rem .65rem;
  border-radius:3px;
  background:var(--parchment-2);
  color:var(--slate);
  border:1px solid var(--line-light);
  white-space:nowrap;
}

.meeting-row .tag--installation{
  background:color-mix(in srgb, var(--brass) 15%, transparent);
  color:var(--heading);
  border-color:var(--brass-soft);
}

@media(max-width:560px){
  .meeting-row{grid-template-columns:80px 1fr;grid-template-rows:auto auto}
  .meeting-row .tag{grid-column:2}
}

/* Contact page two-col responsive */
.contact-grid{
  display:grid;
  grid-template-columns:1fr 400px;
  gap:3rem;
  align-items:start;
}

@media(max-width:820px){
  .contact-grid{grid-template-columns:1fr}
}

/* Meals/Raffles responsive two-col */
.members-form-grid{
  display:grid;
  grid-template-columns:1fr 360px;
  gap:2.5rem;
  align-items:start;
}

@media(max-width:760px){
  .members-form-grid{grid-template-columns:1fr}
}

.member-raffle-tickets{
  margin-top:2rem;
}

.member-raffle-tickets--history{
  margin-top:1rem;
}

.member-raffle-tickets__head{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:.7rem;
  margin-bottom:.7rem;
}

.member-raffle-tickets__head .eyebrow{
  margin:0;
}

.member-raffle-tickets__count{
  font-size:.82rem;
  letter-spacing:.08em;
  text-transform:uppercase;
  color:var(--slate-soft);
  font-weight:700;
}

.member-raffle-tickets__grid{
  display:grid;
  grid-template-columns:repeat(auto-fill,minmax(170px,1fr));
  gap:.7rem;
}

.member-raffle-tickets__grid--history{
  grid-template-columns:repeat(auto-fill,minmax(150px,1fr));
}

.member-raffle-ticket{
  position:relative;
  display:flex;
  flex-direction:column;
  gap:.25rem;
  padding:.85rem .9rem;
  border-radius:10px;
  border:1px dashed color-mix(in srgb, var(--brass) 70%, transparent);
  background:
    radial-gradient(circle at 12px 50%, transparent 9px, color-mix(in srgb, var(--brass) 15%, transparent) 9px 10px, transparent 10px),
    radial-gradient(circle at calc(100% - 12px) 50%, transparent 9px, color-mix(in srgb, var(--brass) 15%, transparent) 9px 10px, transparent 10px),
    linear-gradient(180deg,#fffaf0,#f7ecd0);
  box-shadow:0 8px 18px rgba(11,24,53,.08);
}

.member-raffle-ticket__label{
  font-size:.66rem;
  letter-spacing:.11em;
  text-transform:uppercase;
  font-weight:700;
  color:var(--brass-ink);
  white-space:nowrap;
  overflow:hidden;
  text-overflow:ellipsis;
}

.member-raffle-ticket__number{
  font-family:var(--serif);
  font-size:1.7rem;
  letter-spacing:.12em;
  color:var(--heading);
  line-height:1.1;
}

.member-raffle-ticket__meta{
  font-size:.75rem;
  color:var(--slate-soft);
}

.member-raffle-ticket--history{
  padding:.72rem .78rem;
}

.member-raffle-ticket--history .member-raffle-ticket__number{
  font-size:1.35rem;
}

.member-winning-draw__icon{
  width:24px;
  height:24px;
  color:var(--brass-ink);
  opacity:.92;
  flex-shrink:0;
  align-self:center;
}

.member-winning-draw__icon svg{
  width:100%;
  height:100%;
}

.history-pager{
  margin-top:1rem;
  display:flex;
  align-items:center;
  justify-content:flex-end;
  gap:.55rem;
  flex-wrap:wrap;
}

.history-pager .btn{
  padding:8px 14px;
}

.history-pager .btn-ghost{
  color:var(--heading) !important;
  border-color:var(--line-light);
  background:var(--surface);
}

.history-pager .btn-ghost:hover{
  color:var(--heading) !important;
  border-color:var(--brass);
  background:var(--parchment);
}

.history-pager__meta{
  font-size:.84rem;
  color:var(--slate-soft);
  font-weight:600;
}

.history-pager__disabled{
  opacity:.45;
  pointer-events:none;
}

.history-toolbar{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:.8rem;
  flex-wrap:wrap;
}

.history-toolbar h2{
  margin:0;
}

/* ITEM 64 SLICE 3 ADDED flex-wrap AND min-width, AND THE REASON IS THAT THE CLASS OUTGREW ITS ORIGIN.
   It was built for ONE label and ONE select - "Items per page" - where a nowrap row is exactly right.
   Slice 3 put four and five children in it on /Admin/Mentoring, /Admin/Attendance, /Admin/Requests,
   /Admin/RaffleTickets and /Admin/Documents, and a nowrap flex row cannot shrink: /Admin/Mentoring
   overflowed its 360px viewport BY 188px, which MentoringPlaywrightTests caught.

   The search box is what needs the min-width:0 - a flex item's default min-width is auto, which is its
   content, so an input refuses to shrink below its size attribute and pushes the row wider than the
   screen no matter how much wrapping is allowed above it. */
.history-size-form{
  display:flex;
  flex-wrap:wrap;
  align-items:center;
  gap:.45rem;
}

.history-size-form input[type="search"],
.history-size-form input[type="text"]{min-width:0;flex:1 1 9rem}

.history-size-form label{
  font-size:.78rem;
  color:var(--slate-soft);
  font-weight:600;
  letter-spacing:.03em;
}

.history-size-form select{
  border:1px solid var(--line-light);
  border-radius:8px;
  background:var(--surface);
  color:var(--heading);
  padding:.32rem .5rem;
  font-size:.86rem;
}

/* ── Admin landing ─────────────────────────────────────────────────────────────
   The admin landing cards (.adm-*).

   SCC Phase 7m: this header used to open, re-open and then CLOSE inside itself — "New admin landing cards
   (adm-*)", a second slash-star, a close, and then a rule of box-drawing characters followed by one more
   close. A comment ends at its first close, so the box-drawing rule and the trailing close were garbage at
   the top level, error recovery swallowed `.adm-page-head`, and its `padding:56px 0 32px` was DROPPED — the
   admin landing head has been rendering at 0 padding. Found by the same scan that found the phone board
   (build record §28). */
.adm-page-head{
  padding:56px 0 32px;
}

.adm-page-head h1{
  font-family:var(--serif);
  font-size:clamp(2.4rem,5vw,3.6rem);
  color:var(--heading);
  margin:.5rem 0 0.5rem;
}

.adm-page-lead{
  color:var(--slate-soft);
  font-size:1.05rem;
  margin:14px 0 0;
}

.keystone-rule{
  display:flex;
  align-items:center;
  justify-content:center;
  gap:18px;
  color:var(--brass-ink);
  padding:32px 0;
}

.keystone-rule span{
  height:1px;
  width:min(120px,22vw);
  background:linear-gradient(90deg,transparent,color-mix(in srgb, var(--brass) 70%, transparent));
}

.keystone-rule span:last-child{
  background:linear-gradient(90deg,color-mix(in srgb, var(--brass) 70%, transparent),transparent);
}

.keystone-rule svg{
  width:24px;
  height:24px;
}

.adm-group{
  padding-bottom:32px;
}

.page-hero + .adm-group{
  padding-top:2.5rem;
}

/* =====================================================================================
   ITEM 54 HALF A - THE HUB'S SIX GROUPS (gate answers Q12(a), Q13(a), Q14(a)).

   THE THREE RULES ABOVE AND THE FOUR BELOW WERE WRITTEN YEARS AGO AND USED BY NOTHING.
   `.adm-group__head`, its h2, its eyebrow and `.adm-rule` were used by ZERO Razor files;
   `.adm-group` was one <section> on one page. The grouped hub was designed, styled and
   never built, and every reader of every page has been downloading the rules since. Half
   A is mostly markup because the look was already paid for.

   ------------------------------------------------------------------------------------
   WHY EVERY GROUP SHIPS CLOSED, AND WHAT ACTUALLY OPENS IT AT A DESK

   Q13(a) asks for "collapsed on a phone, open at a desk" out of ONE document with no
   script. `<details>` cannot express that on its own: `open` is STATE, not style, and no
   media query can set an attribute. So every group ships closed and a desktop rule has to
   override the user agent's own hiding.

   THE PLAN BOOKED THAT OVERRIDE AS `display:grid` ON THE CONTENT, ON THE STRENGTH OF A
   DEFECT THIS PROJECT HAD ALREADY PAID FOR - "an author `display` beats the UA rule that
   hides a closed <details>", item 46. IT IS NO LONGER TRUE. Probed in both engines the
   suite runs, at 390 and 1440, before any of this was written:

       display override alone, 1440 : Chromium card 474x60 but checkVisibility()=false,
                                      WebKit the same, and the SCREENSHOT is blank.
       display:contents on <details>: same, in both.
       ::details-content override    : Chromium and WebKit both visible at 1440 and both
                                      hidden at 390. The cards paint.

   Both engines now implement a closed <details> as `::details-content { content-visibility:
   hidden }` rather than as `display:none` on the children, and content-visibility on the
   wrapper is not something a `display` on the child can answer. `content-visibility:visible`
   is the load-bearing declaration and the ONLY one: `block-size:auto` beside it was probed
   on its own and revealed nothing, so it is not here.

   THE `display` PAIR IS KEPT ALL THE SAME, AND NOT BECAUSE IT WAS MEASURED. `::details-content`
   is Chrome 131 and Safari 18.4; an older engine hides the children with `display:none` and
   is opened by the rule below, which is the arm this suite cannot exercise. Written down as
   reasoning rather than as a measurement, so nobody mistakes it for one.

   THE COST OF THIS MECHANISM, STATED: at a desk the summary reports COLLAPSED while its
   content is on screen. A sighted reader sees six open groups; a reader asking the control
   what it is hears a shut disclosure. The alternative is a script that sets `open` from a
   media query, which gets that right and fails with scripting off - and this site's own rule
   is that a page works with scripting off. The headings are real <h2>s inside the summary,
   so heading navigation reaches every group either way.
   ===================================================================================== */
.page-hero + .adm-hub{
  padding-top:2.5rem;
}

.adm-group__head{
  display:flex;
  align-items:baseline;
  gap:16px;
  margin-bottom:22px;
  flex-wrap:wrap;
}

/* THE HEADING IS THE DISCLOSURE CONTROL ON A PHONE, so it owns the marker rather than the
   user agent: a <summary> laid out as flex loses its ::marker in every engine anyway, and a
   triangle that moves with `align-items:baseline` is not a triangle anybody aims at.

   AND IT IS A GRID BELOW 900px RATHER THAN THE FLEX ROW ABOVE, WHICH A SCREENSHOT FORCED.
   Inherited as flex, the first group drew as "WHAT A VISITOR SEES  The shop window" on one
   ragged line with the rule wrapped underneath it and the chevron adrift - six times, on the
   one screen this whole half exists to shorten. Two rows, the chevron spanning both, and the
   rule taken out at this width because a rule that has to share a line with a nine-word
   eyebrow is not a rule, it is a dash. */
summary.adm-group__head{
  cursor:pointer;
  list-style:none;
  padding:10px 0;
  min-height:44px;
  box-sizing:border-box;
  display:grid;
  grid-template-columns:1fr auto;
  grid-template-areas:"eyebrow chev" "title chev";
  align-items:center;
  column-gap:16px;
}

summary.adm-group__head .eyebrow{grid-area:eyebrow}
summary.adm-group__head h2{grid-area:title}
summary.adm-group__head .adm-group__chev{grid-area:chev}
summary.adm-group__head .adm-rule{display:none}

summary.adm-group__head::-webkit-details-marker{display:none}
summary.adm-group__head:focus-visible{outline:2px solid var(--brass);outline-offset:4px}

.adm-group__chev{
  width:10px;
  height:10px;
  flex-shrink:0;
  border-right:2px solid var(--brass-ink);
  border-bottom:2px solid var(--brass-ink);
  transform:rotate(45deg);
  transition:transform .15s;
  margin-inline-start:4px;
}

.adm-group[open] > summary .adm-group__chev{transform:rotate(-135deg)}

/* Closed is the shipped state, and this is the half of it an OLDER engine obeys. */
.adm-group > .adm-cards{display:none}
.adm-group[open] > .adm-cards{display:grid}

@media (min-width:900px){
  /* The one declaration a probe proved opens a closed <details> in Chromium and WebKit today. */
  .adm-group::details-content{content-visibility:visible}

  /* Its counterpart for engines that still hide the children with display:none. Same
     specificity as the closed rule above, so it wins on source order and must stay below it. */
  .adm-group > .adm-cards{display:grid}

  /* A heading that opens nothing should not invite the press. The summary stays operable by
     keyboard, deliberately: blocking the mouse and not the keyboard would be two answers to
     one question, and at this width toggling it changes nothing a reader can see. */
  summary.adm-group__head{
    cursor:default;
    min-height:0;
    /* Back to the row this block was drawn as years ago, and the ORDER is what the original
       rules imply: the name, then the gloss, then the rule running out to the margin. The
       eyebrow is second here and first on a phone, which is why placement is `order` and
       `grid-area` rather than the order of the markup. */
    display:flex;
    align-items:baseline;
    gap:16px;
    flex-wrap:wrap;
  }
  summary.adm-group__head h2{order:1}
  summary.adm-group__head .eyebrow{order:2}
  summary.adm-group__head .adm-rule{order:3;display:block}
  .adm-group__chev{display:none}
}

.adm-group__head h2{
  font-family:var(--serif);
  font-size:1.5rem;
  color:var(--heading);
  margin:0;
}

.adm-group__head .eyebrow{
  margin:0;
}

.adm-rule{
  flex:1;
  height:1px;
  background:var(--line-light);
  min-width:40px;
}

.adm-cards{
  display:grid;
  grid-template-columns:repeat(3,1fr);
  gap:22px;
}

@media(max-width:900px){
  .adm-cards{grid-template-columns:1fr 1fr}
}

@media(max-width:560px){
  .adm-cards{grid-template-columns:1fr}
}

.adm-card{
  display:flex;
  flex-direction:column;
  background:var(--surface);
  border:1px solid var(--line-light);
  border-radius:var(--r);
  padding:26px 26px 22px;
  text-decoration:none;
  color:var(--slate);
  position:relative;
  min-height:200px;
  transition:transform .18s, box-shadow .18s, border-color .18s;
}

.adm-card:hover{
  transform:translateY(-3px);
  box-shadow:0 22px 40px -26px rgba(0,0,0,.45);
  border-color:var(--brass);
  color:var(--slate);
}

.adm-ico{
  width:34px;
  height:34px;
  color:var(--brass-ink);
  margin-bottom:16px;
  flex-shrink:0;
}

.adm-card h3{
  font-family:var(--serif);
  font-size:1.4rem;
  color:var(--heading);
  margin:0 0 0.35rem;
}

.adm-card p{
  color:var(--slate-soft);
  font-size:.95rem;
  margin:10px 0 0;
  flex:1;
}

.adm-action{
  margin-top:auto;
  padding-top:20px;
  font-weight:600;
  font-size:.92rem;
  color:var(--brass-ink);
  display:inline-flex;
  align-items:center;
  gap:8px;
}

.adm-action em{
  font-style:normal;
  transition:transform .18s;
}

.adm-card:hover .adm-action em{
  transform:translateX(4px);
}

/* Dark raffle card */
.adm-card--dark{
  background:var(--ink);
  border-color:color-mix(in srgb, var(--brass) 40%, transparent);
  color:var(--on-dark);
  overflow:hidden;
}

.adm-card--dark::after{
  content:"";
  position:absolute;
  inset:0;
  background:radial-gradient(120% 90% at 90% -20%,color-mix(in srgb, var(--brass) 18%, transparent),transparent 55%);
  pointer-events:none;
}

.adm-card--dark h3{color:var(--on-dark)}
.adm-card--dark p{color:color-mix(in srgb, var(--on-dark) 78%, transparent)}
.adm-card--dark .adm-ico{color:var(--brass-soft)}

.adm-card--dark:hover{
  transform:translateY(-3px);
  color:var(--on-dark);
}

.adm-action--pill{
  color:var(--on-brass);
  background:var(--brass);
  align-self:flex-start;
  padding:10px 18px;
  border-radius:999px;
  margin-top:22px;
}

.adm-action--pill:hover em{
  transform:none;
}

/* Festive Board grid (fb-grid / fb-card) */
.fb-grid{
  display:grid;
  grid-template-columns:repeat(auto-fill,minmax(280px,1fr));
  gap:1.25rem;
}

.fb-card{
  background:var(--surface);
  border:1px solid var(--line-light);
  border-radius:10px;
  padding:1.25rem;
  display:flex;
  flex-direction:column;
  gap:1rem;
}

.fb-card--next{
  border-color:var(--brass);
  background:var(--parchment);
}

.fb-card__head{}

.fb-card__date{
  font-family:var(--serif);
  color:var(--heading);
  font-size:1.1rem;
  margin:.3rem 0 .1rem;
}

.fb-card__sub{
  font-size:.82rem;
  color:var(--slate-soft);
  margin:0 0 .25rem;
}

.fb-card__cuisine{
  font-size:.9rem;
  color:var(--brass-ink);
  font-weight:600;
  margin:0;
}

.fb-card__docs{
  display:grid;
  grid-template-columns:1fr 1fr;
  gap:1rem;
  border-top:1px solid var(--line-light);
  padding-top:1rem;
}

.fb-doc__label{
  font-size:.72rem;
  font-weight:700;
  letter-spacing:.1em;
  text-transform:uppercase;
  color:var(--slate-soft);
  margin:0 0 .5rem;
}

.fb-doc__empty{
  font-size:.8rem;
  color:var(--slate-soft);
  font-style:italic;
  margin-bottom:.5rem;
}

.fb-doc__thumb{
  display:block;
  margin-bottom:.4rem;
  border-radius:4px;
  overflow:hidden;
  border:1px solid #e0e0e0;
  width:80px;
  flex-shrink:0;
}

.fb-doc__thumb img{
  display:block;
  width:80px;
  height:100px;
  max-width:80px;
  max-height:100px;
  object-fit:cover;
  background:#f5f5f5;
}

.fb-doc__upload{
  display:flex;
  flex-direction:column;
  gap:.35rem;
  margin-top:.5rem;
}

.fb-doc__upload input[type=file]{
  font-size:.78rem;
}

/* =====================================================================
   THE IMAGE VIEWER (item 30, Q5 Q6 and Q7 answered).

   This REPLACES the old .menu-dialog block rather than sitting beside
   it. That block worked, and its three callers all opened the festive
   board's flyer or menu - but it was capped at 700px, so a 2560x1440
   monitor showed a 1024px flyer at 68% of its own pixels and gained
   nothing over a 1920x1200 one; and its close button was absolutely
   positioned INSIDE the picture, where on this lodge's own flyer it
   landed on the artwork's roundel. Both were measured, not supposed.

   `[open]` on the display rule is load-bearing. An author `display`
   declaration beats the UA's `dialog:not([open]){display:none}` no
   matter how specific that selector looks, so `.iv{display:grid}`
   would paint a CLOSED dialog over the page.
   ===================================================================== */
.iv{
  border:none;
  padding:0;
  border-radius:12px;
  overflow:hidden;
  background:var(--ink);
  color:var(--on-dark);
  /* Q6 answered (a): fit the viewport, up to the file's own size, with no fixed cap. The picture below
     is never stretched - max-width/max-height only, never width - so a small scan stays small. */
  width:max-content;
  max-width:96vw;
  max-height:94vh;
  box-shadow:0 20px 60px rgba(0,0,0,.45);
}
.iv[open]{display:grid;grid-template-rows:auto minmax(0,1fr) auto}

.iv::backdrop{background:rgba(0,0,0,.75);backdrop-filter:blur(4px)}

.iv__bar{
  display:flex;align-items:center;gap:.75rem;
  padding:.55rem .6rem;border-bottom:1px solid rgba(246,242,233,.14);
}
.iv__name{
  margin:0;font-size:.82rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
  color:var(--on-dark);
}
.iv__of{font-size:.74rem;color:rgba(246,242,233,.65);font-variant-numeric:tabular-nums;margin-left:auto}
.iv__close{
  background:none;border:1px solid rgba(246,242,233,.3);color:var(--on-dark);border-radius:8px;
  min-width:44px;min-height:44px;font-size:1.2rem;line-height:1;cursor:pointer;font-family:inherit;
  margin-left:auto;
}
/* When the counter is present it takes the auto margin, so the close button must not take a second one. */
.iv__of + .iv__close{margin-left:0}
.iv__close:hover,.iv__step:hover{border-color:var(--brass);color:var(--brass-soft)}
.iv__close:focus-visible,.iv__step:focus-visible{outline:2px solid var(--brass);outline-offset:2px}

/* min-height/min-width:0 is what lets the picture shrink inside the 1fr row: a grid track's automatic
   minimum is its content, so without these a tall scan pushes the dialog past max-height. */
.iv__stage{display:grid;place-items:center;padding:.6rem;min-height:0;min-width:0}
.iv__img{display:block;max-width:100%;max-height:100%;width:auto;height:auto;border-radius:4px}

.iv__foot{
  display:flex;align-items:center;justify-content:space-between;gap:.75rem;
  padding:.55rem .6rem;border-top:1px solid rgba(246,242,233,.14);
}
.iv__step{
  background:none;border:1px solid rgba(246,242,233,.3);color:var(--on-dark);border-radius:8px;
  padding:.5rem .9rem;min-height:44px;font-size:.8rem;font-weight:600;font-family:inherit;cursor:pointer;
}
.iv__step[disabled]{opacity:.4;cursor:not-allowed}
.iv__dots{display:flex;gap:.4rem}
.iv__dot{width:8px;height:8px;border-radius:50%;background:rgba(246,242,233,.3)}
.iv__dot--on{background:var(--brass)}

/* Festive Board admin redesign */
.fb-admin{
  padding:2rem 24px 5rem;
}

.fb-admin__back{
  margin-bottom:1.5rem;
}

.fb-admin__title{
  font-family:var(--serif);
  color:var(--heading);
  font-size:clamp(1.8rem,3.5vw,2.8rem);
  margin:0 0 .5rem;
}

.fb-admin__lead{
  color:var(--slate-soft);
  margin:0 0 2rem;
  max-width:64ch;
  line-height:1.65;
}

/* Add panel */
.fb-add-panel{
  display:flex;
  gap:1.25rem;
  align-items:flex-start;
  background:var(--surface);
  border:1px solid var(--line-light);
  border-radius:12px;
  padding:1.5rem 1.75rem;
  margin-bottom:2.5rem;
  max-width:780px;
}

.fb-add-panel__icon{
  width:40px;
  height:40px;
  color:var(--brass-ink);
  flex-shrink:0;
  padding-top:.1rem;
}

.fb-add-panel__icon svg{
  width:40px;
  height:40px;
}

.fb-add-panel__content{
  flex:1;
}

.fb-add-panel__heading{
  font-family:var(--serif);
  font-size:1.3rem;
  color:var(--heading);
  margin:0 0 1rem;
}

.fb-add-form{
  display:flex;
  align-items:center;
  gap:.75rem;
  flex-wrap:wrap;
}

.fb-add-form__label{
  font-size:.85rem;
  font-weight:600;
  color:var(--heading);
  white-space:nowrap;
}

.fb-add-form__select{
  flex:1;
  min-width:220px;
  height:42px;
  border:1px solid var(--line-light);
  border-radius:8px;
  padding:0 .75rem;
  font-size:.92rem;
  color:var(--heading);
  background:var(--surface);
  font-family:var(--sans);
}

/* Section title */
.fb-section-title{
  font-family:var(--serif);
  font-size:1.6rem;
  color:var(--heading);
  margin:0 0 1.1rem;
}

/* Empty state */
.fb-empty{
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:.75rem;
  padding:3rem;
  text-align:center;
  color:var(--slate-soft);
  border:2px dashed var(--line-light);
  border-radius:12px;
  margin-bottom:2rem;
}

/* Board card */
.fb-board-card{
  background:var(--surface);
  border:1px solid var(--line-light);
  border-radius:12px;
  padding:1.5rem 1.75rem;
  margin-bottom:1rem;
}

.fb-board-card--next{
  border-color:var(--brass);
}

.fb-board-card__head{
  display:flex;
  justify-content:space-between;
  align-items:flex-start;
  gap:1rem;
  padding-bottom:1.1rem;
  border-bottom:1px solid var(--line-light);
  margin-bottom:1.25rem;
}

.fb-board-card__left{
  display:flex;
  align-items:flex-start;
  gap:.9rem;
}

.fb-board-card__ico{
  width:32px;
  height:32px;
  color:var(--brass-ink);
  flex-shrink:0;
  margin-top:.15rem;
}

.fb-board-card__title-row{
  display:flex;
  align-items:center;
  gap:.75rem;
  flex-wrap:wrap;
}

.fb-board-card__month{
  font-family:var(--serif);
  font-size:1.4rem;
  color:var(--heading);
  margin:0;
}

.fb-board-card__badge{
  font-size:.72rem;
  font-weight:700;
  letter-spacing:.06em;
  text-transform:uppercase;
  color:var(--brass-ink);
  border:1.5px solid var(--brass);
  border-radius:20px;
  padding:.2rem .65rem;
  white-space:nowrap;
}

.fb-board-card__day{
  font-size:.85rem;
  color:var(--slate-soft);
  margin:.2rem 0 0;
}

/* FB Phase 4: the card head now carries two controls, the way in to the roster and
   Remove. They sit as a pair so Remove does not drift to the far edge of a wide card. */
.fb-board-card__head-acts{
  display:flex;
  align-items:center;
  gap:.35rem;
  flex-shrink:0;
}

.fb-roster-btn{
  display:inline-flex;
  align-items:center;
  gap:.35rem;
  font-size:.8rem;
  font-weight:600;
  color:var(--brass-ink);
  text-decoration:none;
  white-space:nowrap;
  padding:.3rem .6rem;
  border:1px solid var(--line-light);
  border-radius:6px;
  transition:border-color .15s;
}

.fb-roster-btn:hover{border-color:var(--brass)}
.fb-roster-btn:focus-visible{outline:2px solid var(--brass);outline-offset:2px}

.fb-remove-btn{
  display:inline-flex;
  align-items:center;
  gap:.35rem;
  background:none;
  border:none;
  cursor:pointer;
  font-size:.8rem;
  font-weight:600;
  color:var(--claret);
  white-space:nowrap;
  padding:.3rem .5rem;
  border-radius:6px;
  transition:background .15s;
  flex-shrink:0;
}

.fb-remove-btn:hover{
  background:color-mix(in srgb, var(--claret) 8%, transparent);
}

/* Docs two-column grid */
.fb-board-card__docs{
  display:grid;
  grid-template-columns:1fr 1fr;
  gap:1.5rem;
}

/* =====================================================================================
   ITEM 41 — THE FESTIVE BOARD PAGES ON A PHONE (docs/festive-board-mobile-plan.md,
   gate Q1(a) cards, Q2 700px, Q3(a) catering too, Q6(a) 44px, Q7 one slice).

   Measured in Chromium at 320, 390 and 430 before a line of this was written, and the
   numbers were IDENTICAL at all three — which was the diagnosis rather than a curiosity.
   None of these pages had a phone breakpoint at all.

   THE BREAKPOINT IS 700 AND THE PLAN SAID 760. The plan's reason for 760 was "the width
   the roster's own toolbar already breaks at"; the toolbar breaks at 700, twice, and so
   does .fb-details__*. 760 appears five times in this stylesheet and not once in the
   festive board family. The gate took 700 on the corrected premise.

   THE DOCS GRID USED TO BREAK AT 640 AND NOW BREAKS HERE. That left the board page with
   two phone numbers — 640 for the documents, 700 for the meal details editor — which is
   precisely the "one number governs the page" the gate asked for and did not have.
   ===================================================================================== */
@media screen and (max-width:700px){

  /* ---- the overflow, and it was ONE DECLARATION ----
     .fb-board-card__head is space-between and would not wrap; .fb-board-card__head-acts was
     flex-shrink:0 around two white-space:nowrap buttons. 250px of controls that could neither
     wrap nor shrink, beside a date heading that also would not shrink, made the DOCUMENT
     519px wide on a 320px phone — so the whole page slid under the thumb, band and heading
     included.

     THE PLAN PROPOSED TWO RULES AND THE MUTATION RUN PROVED ONE. §3.1 also asked for
     `.fb-board-card__head-acts{flex-shrink:1;flex-basis:100%;justify-content:flex-start}`.
     Deleted whole, the sweep stays green at 320, 390 and 430: once the head can WRAP, the
     acts row drops to its own line at its natural 250px, which fits inside a 272px card, and
     a single flex item on a wrapped line sits at flex-start whatever justify-content says.
     Every declaration in it was either inert or unmeasurable, so none of it is here. */
  .fb-board-card__head{ flex-wrap:wrap; }

  .fb-board-card__docs{ grid-template-columns:1fr; }

  /* ---- 44px, gate Q6(a) ----
     Not 48: /Admin/FestiveBoard/Tonight already says 44 in this stylesheet and states its
     reason, and a second number on a sibling page is two standards.

     .fb-doc2__action measured 20px, the smallest control on any of the four pages. The
     min-height is the fix and the display change is NOT: mutating inline-flex away leaves the
     sweep green, because .fb-doc2__actions is already display:flex and blockifies its
     children, so the height applies with or without it. inline-flex + align-items stays for
     one reason, which is that it CENTRES the label in the 44px box instead of leaving "View"
     sitting against the top of it. That is presentation and no claim here can see it.

     .fb-doc2__upload-label is a <label> and is here on purpose: the real submit beside it
     is display:none because the form auto-submits on file change, so the control a Steward
     actually presses to upload a flyer is the label. */
  .fb-roster-btn,
  .fb-remove-btn,
  .fb-doc2__upload-label{ min-height:44px; }

  .fb-doc2__action{ display:inline-flex; align-items:center; min-height:44px; }

  /* The meal offer editor's four sub-floor controls are NOT here. They are .att-* and the
     .att-* family is declared ~4,000 lines further down this file, so a rule written here at
     the same specificity LOSES ON SOURCE ORDER. It was written here first and measured
     unchanged at 38 and 40px. The block that fixes them sits beside their definitions. */

  /* The whole details form, not only the two <select>s the sweep names. Raising the number
     fields alone would leave 44px price and capacity boxes sitting in the same grid as
     35px text and date boxes, which is a worse page than the one this item started with. */
  .fb-details__field input,
  .fb-details__field select,
  .fb-details__num,
  .fb-add-form__select{ min-height:44px; }

  /* ---- "Add a festive board", REPORTED BY THE USER AFTER THIS ITEM SHIPPED ----
     This panel only exists inside `@if (Model.AvailableMeetings.Any())`, and item 41's sweep
     seeded no addable meeting — so the panel was never on the page the sweep measured and the
     whole of it passed VACUOUSLY. With one meeting to add, /Admin/FestiveBoard slid 78px at
     320 and 8px at 390.

     The cause is `.fb-add-form__select{min-width:220px}` — an AUTHORED floor, not the
     `min-width:auto` the picker next door had, so nothing about shrinking a flex item touches
     it. 220px of select plus a nowrap "Meeting" label plus 60px of decorative icon plus 56px
     of panel padding does not fit 320.

     The icon goes on a phone. It is aria-hidden and decorative, and it was spending 60px of a
     320px screen on a calendar glyph sitting beside the words "Add a festive board". */
  /* THE SELECT TAKES THE LINE, AND THAT ONE DECLARATION IS THE FIX. The first version of this
     block put flex-basis:100% on the label, the select AND the button; mutation showed the label's
     and the select's are individually redundant and jointly necessary — EITHER one alone pushes
     the select onto a full-width line of its own, and removing only one leaves the sweep green.
     So one is kept, on the control the rule is actually about, and a mutant can now kill it.

     min-width:0 clears .fb-add-form__select's AUTHORED min-width:220px, which is what made this
     panel unable to shrink at all. It survives mutation, and that is stated rather than hidden:
     with the icon gone and the padding down, the line is already 264px on a 320px phone, so 220
     clears it with room. It is here because a 220px floor on a control in a 264px line is a fit
     by arithmetic rather than by design, and claim 7 is what would catch it going wrong.

     The padding and the icon are PRESENTATION and no claim here can see either. A 40px decorative
     calendar glyph and 28px of padding either side spend 96px of a 320px screen sitting beside
     the words "Add a festive board". The icon is already aria-hidden; on a phone it is gone. */
  .fb-add-panel{ padding:1.1rem 1rem; gap:.9rem; }
  .fb-add-panel__icon{ display:none; }
  .fb-add-panel__content{ min-width:0; }
  .fb-add-form__select{ flex-basis:100%; min-width:0; }
  .fb-add-form .btn{ min-height:44px; }

  /* Scoped to this page deliberately. .history-size-form is shared by EIGHT pages — Charity,
     Events, RaffleDraw, Requests, Links, Enquiries and Members/Raffles as well as this one —
     and none of the other seven was measured or is covered by item 41's sweep. They have the
     same 30px select and they are booked, not quietly restyled. */
  .fb-boards-page .history-size-form select{ min-height:44px; }

  /* ---- WHAT SAFARI DOES TO A <select>, AND NEITHER HALF WAS VISIBLE IN CHROMIUM ----
     Reported from an iPhone 17 Pro Max after this item shipped twice. The whole sweep was
     Chromium and the whole suite still is: WebKit was not driven anywhere in this project.

     HEIGHT. WebKit lays out a menulist <select> from its own metrics and IGNORES min-height,
     so every select these pages were "fixed" to 44px rendered at 21px on the phone the
     complaint came from — and .fb-add-form__select, which carries an authored height:42px,
     stayed 42. An explicit `height` is what WebKit honours. Measured: min-height alone 21px,
     height:44px 44px.

     OVERFLOW, AND THIS IS THE ONE NOTHING COULD HAVE CAUGHT. A <select> in WebKit keeps a
     scrollable overflow the width of its longest OPTION — the board picker measured
     `box=390, scrollWidth=507` — and that overflow propagates to the document. So the page
     scrolled sideways by 118px at 390 while EVERY element's box was correctly inside the
     viewport: nothing was drawn past the edge, nothing had a right edge to report, and the
     document width was a constant 508 at every viewport because it was the option text and not
     the layout. overflow:hidden on the control stops it propagating. Measured: 508 -> 440.

     Both are needed and neither is sufficient. height:44px alone leaves the page at 508;
     overflow:hidden alone leaves the control at 21px.

     .fbt-section IS TONIGHT, AND ITS PRESENCE HERE IS THE POINT. That page is the one this
     item held up as already correct — written phone-first on purpose, saying so in its own
     comment, and green on every Chromium claim from the first measurement onwards. Its walk-in
     member picker renders at 24px in WebKit. Being mobile-first is not the same as having been
     looked at in the engine the phone actually runs. */
  .fb-boards-page select,
  .fbr-section--phone select,
  .fbt-section select{ height:44px; overflow:hidden; }

  /* ---- THE GUTTER THESE PAGES NEVER HAD, REPORTED FROM THE PHONE ----
     `.wrap` gives every page 16-24px of side padding. `.fbr-section{padding:0 0 4rem}` and
     `.fbt-section{padding:1.25rem 0 3rem}` are SHORTHANDS, they sit later in this file than
     `.wrap`, and they carry the same specificity — so on three of these four pages the wrap's
     horizontal gutter was being set and then silently overwritten, and every card, label and
     control sat flush against the glass. The board page was never affected: `.admin-sub` spells
     its own `padding:2rem 24px 4rem` and keeps the 24.

     Nothing in item 41 caused this and nothing in its first two rounds could see it — a page
     with no gutter overflows nothing, clips nothing and has no control under 44px. It took
     somebody looking at it.

     WRITTEN AS `section.` DELIBERATELY. Both families are declared THOUSANDS of lines below this
     block, so at equal specificity they win on source order and a plain `.fbr-section--phone`
     here loses — which is exactly what it did, silently, on the first attempt. The type selector
     buys the one point that settles it wherever this block sits. Longhand, so the vertical
     padding each family sets is left alone. */
  section.fbr-section--phone,
  section.fbt-section{ padding-left:16px; padding-right:16px; }
}

/* Individual doc slot */
.fb-doc2__label{
  font-size:.78rem;
  font-weight:700;
  letter-spacing:.08em;
  text-transform:uppercase;
  color:var(--slate-soft);
  margin:0 0 .75rem;
}

.fb-doc2__row{
  display:flex;
  gap:.9rem;
  align-items:flex-start;
  margin-bottom:.75rem;
}

.fb-doc2__thumb{
  display:block;
  flex-shrink:0;
  width:80px;
  height:100px;
  border-radius:5px;
  overflow:hidden;
  border:1px solid var(--line-light);
}

.fb-doc2__thumb img{
  display:block;
  width:80px;
  height:100px;
  object-fit:cover;
}

.fb-doc2__info{
  flex:1;
  min-width:0;
}

.fb-doc2__status{
  display:inline-flex;
  width:fit-content;
  align-items:center;
  gap:.3rem;
  font-size:.82rem;
  font-weight:600;
  color:var(--ok-ink);
  background:var(--ok-fill);
  border-radius:5px;
  padding:1px 7px;
  align-self:flex-start;
  margin:0 0 .25rem;
}

.fb-doc2__meta{
  font-size:.78rem;
  color:var(--slate-soft);
  margin:0 0 .5rem;
  word-break:break-all;
}

.fb-doc2__actions{
  display:flex;
  align-items:center;
  gap:.4rem;
  flex-wrap:wrap;
}

.fb-doc2__action{
  background:none;
  border:none;
  cursor:pointer;
  font-size:.8rem;
  font-weight:600;
  color:var(--brass-ink);
  text-decoration:none;
  padding:0;
  font-family:var(--sans);
}

.fb-doc2__action:hover{ text-decoration:underline; }

.fb-doc2__action--danger{ color:var(--claret); }

.fb-doc2__sep{
  color:var(--slate-soft);
  font-size:.8rem;
}

/* Upload label (dashed button) */
.fb-doc2__upload-form{
  display:flex;
  align-items:center;
  gap:.5rem;
  margin-top:.25rem;
}

.fb-doc2__upload-label{
  display:inline-flex;
  align-items:center;
  gap:.4rem;
  border:1.5px dashed color-mix(in srgb, var(--brass) 50%, transparent);
  border-radius:7px;
  padding:.45rem .85rem;
  font-size:.8rem;
  color:var(--slate-soft);
  cursor:pointer;
  transition:border-color .15s, color .15s;
  flex:1;
}

.fb-doc2__upload-label:hover{
  border-color:var(--brass);
  color:var(--brass-ink);
}

.fb-doc2__file-input{
  position:absolute;
  width:1px;
  height:1px;
  opacity:0;
  overflow:hidden;
  clip:rect(0,0,0,0);
}

/* Hidden because the form auto-submits on file change (site.js). Anything driving this page from a
   browser must SET THE FILE and wait for the navigation — clicking Upload waits forever. */
.fb-doc2__upload-submit{
  display:none;
}

/* Info bar.

   The comment that used to sit here had a second comment opener inside it. CSS comments do not nest,
   so it terminated at the first closing delimiter and left the words "Info bar" plus a stray closing
   delimiter loose in the stylesheet. The parser swallowed that together with the selector that
   followed, and the .fb-info-bar rule below was silently dropped. Keep this one flat. */
.fb-info-bar{
  display:flex;
  align-items:flex-start;
  gap:.85rem;
  background:var(--parchment-2);
  border:1px solid color-mix(in srgb, var(--brass) 30%, transparent);
  border-radius:10px;
  padding:1rem 1.25rem;
  margin-top:2rem;
  max-width:780px;
}

.fb-info-bar__icon{
  width:32px;
  height:32px;
  flex-shrink:0;
}

.fb-info-bar p{
  margin:0;
  font-size:.88rem;
  color:var(--slate);
  line-height:1.6;
}

/* =====================================================================
   B3 / T4 — Admin › Site theme page (docs/theme-system-plan.md §8.1)
   Admin chrome deliberately uses fixed Heritage-idiom literals (decision
   D3: the admin stays readable whichever theme is active); only the
   .pv-* live-preview pane reads the token custom properties, which the
   page sets INLINE on .pv-site from the edited theme's values.
   ===================================================================== */

.tm-section{padding:6px 28px 60px}

.tm-grid{
  display:grid;
  grid-template-columns:225px minmax(330px,1fr) minmax(300px,.95fr);
  gap:16px;
  align-items:start;
}

.tm-card{background:var(--surface);border:1px solid rgba(148,163,184,.25);border-radius:10px;padding:14px}
.tm-label{font-size:.72rem;font-weight:600;letter-spacing:.05em;text-transform:uppercase;color:var(--slate-soft);margin:0 0 10px}

.tm-theme-item{
  display:flex;align-items:center;justify-content:space-between;gap:8px;
  width:100%;box-sizing:border-box;text-align:left;text-decoration:none;
  background:var(--surface);border:1px solid rgba(148,163,184,.3);border-radius:8px;
  padding:9px 11px;margin:0 0 8px;font-size:.95rem;color:var(--heading);cursor:pointer;
  transition:border-color .12s;
}
/* TD Phase 5: --brass, not #C7A24A. The hover and selection accents on this page were Heritage's gold
   spelled as a literal, which is the one palette where a fixed accent is invisible as a defect — on
   Grammarians the rail highlighted the SELECTED theme in a gold that palette does not contain. Nothing
   measured it: a border carries no text, so the contrast lock cannot see it, and only the screenshot did. */
.tm-theme-item:hover{border-color:var(--brass)}
.tm-theme-item.is-selected{border-color:var(--brass);box-shadow:0 0 0 1px var(--brass)}
.tm-pill{font-size:.6rem;font-weight:700;letter-spacing:.09em;text-transform:uppercase;border-radius:999px;padding:2px 8px;background:var(--parchment-2);color:var(--slate-soft);white-space:nowrap}
.tm-pill--active{background:var(--brass);color:var(--on-brass)}
.tm-rail-actions{display:flex;gap:8px;margin-top:4px}
.tm-btn-ghost{background:var(--surface);border:1px solid rgba(148,163,184,.4);border-radius:8px;padding:6px 10px;font-size:.8rem;color:var(--slate);cursor:pointer}
.tm-btn-ghost:hover{border-color:var(--brass)}
.tm-rail-note{font-size:.76rem;color:var(--slate-soft);margin:12px 2px 0;line-height:1.5}

/* TD Phase 4 / F6 + F7: rename and delete, each its own form in the rail (see the Razor for why they are not
   one). Every colour here is a THEME token — this page renders in the theme being edited, so a fixed grey or a
   fixed red would vanish or clash on the six dark palettes (the lesson Phase 2's swatch ring taught).
   --claret is the danger colour rather than an invented red because it is DERIVED to clear 4.5:1 against this
   card's surface on every palette, which no hardcoded red can promise. */
.tm-rail-rename{display:flex;flex-direction:column;gap:5px;margin-top:12px}
.tm-rail-rename__label{font-size:.72rem;color:var(--slate-soft)}
.tm-rail-rename__row{display:flex;gap:6px;align-items:center}
.tm-rail-rename__row input{
  flex:1 1 auto;min-width:0;box-sizing:border-box;
  border:1px solid rgba(148,163,184,.4);border-radius:6px;padding:6px 8px;
  font-size:.88rem;color:var(--heading);background:var(--surface);
}
.tm-rail-rename__row input:focus{outline:none;border-color:var(--brass)}
.tm-rail-delete{display:flex;flex-direction:column;gap:5px;margin-top:10px}
.tm-btn-danger{
  align-self:flex-start;background:var(--surface);border:1px solid var(--claret);border-radius:8px;
  padding:6px 10px;font-size:.8rem;color:var(--claret);cursor:pointer;transition:border-color .12s,opacity .12s;
}
.tm-btn-danger:hover:not(:disabled){border-color:var(--brass)}
/* A refused delete says WHY in the note below it, so the disabled button does not have to be a dead end. */
.tm-btn-danger:disabled{opacity:.45;cursor:default}
.tm-btn-danger--quiet{padding:5px 9px;font-size:.76rem}
.tm-rail-delete__why{font-size:.72rem;color:var(--slate-soft);line-height:1.45}

/* TD Phase 4 / T7: the lodge's own colours. One row per swatch — chip, hex, an optional name and its two
   buttons — and the add row underneath. */
.tm-swatches__empty{font-size:.78rem;color:var(--slate-soft);margin:0 2px;line-height:1.5}
.tm-swatches__list{display:flex;flex-direction:column;gap:6px;margin:10px 0 0}
.tm-swatch-row{display:grid;grid-template-columns:26px 72px 1fr auto auto;gap:8px;align-items:center}
.tm-swatch-row__chip{width:26px;height:26px;border:1px solid var(--slate-soft);border-radius:6px;display:block}
.tm-swatch-row__hex{font-size:.74rem;font-family:ui-monospace,Menlo,Consolas,monospace;color:var(--slate-soft)}
.tm-swatch-row__label{
  min-width:0;box-sizing:border-box;width:100%;
  border:1px solid rgba(148,163,184,.4);border-radius:6px;padding:5px 8px;
  font-size:.84rem;color:var(--heading);background:var(--surface);
}
.tm-swatch-row__label:focus{outline:none;border-color:var(--brass)}
.tm-swatch-add{display:flex;gap:8px;align-items:center;margin-top:12px;padding-top:12px;border-top:1px solid rgba(148,163,184,.16)}
.tm-swatch-add input[type=text]{
  flex:1 1 auto;min-width:0;box-sizing:border-box;
  border:1px solid rgba(148,163,184,.4);border-radius:6px;padding:6px 8px;
  font-size:.84rem;color:var(--heading);background:var(--surface);
}
.tm-swatch-add input[type=text]:focus{outline:none;border-color:var(--brass)}
/* A kept colour is offered AFTER the theme's roles in every toolbar, and reads as a different kind of thing.
   The GAP is what carries that at 18px — a screenshot showed the dotted ring is invisible at chip size, so the
   ring is the close-up detail and the space before the first kept colour is the one that reads. */
.tm-rt-toolbar .tm-rt-swatch--custom{border-style:dotted;border-color:var(--slate-soft)}
.tm-rt-toolbar .tm-rt-swatch:not(.tm-rt-swatch--custom) + .tm-rt-swatch--custom{margin-left:7px}

.tm-group{margin:0 0 12px}
.tm-token{display:grid;grid-template-columns:34px 1fr 96px;gap:10px;align-items:center;padding:7px 4px;border-top:1px solid rgba(148,163,184,.16)}
.tm-token:first-of-type{border-top:none}
/* TD Phase 2 / T3: the picker was always here, one thin hairline away from reading as a preview swatch
   rather than a control — so the reported "I should have a picker" was a discoverability failure. It now
   borrows the hover language every other control on this page uses (border -> brass) and shows a focus
   ring, which is what tells a keyboard user it is reachable at all. */
/* --slate-soft, not a fixed rgba(0,0,0,…): the border IS the affordance, and a black hairline is
   invisible on the six dark palettes — a screenshot on Onyx is what caught it. --slate-soft is the muted
   TEXT token, so TC's floor already guarantees it is visible against this card's surface on every theme. */
.tm-token-swatch{width:32px;height:32px;border:2px solid var(--slate-soft);border-radius:6px;padding:0;background:none;cursor:pointer;transition:border-color .12s,transform .1s}
/* Hover takes --brass (the theme's accent); the focus RING takes --brass-ink, the floored one. A ring is
   what tells a keyboard user where they are, so it has to clear 4.5:1 against this card on every palette,
   and --brass-ink is the token that is computed to do exactly that (ThemeStyleComposer). */
.tm-token-swatch:hover{border-color:var(--brass);transform:scale(1.06)}
.tm-token-swatch:focus-visible{outline:2px solid var(--brass-ink);outline-offset:2px}
.tm-token-swatch::-webkit-color-swatch-wrapper{padding:2px}
.tm-token-swatch::-webkit-color-swatch{border:none;border-radius:4px}
.tm-token-swatch::-moz-color-swatch{border:none;border-radius:4px}
.tm-token-name{font-size:.88rem;color:var(--heading);line-height:1.25}
.tm-token-name small{display:block;color:var(--slate-soft);font-size:.72rem;margin-top:2px}
/* TD Phase 3 / T5: the swatch's NAME is this theme's own and is editable in place. It has to read as the
   row's title first and as a text box second, or twelve input frames turn the editor into a form — so it
   carries no border until it is hovered or focused, and inherits the heading colour and weight the plain
   label had. Every colour here is a THEME token (this page is rendered in the theme being edited, so a
   fixed grey would vanish on the six dark palettes — the lesson the swatch ring taught in Phase 2). */
.tm-token-label{
  display:block;width:100%;min-width:0;box-sizing:border-box;
  font:inherit;color:var(--heading);
  background:none;border:1px solid transparent;border-radius:6px;
  padding:2px 5px;margin:0 0 0 -5px;
  transition:border-color .12s,background-color .12s;
}
.tm-token-label:hover{border-color:var(--slate-soft);background:var(--surface)}
.tm-token-label:focus{outline:none;border-color:var(--brass);background:var(--surface)}
.tm-token-label:focus-visible{outline:2px solid var(--brass-ink);outline-offset:1px}
.tm-token-label::placeholder{color:var(--slate-soft);opacity:1}
/* Q5's retired palette word, on the shipped themes only. Its own line, because the note above it is the
   role's detail and these are two different kinds of fact. */
.tm-token-legacy{display:block;font-style:italic}
.tm-token-hex{font-size:.8rem;font-family:ui-monospace,Menlo,Consolas,monospace;border:1px solid rgba(148,163,184,.4);border-radius:6px;padding:5px 7px;width:100%;min-width:0;box-sizing:border-box;color:var(--slate);background:var(--surface)}
.tm-token-hex--wide{grid-column:3;width:100%}
.tm-token-chip{
  width:30px;height:30px;border:1px solid rgba(0,0,0,.12);border-radius:6px;
  background:
    linear-gradient(45deg,#e8e4d8 25%,transparent 25%,transparent 75%,#e8e4d8 75%),
    linear-gradient(45deg,#e8e4d8 25%,#fff 25%,#fff 75%,#e8e4d8 75%);
  background-size:12px 12px;background-position:0 0,6px 6px;
  position:relative;overflow:hidden;
}
.tm-token-chip i{position:absolute;inset:0;display:block}

.tm-font-row{display:grid;grid-template-columns:1fr;gap:4px;padding:7px 4px;border-top:1px solid rgba(148,163,184,.16)}
.tm-font-row:first-of-type{border-top:none}
.tm-font-row label{font-size:.8rem;color:var(--slate-soft)}
.tm-font-row select{border:1px solid rgba(148,163,184,.4);border-radius:6px;padding:7px 8px;font-size:.88rem;color:var(--heading);background:var(--surface)}
.tm-font-row select:disabled{color:var(--slate-soft);background:var(--surface)}
.tm-font-note{font-size:.74rem;color:var(--slate-soft);margin:10px 2px 0;line-height:1.5}

.tm-brand-row{display:flex;gap:12px;align-items:center;padding:2px 2px 12px;border-bottom:1px solid rgba(148,163,184,.16);margin-bottom:10px}
.tm-brand-logo{width:54px;height:54px;object-fit:contain;border:1px solid rgba(0,0,0,.12);border-radius:8px;background:var(--surface);padding:4px;flex:0 0 auto}
.tm-brand-meta{display:flex;flex-direction:column;gap:5px;font-size:.88rem;color:var(--heading)}
.tm-brand-meta small{color:var(--slate-soft);font-size:.74rem;line-height:1.4}
.tm-upload{align-self:flex-start;cursor:pointer}
.tm-brand-texthead{font-size:.8rem;color:var(--slate-soft);display:block;margin:0 0 5px}
.tm-brand-motto{margin-top:10px}
.tm-rt-toolbar{display:flex;gap:4px;align-items:center;flex-wrap:wrap;margin-bottom:5px}
.tm-rt-toolbar button{width:26px;height:26px;border:1px solid rgba(148,163,184,.4);border-radius:6px;background:var(--surface);cursor:pointer;font-size:.8rem;color:var(--slate)}
.tm-rt-toolbar button:hover{border-color:var(--brass)}
.tm-rt-swatches{display:flex;gap:4px;align-items:center;margin-left:6px}
.tm-rt-toolbar .tm-rt-swatch{width:18px;height:18px;border-radius:4px;border:1px solid rgba(0,0,0,.15);padding:0;transition:transform .1s,border-color .1s}
.tm-rt-toolbar .tm-rt-swatch:hover{border-color:var(--brass);transform:scale(1.12)}
.tm-rt-custom{position:relative;display:inline-flex;align-items:center;justify-content:center;width:18px;height:18px;border:1px solid rgba(0,0,0,.15);border-radius:4px;background:var(--surface);color:var(--slate-soft);font-weight:700;font-size:.72rem;line-height:1;cursor:pointer}
.tm-rt-custom input{position:absolute;width:1px;height:1px;opacity:0}
.tm-rt-editor{border:1px solid rgba(148,163,184,.4);border-radius:6px;padding:8px 10px;font-size:.95rem;line-height:1.35;min-height:2.6em;background:var(--surface);color:var(--heading)}
.tm-motto-input{width:100%;min-width:0;box-sizing:border-box;border:1px solid rgba(148,163,184,.4);border-radius:6px;padding:7px 9px;font-size:.9rem;color:var(--heading);background:var(--surface)}

.tm-actions{display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin-top:14px}
.tm-btn-primary{background:var(--ink);color:var(--on-dark);border:none;border-radius:8px;padding:9px 16px;font-size:.86rem;font-weight:600;cursor:pointer}
.tm-btn-activate{background:var(--brass);color:var(--on-brass);border:none;border-radius:8px;padding:9px 16px;font-size:.86rem;font-weight:600;cursor:pointer}
.tm-btn-activate:disabled{opacity:.45;cursor:default}
.tm-status{font-size:.78rem;color:var(--slate-soft);flex-basis:100%;min-height:1.2em}

/* ---------- live preview minisite (reads the inline token vars on .pv-site) ---------- */
.pv-wrap{position:sticky;top:12px}
.pv-tag{display:flex;justify-content:space-between;align-items:baseline;font-size:.66rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--slate-soft);margin:0 2px 8px}
.pv-site{border:1px solid rgba(0,0,0,.18);border-radius:10px;overflow:hidden;background:var(--parchment,#F6F2E9);color:var(--slate,#1f1f1f);font-family:var(--pv-sans,var(--sans));transition:background .2s}
.pv-head{display:flex;align-items:center;gap:10px;background:var(--ink,#111);color:var(--on-dark,#F6F2E9);padding:12px 16px;font-size:.78rem;letter-spacing:.14em;text-transform:uppercase}
.pv-seal{width:30px;height:30px;object-fit:contain;background:#F6F2E9;border-radius:50%;padding:2px;flex:0 0 auto}
.pv-brandname{flex:1 1 auto;min-width:0;overflow:hidden;white-space:nowrap;line-height:1.35;letter-spacing:.1em;font-size:.72rem;font-family:var(--pv-serif,var(--serif))}
.pv-nav{margin-left:auto;display:flex;gap:12px;font-size:.62rem;letter-spacing:.12em;color:var(--on-dark,#F6F2E9)}
.pv-hero{padding:26px 22px 24px}
.pv-eyebrow{font-size:.66rem;font-weight:600;letter-spacing:.2em;text-transform:uppercase;color:var(--brass-ink,#7D6525);margin:0 0 12px}
/* .pv-title, not h1: this block is a PICTURE of the public home page inside the theme editor, and a
   real h1 here gave /Admin/Theme two top-level headings — the page's own and the mock's. */
.pv-title{font-family:var(--pv-serif,var(--serif));font-weight:500;font-size:1.95rem;line-height:1.1;color:var(--heading,#111111);margin:0 0 12px}
.pv-title em{color:var(--claret,#6F2435)}
.pv-lead{font-size:.86rem;color:var(--slate-soft,#4f4f4f);margin:0 0 18px;max-width:44ch}
.pv-ctas{display:flex;flex-wrap:wrap;gap:10px;margin:0 0 20px}
.pv-btn{border-radius:999px;padding:8px 18px;font-size:.8rem;font-weight:600;cursor:default}
.pv-btn--primary{background:var(--brass,#C7A24A);color:var(--on-brass,#000);border:1px solid var(--brass,#C7A24A)}
.pv-btn--ghost{background:transparent;color:var(--slate,#1f1f1f);border:1px solid var(--line-light,rgba(0,0,0,.12))}
.pv-stats{display:flex;flex-wrap:wrap;gap:8px}
.pv-stat{background:var(--parchment-2,#EFE9DA);border:1px solid var(--line-light,rgba(0,0,0,.12));border-radius:8px;padding:8px 12px;font-size:.72rem;color:var(--slate,#1f1f1f)}
.pv-stat b{display:block;font-family:var(--pv-serif,var(--serif));font-size:1.05rem;font-weight:600;color:var(--claret,#6F2435)}
/* Card surfaces: read var(--surface) so the preview shows the raised-card token per theme (B3 T7). */
.pv-cards{display:grid;grid-template-columns:1.5fr 1fr;gap:14px;padding:0 22px 24px}
.pv-card{background:var(--surface,#fff);border:1px solid var(--line-light,rgba(0,0,0,.12));border-radius:10px;padding:16px 18px}
.pv-card-eyebrow{font-size:.6rem;font-weight:600;letter-spacing:.2em;text-transform:uppercase;color:var(--brass-ink,#7D6525);margin:0 0 7px}
.pv-card-title{font-family:var(--pv-serif,var(--serif));font-weight:600;font-size:1.4rem;line-height:1;color:var(--heading,#111111);margin:0 0 6px}
.pv-card-sub{font-size:.72rem;color:var(--slate-soft,#4f4f4f);margin:0}
.pv-card-row{display:flex;gap:12px;align-items:center;margin-top:12px;padding-top:10px;border-top:1px solid var(--line-light,rgba(0,0,0,.12))}
.pv-card-date{font-family:var(--pv-serif,var(--serif));text-align:center;line-height:1;color:var(--heading,#111111)}
.pv-card-date small{display:block;font-size:.55rem;text-transform:uppercase;letter-spacing:.06em;color:var(--brass-ink,#7D6525)}
.pv-card-date b{display:block;font-size:1.2rem;font-weight:600}
.pv-card-what{display:flex;flex-direction:column;gap:2px}
.pv-card-what b{font-size:.82rem;color:var(--slate,#1f1f1f)}
.pv-card-what span{font-size:.72rem;color:var(--slate-soft,#4f4f4f)}
@media (max-width:520px){.pv-cards{grid-template-columns:1fr}}
.pv-foot{background:var(--ink-deep,#000);border-top:1px solid var(--line-dark,rgba(231,217,178,.22));padding:16px 16px 12px;color:color-mix(in srgb, var(--on-dark,#F6F2E9) 72%, transparent)}
.pv-foot-grid{display:flex;justify-content:space-between;gap:18px;flex-wrap:wrap;margin-bottom:12px}
.pv-foot-brand{display:flex;flex-direction:column;gap:7px;min-width:0}
.pv-foot-brandline{display:flex;align-items:center;gap:8px}
.pv-foot-brandline img{width:26px;height:26px;object-fit:contain;background:#F6F2E9;border-radius:50%;padding:2px;flex:0 0 auto}
.pv-foot-name{font-size:.6rem;letter-spacing:.1em;text-transform:uppercase;color:var(--on-dark,#F6F2E9);line-height:1.35;font-family:var(--pv-serif,var(--serif))}
.pv-foot-motto{margin:0;font-family:var(--pv-serif,var(--serif));font-style:italic;color:var(--brass-soft,#E4CF94);font-size:.88rem}
.pv-foot-col{display:flex;flex-direction:column;gap:2px}
.pv-foot-col h5{margin:0 0 4px;font-size:.56rem;font-weight:600;letter-spacing:.16em;text-transform:uppercase;color:var(--brass-soft,#E4CF94)}
.pv-foot-col span{font-size:.66rem}
.pv-foot-bottom{border-top:1px solid var(--line-dark,rgba(231,217,178,.22));padding-top:8px;display:flex;justify-content:space-between;gap:10px;flex-wrap:wrap;font-size:.56rem;color:color-mix(in srgb, var(--on-dark,#F6F2E9) 60%, transparent)}

/* The brand HTML's structural <span> is the second line (like the real masthead's .brand-name span);
   editor-authored colour spans carry inline style and must stay inline. */
.pv-brandname span:not([style]),
.pv-foot-name span:not([style]){display:block;color:var(--brass-soft,#E4CF94);font-size:.85em;letter-spacing:.14em}

@media (max-width:980px){
  .tm-grid{grid-template-columns:1fr 1fr}
  .tm-rail{grid-column:1 / -1}
}
@media (max-width:700px){
  .tm-grid{grid-template-columns:1fr}
  .pv-wrap{position:static}
}

/* =====================================================================
   B3 / T4 — masthead/footer brand auto-fit support (_Layout.cshtml).
   The JS shrinks the font until the text fits; these rules make the
   overflow measurable (min-width:0 lets flex shrink the brand, nowrap +
   hidden overflow stops wrapping) and keep editor colour spans inline
   while the structural <span> stays the styled second line.
   ===================================================================== */
.brand{min-width:0}
.brand-name{min-width:0;overflow:hidden;white-space:nowrap}
.foot-brand{min-width:0}
.foot-brand .brand-name{overflow:hidden;white-space:nowrap}
.brand-name span[style]{
  display:inline;
  font-family:inherit;
  font-size:inherit;
  font-weight:inherit;
  letter-spacing:inherit;
  text-transform:inherit;
  margin:0;
}

/* B3/T4 follow-up (user ask): the Brand card's two boxes are WYSIWYG - they render on the EDITED
   theme's real surfaces in the same colours as the homepage (brand name on the masthead's --ink bar,
   motto on the footer's --ink-deep), reading the token custom properties the page sets inline on the
   section (kept live by admin-theme.js). The structural (style-less) <span> is the brand's second line,
   exactly as .brand-name renders it; editor-coloured spans (inline style) stay inline. Enter = <br>. */
.tm-rt-editor--brand{
  background:var(--ink,#111);
  color:var(--on-dark,#F6F2E9);
  font-family:var(--pv-serif,var(--serif));
  font-weight:600;
  line-height:1.15;
}
.tm-rt-editor--brand span:not([style]){
  display:block;
  font-family:var(--sans);
  font-size:.62em;
  font-weight:600;
  letter-spacing:.2em;
  text-transform:uppercase;
  color:var(--brass-soft,#E4CF94);
  margin-top:3px;
}
.tm-rt-editor--motto{
  background:var(--ink-deep,#000);
  color:var(--brass-soft,#E4CF94);
  font-family:var(--pv-serif,var(--serif));
  font-style:italic;
}
.tm-rt-font{
  height:26px;
  max-width:150px;
  border:1px solid rgba(148,163,184,.4);
  border-radius:6px;
  padding:2px 4px;
  font-size:.76rem;
  color:var(--slate);
  background:var(--surface);
}

/* ============================================================
   ML - Admin media library (/Admin/Media, docs/media-library-plan.md Phase 3).
   Mockup: docs/mockups/media-library.html, restyled onto the theme tokens
   (--surface / --heading / --slate per the render-mapping rules) so every
   theme renders it correctly. Prefix: ml-.
   ============================================================ */
.ml-page{padding-bottom:60px}

.ml-toolbar{display:flex;align-items:center;gap:12px;flex-wrap:wrap;padding:18px 0 16px}
.ml-crumbs{font-size:.9rem;color:var(--slate-soft);display:flex;align-items:center;gap:8px}
.ml-crumbs b{color:var(--heading);font-weight:600}
.ml-crumb-sep{opacity:.55}
.ml-toolbar-grow{flex:1}
.ml-filter-form{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
.ml-search{position:relative;min-width:230px}
.ml-search input{width:100%;padding:9px 12px 9px 34px;border:1px solid var(--line-light);border-radius:10px;background:var(--surface);font:inherit;font-size:.9rem;color:var(--slate)}
.ml-search::before{content:"\2315";position:absolute;left:11px;top:50%;transform:translateY(-50%);opacity:.5;font-size:1.05rem}
.ml-select,.ml-btn{font:inherit;font-size:.9rem;border-radius:10px;border:1px solid var(--line-light);background:var(--surface);color:var(--slate);padding:9px 13px;cursor:pointer}
.ml-btn--primary{background:var(--brass);border-color:var(--brass);color:var(--on-brass,#1c1204);font-weight:600;display:inline-flex;align-items:center;gap:7px}
.ml-btn--primary:hover{filter:brightness(1.08)}
.ml-toggle{display:flex;border:1px solid var(--line-light);border-radius:10px;overflow:hidden;background:var(--surface)}
.ml-toggle button{border:0;background:transparent;padding:9px 12px;cursor:pointer;color:var(--slate-soft);font-size:1rem}
.ml-toggle button.on{background:var(--parchment-2);color:var(--heading)}

.ml-panes{display:grid;grid-template-columns:210px minmax(0,1fr) 300px;gap:18px;align-items:start}
.ml-card{background:var(--surface);border:1px solid var(--line-light);border-radius:var(--r);box-shadow:0 18px 40px -28px rgba(0,0,0,.45)}

/* folders rail */
.ml-rail{padding:8px}
.ml-rail-grp{font-size:.62rem;letter-spacing:.16em;text-transform:uppercase;color:var(--slate-soft);padding:12px 10px 6px;font-weight:700;margin:0}
.ml-rail hr{border:0;border-top:1px solid var(--line-light);margin:8px 6px}
.ml-folder{display:flex;align-items:center;gap:9px;padding:8px 10px;border-radius:9px;cursor:pointer;font-size:.9rem;color:var(--slate);text-decoration:none;border:0;background:transparent;width:100%;text-align:left;font-family:var(--sans)}
.ml-folder:hover{background:var(--parchment-2)}
.ml-folder.on{background:color-mix(in srgb,var(--brass) 18%,transparent);color:var(--heading);font-weight:600}
.ml-folder-ic{width:17px;text-align:center;opacity:.8;flex:none}
.ml-folder-n{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.ml-folder-ct{font-size:.78rem;color:var(--slate-soft);font-variant-numeric:tabular-nums}
.ml-folder.on .ml-folder-ct{color:var(--heading)}
.ml-newf{margin:4px 0 6px;color:var(--brass-ink);font-weight:600;border:1px dashed var(--line-light);cursor:pointer}
.ml-newf-form{display:flex;gap:6px;padding:2px 6px 8px}
.ml-newf-form input{flex:1;min-width:0;border:1px solid var(--line-light);border-radius:8px;padding:6px 8px;font:inherit;font-size:.85rem;color:var(--slate);background:var(--surface)}
.ml-newf-form .ml-btn{padding:6px 10px;font-size:.82rem}
.ml-usage{padding:12px 12px 14px;border-top:1px solid var(--line-light);margin-top:6px}
.ml-usage-lab{font-size:.68rem;letter-spacing:.12em;text-transform:uppercase;color:var(--slate-soft);font-weight:700;margin:0 0 8px}
.ml-usage-bar{height:7px;border-radius:99px;background:var(--parchment-2);overflow:hidden}
.ml-usage-bar>span{display:block;height:100%;background:linear-gradient(90deg,var(--brass),var(--brass-ink));transition:width .3s}
.ml-usage--warn .ml-usage-bar>span{background:linear-gradient(90deg,#b8863b,var(--claret))}
.ml-usage-val{font-size:.8rem;color:var(--slate-soft);margin:7px 0 0;font-variant-numeric:tabular-nums}

/* dropnote + upload progress */
.ml-dropnote{border:1.5px dashed var(--line-light);border-radius:var(--r);padding:11px 14px;margin-bottom:14px;color:var(--slate-soft);font-size:.86rem;display:flex;align-items:center;gap:10px;background:color-mix(in srgb,var(--brass) 6%,var(--surface))}
.ml-dropnote b{color:var(--heading)}
.ml-dragover{outline:2px dashed var(--brass);outline-offset:-2px;background:color-mix(in srgb,var(--brass) 12%,var(--surface))}
/* The page hides and shows a lot of things with the `hidden` attribute (drawers, the bulk group,
   the uploads panel, empty/loading states). Any class rule that sets `display` silently beats the
   UA stylesheet's [hidden]{display:none}, so `hidden` stops working - which is exactly what the
   folder drawer hit. One guard for the whole page rather than a paired [hidden] rule per class. */
.ml-page [hidden]{display:none !important}

/* FR-7/FR-8: the folder row carries its manage toggle as a SIBLING (a button inside the <a> would
   be invalid), and the rename/delete drawer sits under it. */
.ml-folder-row{display:flex;align-items:center;gap:2px}
.ml-folder-row .ml-folder{flex:1;min-width:0}
.ml-folder-menu{flex:0 0 auto;font:inherit;line-height:1;border:0;background:none;color:var(--slate-soft);cursor:pointer;padding:6px 6px;border-radius:7px;opacity:0;transition:opacity .15s}
.ml-folder-row:hover .ml-folder-menu,.ml-folder-menu:focus-visible,.ml-folder-menu[aria-expanded="true"]{opacity:1}
.ml-folder-menu:hover{background:var(--parchment-2);color:var(--heading)}
/* minmax(0,1fr) + min-width:0 are load-bearing: a grid item defaults to min-width:auto, so the
   rename form would refuse to shrink below its min-content width and spill out of the 210px rail
   over the file grid (where it is unclickable). The input's own min-width:0 is not enough - it
   fixes the flex row INSIDE the form, not the form's own floor as a grid item. */
.ml-folder-drawer{display:grid;grid-template-columns:minmax(0,1fr);gap:6px;padding:8px 8px 10px;margin:2px 0 6px;background:var(--parchment-2);border-radius:9px}
.ml-folder-drawer>*{min-width:0}
.ml-folder-drawer .ml-newf-form{margin:0;padding:0}
.ml-folder-del{font:inherit;font-size:.78rem;width:100%;text-align:left;border:1px solid color-mix(in srgb,var(--claret) 35%,var(--line-light));border-radius:9px;background:var(--surface);color:var(--claret);padding:6px 10px;cursor:pointer}
.ml-folder-del:hover{background:color-mix(in srgb,var(--claret) 8%,transparent)}

/* Trash view banner: trashed bytes keep counting toward the quota until deletion (Q3). */
.ml-trashnote{display:flex;align-items:center;gap:12px;flex-wrap:wrap;font-size:.85rem;color:var(--slate);background:var(--surface);border:1px solid var(--line-light);border-radius:11px;padding:11px 14px;margin-bottom:14px}
.ml-trashnote>span{flex:1;min-width:220px}
.ml-trashnote form{margin:0}
.ml-trashnote .ml-bulk-del{font:inherit;font-size:.8rem;border:1px solid color-mix(in srgb,var(--claret) 35%,var(--line-light));border-radius:9px;background:var(--surface);color:var(--claret);padding:6px 12px;cursor:pointer}
.ml-trashnote .ml-bulk-del:hover{background:color-mix(in srgb,var(--claret) 8%,transparent)}

.ml-uploads{display:grid;gap:6px;margin-bottom:14px}
.ml-uprow{display:flex;align-items:center;gap:10px;font-size:.82rem;color:var(--slate);background:var(--surface);border:1px solid var(--line-light);border-radius:9px;padding:7px 10px}
.ml-uprow-name{flex:0 1 220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-weight:600}
.ml-uprow-bar{flex:1;height:6px;border-radius:99px;background:var(--parchment-2);overflow:hidden}
.ml-uprow-bar>span{display:block;height:100%;width:0;background:var(--brass);transition:width .2s}
.ml-uprow-status{flex:0 0 auto;max-width:45%;color:var(--slate-soft)}
.ml-uprow.ok .ml-uprow-status{color:var(--ok-ink);background:var(--ok-fill);border-radius:5px;padding:0 6px;font-weight:600}
.ml-uprow.err .ml-uprow-status{color:var(--claret);font-weight:600}
.ml-uprow.err .ml-uprow-bar>span{background:var(--claret)}
/* Dismiss control: shown only on a FAILED row, which stays put so its reason stays readable
   (a successful row retires itself on a timer, so it never needs one). */
.ml-uprow-x{flex:0 0 auto;font:inherit;line-height:1;border:0;background:none;color:var(--slate-soft);cursor:pointer;padding:0 2px;border-radius:6px}
/* --claret is DERIVED (a 4.5:1 floor), so a wash of it under itself spends the floor — the same defect HW
   Phase 4 found in `.hlp-q--light`, in a hover state on a page no crawl reaches in this shape. Measured on
   eleven palettes: the shipped 10% wash was 4.02 on MIDNIGHT (and 5.1–10.9 everywhere else, because the
   composer only lifts a stored claret that is too weak — on ten palettes it starts well past the floor and
   the wash has room to spend). --parchment-2 is the header's own chip ground and one of the three the floor
   is computed against: worst 5.44. Out of HW's phase, fixed here because the remedy is a token swap.
   StylesheetPairingTests.NoDerivedAccentTintsTheGroundBeneathItself holds it. */
.ml-uprow-x:hover{color:var(--claret);background:var(--parchment-2)}

/* selection bar */
.ml-selbar{display:flex;align-items:center;gap:12px;font-size:.85rem;color:var(--slate-soft);padding:0 0 10px}
.ml-selall{display:inline-flex;align-items:center;gap:7px;cursor:pointer;margin:0}
.ml-selall input{accent-color:var(--brass)}
.ml-selcount{font-weight:600;color:var(--heading)}
/* Phase 6: the bulk action group, revealed with the count while a selection exists (FR-37/38/39/40) */
.ml-bulk{display:inline-flex;align-items:center;gap:8px;flex-wrap:wrap}
.ml-bulk select,.ml-bulk button{font:inherit;font-size:.8rem;border:1px solid var(--line-light);border-radius:9px;background:var(--surface);color:var(--slate);padding:6px 10px;cursor:pointer}
.ml-bulk button:hover{background:var(--parchment-2)}
.ml-bulk button[disabled]{opacity:.55;cursor:default}
.ml-bulk .ml-bulk-del{color:var(--claret);border-color:color-mix(in srgb,var(--claret) 35%,var(--line-light))}
.ml-bulk .ml-bulk-del:hover{background:color-mix(in srgb,var(--claret) 8%,transparent)}
.ml-bulk-msg{margin-left:auto;font-size:.8rem;color:var(--brass-ink)}
.ml-bulk-msg.err{color:var(--claret)}

/* grid + tiles */
.ml-gridwrap{position:relative}
.ml-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:14px}
.ml-tile{border:1px solid var(--line-light);border-radius:12px;overflow:hidden;background:var(--surface);cursor:pointer;transition:transform .12s,box-shadow .12s}
.ml-tile:hover{transform:translateY(-2px);box-shadow:0 18px 40px -28px rgba(0,0,0,.55)}
.ml-tile:focus-visible{outline:2px solid var(--brass);outline-offset:2px}
.ml-tile.sel,.ml-tile.active{border-color:var(--brass);box-shadow:0 0 0 2px color-mix(in srgb,var(--brass) 45%,transparent)}
.ml-thumb{aspect-ratio:4/3;display:flex;align-items:center;justify-content:center;position:relative;background:var(--parchment-2);overflow:hidden}
.ml-thumb img{width:100%;height:100%;object-fit:cover}
.ml-tile[data-kind="svg"] .ml-thumb img{object-fit:contain;padding:14px;box-sizing:border-box}
.ml-glyph{font-size:2rem;color:var(--slate-soft)}
.ml-badge{position:absolute;top:7px;left:7px;font-size:.58rem;font-weight:700;letter-spacing:.06em;text-transform:uppercase;background:rgba(0,0,0,.55);color:#fff;padding:2px 6px;border-radius:5px}
.ml-chk{position:absolute;top:7px;right:7px;margin:0;display:flex}
.ml-chk input{width:18px;height:18px;accent-color:var(--brass);cursor:pointer}
.ml-tmeta{padding:8px 10px 10px}
.ml-fn{font-size:.8rem;font-weight:600;color:var(--heading);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ml-fd{font-size:.72rem;color:var(--slate-soft);margin-top:2px;font-variant-numeric:tabular-nums}

/* list layout (FR-11) */
.ml-grid--list{grid-template-columns:1fr;gap:8px}
.ml-grid--list .ml-tile{display:flex;align-items:center}
.ml-grid--list .ml-thumb{aspect-ratio:auto;width:88px;height:60px;flex:none}
.ml-grid--list .ml-tmeta{flex:1;min-width:0;display:flex;align-items:baseline;gap:12px}
.ml-grid--list .ml-fn{flex:1;min-width:0}
.ml-grid--list .ml-fd{margin:0;flex:none}
.ml-grid--list .ml-badge{display:none}

/* empty + loading states (FR-49/FR-50) */
.ml-empty{border:1px dashed var(--line-light);border-radius:var(--r);padding:44px 20px;text-align:center;color:var(--slate-soft)}
.ml-empty p{margin:0}
.ml-loading{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;gap:10px;background:color-mix(in srgb,var(--parchment) 72%,transparent);border-radius:var(--r);color:var(--slate);font-size:.9rem;z-index:2}
.ml-spinner{width:18px;height:18px;border-radius:50%;border:2.5px solid var(--line-light);border-top-color:var(--brass);animation:ml-spin .8s linear infinite}
@keyframes ml-spin{to{transform:rotate(360deg)}}

/* pager (FR-14) */
.ml-pager{display:flex;align-items:center;gap:6px;margin-top:18px;font-size:.85rem;color:var(--slate-soft)}
.ml-page-btn{border:1px solid var(--line-light);background:var(--surface);border-radius:8px;padding:5px 10px;cursor:pointer;color:var(--slate);text-decoration:none;font-variant-numeric:tabular-nums}
.ml-page-btn:hover{background:var(--parchment-2)}
.ml-page-btn.on{background:var(--brass);border-color:var(--brass);color:var(--on-brass,#1c1204);font-weight:600}
.ml-page-gap{padding:0 2px}

/* inspector (FR-24) */
.ml-insp{overflow:hidden;position:sticky;top:14px}
.ml-insp-empty{padding:40px 18px;text-align:center;color:var(--slate-soft);font-size:.9rem}
.ml-insp-empty p{margin:0}
.ml-insp-prev{aspect-ratio:4/3;display:flex;align-items:center;justify-content:center;background:var(--parchment-2);overflow:hidden}
.ml-insp-prev img{width:100%;height:100%;object-fit:contain}
.ml-insp-fields{padding:14px 15px 16px;display:grid;gap:13px}
.ml-insp-fields h3{margin:0;font-size:1.05rem;font-family:var(--serif);font-weight:600;color:var(--heading);word-break:break-word}
.ml-field{display:grid;gap:5px}
.ml-flabel{font-size:.62rem;letter-spacing:.1em;text-transform:uppercase;color:var(--slate-soft);font-weight:700}
.ml-urlrow{display:flex;border:1px solid var(--line-light);border-radius:9px;overflow:hidden}
.ml-urlrow input{flex:1;border:0;padding:8px 10px;font-size:.78rem;font-family:ui-monospace,Menlo,monospace;color:var(--slate);background:var(--parchment-2);min-width:0}
.ml-urlrow button{border:0;border-left:1px solid var(--line-light);background:var(--surface);padding:0 11px;cursor:pointer;color:var(--brass-ink);font-weight:600;font-size:.78rem}
.ml-urlrow button:hover{background:var(--parchment-2)}
.ml-meta{display:grid;grid-template-columns:auto 1fr;gap:5px 12px;font-size:.82rem;margin:0}
.ml-meta dt{color:var(--slate-soft)}
.ml-meta dd{margin:0;color:var(--slate);text-align:right;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}
.ml-insp-alt{margin:0;font-size:.85rem;color:var(--slate)}

/* Phase 4: alt-text editor + single-file actions (FR-25/26/28/29/30) */
.ml-alt{width:100%;border:1px solid var(--line-light);border-radius:9px;padding:8px 10px;font:inherit;font-size:.85rem;resize:vertical;min-height:56px;color:var(--slate);background:var(--surface)}
.ml-alt:focus{outline:2px solid var(--brass);outline-offset:1px}
.ml-actions{display:grid;grid-template-columns:1fr 1fr;gap:8px}
.ml-actions button,.ml-actions .ml-actlink{border:1px solid var(--line-light);background:var(--surface);border-radius:9px;padding:8px;cursor:pointer;font:inherit;font-size:.82rem;color:var(--slate);display:flex;align-items:center;justify-content:center;gap:6px;text-decoration:none}
.ml-actions button:hover,.ml-actions .ml-actlink:hover{background:var(--parchment-2)}
.ml-actions .ml-act-save{grid-column:1 / -1;background:var(--brass);border-color:var(--brass);color:var(--on-brass,#1c1204);font-weight:600}
.ml-actions .ml-act-save:hover{background:var(--brass);filter:brightness(1.08)}
/* Phase 5: the danger actions - delete-to-trash + the Trash view's permanent delete (FR-31/33/35) */
.ml-actions .ml-act-del{color:var(--claret);border-color:color-mix(in srgb,var(--claret) 35%,var(--line-light))}
.ml-actions .ml-act-del:hover{background:color-mix(in srgb,var(--claret) 8%,transparent)}
.ml-actions .ml-act-del--wide{grid-column:1 / -1}
.ml-moverow{display:flex;gap:8px}
.ml-moverow select{flex:1;min-width:0;font:inherit;font-size:.85rem;border:1px solid var(--line-light);border-radius:9px;padding:8px 10px;background:var(--surface);color:var(--slate)}
.ml-moverow .ml-btn{padding:8px 12px;font-size:.82rem}
.ml-act-msg{margin:0;font-size:.8rem;color:var(--brass-ink)}
.ml-act-msg.err{color:var(--claret)}

@media(max-width:1080px){
  .ml-panes{grid-template-columns:1fr}
  .ml-insp{position:static}
  .ml-rail{order:2}
}

/* =========================================================================
   PV (photo viewer widget) - the RENDER side.
   docs/photo-viewer-widget-plan.md §8.6. Phase 1 shipped the static frame;
   Phase 3 added the motion and the effect treatments below; Phase 4 added the
   three static layouts (grid / masonry / full-bleed) at the end of this block,
   which is where display.Layout is finally read.

   Namespace note: the obvious `pv-` prefix is ALREADY TAKEN by the theme
   admin's live-preview minisite (.pv-site/.pv-hero/.pv-nav, ~line 5730), so
   this uses the full `photo-viewer` block name. Do not "shorten" it.

   Everything is theme-token driven - a cinematic widget puts light text over
   an image inside a themed surface, which is exactly where the render-mapping
   tokens matter. Verify on Midnight and College, not just Heritage.

   TWO custom properties arrive inline on the article, from
   PhotoViewerRenderTokens.RootStyle: --photo-viewer-aspect (the stage's
   aspect-ratio) and --photo-viewer-intensity (the 0-1 scalar every filter
   function multiplies through). They are set on the ARTICLE, not the stage,
   because custom properties inherit and the filters live on the images.

   REDUCED MOTION IS OFF, NOT SHORTENED (plan §8.8). Every animation and
   transition below sits inside `@media(prefers-reduced-motion:no-preference)`,
   so the default - what a visitor who has asked for reduced motion gets - is a
   still first slide with working prev/next. The runtime module makes the same
   promise on its side: no autoplay, no parallax listener.
   ========================================================================= */
.home-widget--photo-viewer{
  background:var(--ink);
  color:var(--on-dark);
  overflow:hidden;
  padding:0;

  /* Film grain, as a tiled SVG turbulence texture. Declared here rather than at
     :root so it travels with the widget, and used only when the Effects panel
     asks for it. It is an OVERLAY over the served image - stored bytes are
     never altered (plan §2). */
  --photo-viewer-grain:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* The two Effects switches that shape the widget's OUTER frame rather than the
   image. `.home-widget` gives every widget `border-radius:var(--r)`, so
   "rounded off" has to SQUARE it rather than simply not adding a radius - these
   two rules have equal specificity and the -rounded one is written second, so
   it wins. `overflow:hidden` above is what makes the radius clip the stage. */
.home-widget--photo-viewer{border-radius:0}
.home-widget--photo-viewer-rounded{border-radius:var(--r)}
.home-widget--photo-viewer-shadow{box-shadow:0 40px 90px -50px rgba(0,0,0,.9),0 0 0 1px var(--line-light)}

.photo-viewer{
  display:block;
}

.photo-viewer__stage{
  position:relative;
  aspect-ratio:var(--photo-viewer-aspect,16 / 9);
  background:var(--ink-deep,#000);
  overflow:hidden;
}

/* The track holding every slide. Both transition families work off this one
   element: crossfade / Ken Burns / parallax stack the slides on top of each
   other and cross-fade them, while `slide` turns the track into a flex row and
   translates it by --photo-viewer-index. That keeps ONE markup shape for all
   four transitions, so the runtime never rebuilds the DOM to change mode. */
.photo-viewer__slides{
  position:absolute;
  inset:0;
}

.photo-viewer__slide{
  position:absolute;
  inset:0;
  opacity:0;
}

.photo-viewer__slide.is-current{
  opacity:1;
}

/* The frame holds the photo plus its blurred backdrop and carries the effect
   treatment for both (see the partial). `overflow:hidden` matters in `slide`
   mode, where the slides are flex items rather than a stack - without it the
   over-scaled backdrop bleeds into the neighbouring slide. */
.photo-viewer__frame{
  position:absolute;
  inset:0;
  overflow:hidden;
}

/* PORTRAIT HANDLING. The stage has a fixed aspect, so `cover` centre-cropped a
   portrait photo to a horizontal band - a 9:16 phone picture in a 21:9 stage
   kept about its middle 13%. `contain` never crops; the backdrop below fills
   what is left, so the widget's height is still constant and the letterbox
   reads as deliberate rather than as a gap.

   Do not "restore" cover here. If a crop is ever wanted it belongs behind a
   per-photo control, not as the silent default. */
.photo-viewer__image{
  position:absolute;
  inset:0;
  width:100%;
  height:100%;
  object-fit:contain;
  display:block;
}

/* An enlarged, blurred, dimmed copy of the same photo, filling the letterbox.
   It loads the 320px thumbnail, not the full image: it is blurred past
   recognition anyway, so paying for the original twice would be waste.
   `scale` hides the soft edges a blur leaves at the element's bounds. */
.photo-viewer__backdrop{
  position:absolute;
  inset:0;
  width:100%;
  height:100%;
  object-fit:cover;
  display:block;
  transform:scale(1.15);
  filter:blur(28px) brightness(.55) saturate(1.15);
}

/* The next three layers sit INSIDE a slide, between its image and its caption -
   they are treatments of that photo, so they crossfade with it, and painting
   them at stage level would cover every caption. DOM order alone puts the
   caption on top of them; no z-index is involved, and none should be added
   without checking the caption still wins.

   The scrim keeps the caption legible over an arbitrary photo without dimming
   the whole image: the gradient is bottom-weighted, where the text is. */
.photo-viewer__scrim{
  position:absolute;
  inset:0;
  background:linear-gradient(to top,rgba(0,0,0,.68) 0%,rgba(0,0,0,.18) 42%,transparent 72%);
  pointer-events:none;
}

/* Vignette darkens the EDGES only, with an inset shadow rather than a gradient,
   so it survives any aspect ratio without being re-tuned. */
.photo-viewer__vignette{
  position:absolute;
  inset:0;
  box-shadow:inset 0 0 160px 40px rgba(0,0,0,.55);
  pointer-events:none;
}

/* `mix-blend-mode:overlay` is what makes this read as grain rather than a grey
   film: it lifts the lights and deepens the darks instead of flattening both.
   Where the blend mode is unsupported the texture simply sits on top at 7%
   opacity, which is the same "degrade to the plain image" rule the filters
   follow (plan §8.8). */
.photo-viewer__grain{
  position:absolute;
  inset:0;
  background-image:var(--photo-viewer-grain);
  background-size:170px;
  opacity:.07;
  mix-blend-mode:overlay;
  pointer-events:none;
}

.photo-viewer__corner{
  position:absolute;
  width:26px;
  height:26px;
  border:2px solid var(--brass);
  opacity:.85;
  pointer-events:none;
}

.photo-viewer__corner--tl{top:16px;left:16px;border-right:0;border-bottom:0}
.photo-viewer__corner--tr{top:16px;right:16px;border-left:0;border-bottom:0}
.photo-viewer__corner--bl{bottom:16px;left:16px;border-right:0;border-top:0}
.photo-viewer__corner--br{bottom:16px;right:16px;border-left:0;border-top:0}

.photo-viewer__counter{
  position:absolute;
  top:22px;
  right:52px;
  display:flex;
  align-items:center;
  gap:12px;
  font-size:.72rem;
  font-variant-numeric:tabular-nums;
  letter-spacing:.14em;
  color:var(--on-dark);
  text-shadow:0 1px 3px rgba(0,0,0,.6);
}

.photo-viewer__counter b{
  color:var(--brass-soft);
  font-weight:700;
}

/* Tabular numerals above plus a fixed-width track here: without both, the
   counter shifts sideways as it steps from "09" to "10" and the whole chrome
   twitches once per slide. */
.photo-viewer__progress{
  width:104px;
  height:2px;
  border-radius:2px;
  background:color-mix(in srgb,var(--on-dark) 28%,transparent);
  overflow:hidden;
}

.photo-viewer__progress > span{
  display:block;
  height:100%;
  width:0;
  background:var(--brass);
}

/* Prev / next are HIDDEN until the runtime claims the widget. With no JS - or
   with a script error - a visitor gets an honest static first slide rather than
   two buttons that do nothing. The filmstrip stays visible either way because
   it is also the "there are more photos" signal. */
.photo-viewer__nav{
  position:absolute;
  top:50%;
  transform:translateY(-50%);
  display:none;
  align-items:center;
  justify-content:center;
  width:44px;
  height:44px;
  border-radius:50%;
  border:1px solid color-mix(in srgb,var(--brass) 40%,transparent);
  background:rgba(0,0,0,.42);
  color:var(--on-dark);
  font-size:1.5rem;
  line-height:1;
  cursor:pointer;
  transition:background .15s,border-color .15s,opacity .15s;
}

[data-photo-viewer-ready] .photo-viewer__nav{display:flex}

.photo-viewer__nav:hover{background:rgba(0,0,0,.72);border-color:var(--brass)}
.photo-viewer__nav:focus-visible{outline:2px solid var(--brass);outline-offset:2px}

/* Non-looping galleries disable rather than hide the end buttons: a control
   that vanishes moves everything next to it, and there is nothing next to it
   to move, so `disabled` is the honest state. */
.photo-viewer__nav[disabled]{opacity:.3;cursor:default}
.photo-viewer__nav[disabled]:hover{background:rgba(0,0,0,.42);border-color:color-mix(in srgb,var(--brass) 40%,transparent)}

.photo-viewer__nav--prev{left:20px}
.photo-viewer__nav--next{right:20px}

.photo-viewer__caption{
  position:absolute;
  left:34px;
  right:34px;
  bottom:30px;
  max-width:62ch;
}

.photo-viewer__caption h2{
  font-family:var(--serif);
  font-weight:500;
  font-size:1.7rem;
  line-height:1.15;
  margin:0;
  color:var(--on-dark);
  text-shadow:0 2px 10px rgba(0,0,0,.55);
}

/* Captions placed BELOW sit outside the scrim, so they take the widget's own
   surface colours rather than the over-image treatment. */
.photo-viewer__caption-below{
  font-family:var(--serif);
  font-size:1.05rem;
  color:var(--on-dark);
  margin:0;
  padding:16px 24px 0;
}

.photo-viewer__strip{
  display:flex;
  gap:8px;
  padding:14px 16px 16px;
  overflow-x:auto;
}

/* A real <button> from Phase 3, so selecting a photo is keyboard-operable for
   free - hence the padding/background reset a div did not need. */
.photo-viewer__thumb{
  flex:0 0 auto;
  width:74px;
  aspect-ratio:16 / 10;
  padding:0;
  /* Not `none`: the thumbnail is CONTAINED, so for a portrait photo this surround
     is what the letterboxed sides show. */
  background:var(--ink-deep,#000);
  border-radius:6px;
  overflow:hidden;
  border:1px solid color-mix(in srgb,var(--brass) 30%,transparent);
  opacity:.6;
  cursor:pointer;
  transition:opacity .15s,border-color .15s;
}

.photo-viewer__thumb:hover{opacity:.85}
.photo-viewer__thumb:focus-visible{outline:2px solid var(--brass);outline-offset:2px}

.photo-viewer__thumb--current{
  opacity:1;
  border-color:var(--brass);
}

/* `contain`, for the same reason the stage uses it: a cropped thumbnail makes a
   portrait photo look landscape, so the filmstrip stops describing the gallery
   it is a filmstrip for. The tile's own dark surround shows through. */
.photo-viewer__thumb img{
  width:100%;
  height:100%;
  object-fit:contain;
  display:block;
}

@media(max-width:900px){
  .photo-viewer__caption{left:22px;right:22px;bottom:22px}
  .photo-viewer__caption h2{font-size:1.3rem}
  .photo-viewer__counter{right:44px}
  .photo-viewer__progress{display:none}
  .photo-viewer__nav{width:38px;height:38px;font-size:1.25rem}
  .photo-viewer__nav--prev{left:12px}
  .photo-viewer__nav--next{right:12px}
}

/* -------------------------------------------------------------------------
   PV Phase 4 - the live region and the three STATIC layouts.

   The live region is visually hidden rather than display:none, because a
   display:none element is not announced at all - which would make the whole
   thing a no-op. The runtime writes into it only for a change the VISITOR
   asked for; autoplay stays silent (see photo-viewer.js).
   ------------------------------------------------------------------------- */
.photo-viewer__live{
  position:absolute;
  width:1px;
  height:1px;
  margin:-1px;
  padding:0;
  border:0;
  overflow:hidden;
  clip-path:inset(50%);
  white-space:nowrap;
}

/* GRID / MASONRY / FULL-BLEED. All three render the same list of figures and
   differ only in placement, which is the whole reason they share a partial.

   Two custom properties tune them, and both are set per layout rather than at
   :root so a widget spanning one column and one spanning three both look
   deliberate: the tile MINIMUM drives how many columns fit, and the browser
   decides the count from the width it actually has. */
.photo-viewer__tiles{
  list-style:none;
  margin:0;
  padding:0;
}

.photo-viewer--grid,
.photo-viewer--masonry{
  --photo-viewer-tile-min:190px;
  --photo-viewer-tile-gap:10px;
}

.photo-viewer--grid .photo-viewer__tiles{
  display:grid;
  grid-template-columns:repeat(auto-fill,minmax(var(--photo-viewer-tile-min),1fr));
  gap:var(--photo-viewer-tile-gap);
  padding:var(--photo-viewer-tile-gap);
}

/* Real masonry (grid-template-rows:masonry) is not everywhere yet, so this is
   the CSS-columns form: tiles flow down a column and then across, which is
   masonry's own reading order rather than a compromise. `break-inside` is what
   stops a figure being split across the column boundary. */
.photo-viewer--masonry .photo-viewer__tiles{
  columns:var(--photo-viewer-tile-min);
  column-gap:var(--photo-viewer-tile-gap);
  padding:var(--photo-viewer-tile-gap);
}

.photo-viewer--masonry .photo-viewer__tile{
  break-inside:avoid;
  margin-bottom:var(--photo-viewer-tile-gap);
}

/* Full-bleed is edge to edge with no gap at all - that is what "bleed" means.
   The widget's own rounding still clips it, because the article is
   overflow:hidden. */
.photo-viewer--full-bleed .photo-viewer__tiles{
  display:block;
  padding:0;
}

.photo-viewer__tile-figure{
  position:relative;
  display:block;
  margin:0;
}

.photo-viewer__tile-frame{
  position:relative;
  display:block;
  overflow:hidden;
  /* Not `none`: in the fixed-aspect grid the photo is CONTAINED, so this is
     what the letterboxed edges show through as. */
  background:var(--ink-deep,#000);
}

/* Masonry and full-bleed let the picture set its own height, so nothing is
   letterboxed and no blurred fill is needed - which is also why their markup
   emits no backdrop image at all. */
.photo-viewer__tile-image{
  display:block;
  width:100%;
  height:auto;
}

/* The grid gives every tile the SAME shape - the one the Aspect control chose -
   so the rows line up. The photo is contained inside it and the blurred copy
   fills the rest, exactly as the carousel stage does: nothing is ever cropped
   (see the carousel's PORTRAIT HANDLING note). */
.photo-viewer--grid .photo-viewer__tile-frame{
  aspect-ratio:var(--photo-viewer-aspect,16 / 9);
}

.photo-viewer--grid .photo-viewer__tile-image{
  position:absolute;
  inset:0;
  width:100%;
  height:100%;
  object-fit:contain;
}

/* SF PHASE 6 STEP 6, GATE SECTION 34 Q6b - ONE PHOTOGRAPH AT THE CARD'S OWN MEASURE
   (docs/sample-front-end-plan.md section 33.7, section 34.1).

   IT IS THE GRID'S FRAME WITHOUT THE GRID. The shape, the contained photograph and the blurred fill behind
   it are all the tiled layout's, which is what keeps this widget's standing promise (nothing is ever
   cropped) true in a layout whose whole job is to show one picture large. What it does NOT take is the
   auto-fill track list and the padding those tracks sit in: one tile across a 1132px measure wants the
   measure, not a fifth of it.

   `display:block` RATHER THAN A ONE-COLUMN GRID, because the tile is the only child and a grid would add a
   gap nothing sits in. The 10px padding the tiled layouts carry is dropped for the same reason full-bleed
   drops it: a single picture at the card's measure is already inside the card's own padding. */
.photo-viewer--single .photo-viewer__tiles{
  display:block;
  padding:0;
}

.photo-viewer--single .photo-viewer__tile-frame{
  aspect-ratio:var(--photo-viewer-aspect,16 / 9);
}

.photo-viewer--single .photo-viewer__tile-image{
  position:absolute;
  inset:0;
  width:100%;
  height:100%;
  object-fit:contain;
}

.home-widget--photo-viewer-rounded .photo-viewer--grid .photo-viewer__tile-frame,
.home-widget--photo-viewer-rounded .photo-viewer--masonry .photo-viewer__tile-frame,
.home-widget--photo-viewer-rounded .photo-viewer--single .photo-viewer__tile-frame{
  border-radius:6px;
}

/* THE TREATMENTS, AS PSEUDO-ELEMENTS. The carousel emits three overlay divs per
   slide, which is fine for the one or two it shows at a time; a 60-photo grid
   would emit 180 elements to express two booleans (plan §11.7). One ::before
   carries the scrim AND the vignette - a single element can hold both a
   gradient and an inset shadow - and ::after carries the grain, so the whole
   treatment costs nothing per photo.

   They sit INSIDE the frame, so the filter treats them with the image as one
   composite, and the caption sits outside it and is never filtered. Same rule
   as the carousel, reached a cheaper way. */
.photo-viewer--captions-overlay .photo-viewer__tile-frame::before,
.photo-viewer--vignette .photo-viewer__tile-frame::before,
.photo-viewer--grain .photo-viewer__tile-frame::after{
  content:"";
  position:absolute;
  inset:0;
  pointer-events:none;
}

/* The scrim exists to keep an overlaid caption legible over an arbitrary
   photo, so it is drawn only where captions are overlaid. */
.photo-viewer--captions-overlay .photo-viewer__tile-frame::before{
  background:linear-gradient(to top,rgba(0,0,0,.68) 0%,rgba(0,0,0,.18) 42%,transparent 72%);
}

/* Tighter than the carousel's 160px spread: a tile is a fraction of the size,
   and the stage's numbers on a 190px tile close the picture up entirely. */
.photo-viewer--vignette .photo-viewer__tile-frame::before{
  box-shadow:inset 0 0 90px 24px rgba(0,0,0,.55);
}

.photo-viewer--grain .photo-viewer__tile-frame::after{
  background-image:var(--photo-viewer-grain);
  background-size:170px;
  opacity:.07;
  mix-blend-mode:overlay;
}

.photo-viewer--full-bleed.photo-viewer--vignette .photo-viewer__tile-frame::before{
  box-shadow:inset 0 0 160px 40px rgba(0,0,0,.55);
}

.photo-viewer__tile-caption{
  font-family:var(--serif);
  color:var(--on-dark);
  margin:0;
}

.photo-viewer--captions-overlay .photo-viewer__tile-caption{
  position:absolute;
  left:14px;
  right:14px;
  bottom:11px;
  font-size:.95rem;
  line-height:1.2;
  text-shadow:0 2px 10px rgba(0,0,0,.55);
}

.photo-viewer--captions-below .photo-viewer__tile-caption{
  padding:8px 2px 2px;
  font-size:.9rem;
  line-height:1.3;
  opacity:.85;
}

/* Full-bleed shows one picture across the whole widget, so its caption is the
   editorial one the carousel uses rather than a tile label. */
.photo-viewer--full-bleed.photo-viewer--captions-overlay .photo-viewer__tile-caption{
  left:34px;
  right:34px;
  bottom:26px;
  max-width:62ch;
  font-size:1.4rem;
}

.photo-viewer--full-bleed.photo-viewer--captions-below .photo-viewer__tile-caption{
  padding:12px 24px 20px;
  font-size:1.05rem;
  opacity:1;
}

@media(max-width:900px){
  .photo-viewer--grid,
  .photo-viewer--masonry{
    --photo-viewer-tile-min:140px;
  }

  .photo-viewer--full-bleed.photo-viewer--captions-overlay .photo-viewer__tile-caption{
    left:22px;
    right:22px;
    bottom:18px;
    font-size:1.1rem;
  }
}

/* -------------------------------------------------------------------------
   PV filters - ONE set of rules, used by BOTH the render and the editor's
   chip swatches (plan §10.6: share the values, do not declare a second set).
   Selecting on the attribute rather than a class is what lets an <img> on the
   home page and a <span> in the edit dialog take the identical treatment.

   Intensity is folded in as a MULTIPLIER on each function's distance from its
   neutral value, so 0 leaves the image untouched and 100 is exactly the chip
   swatch. Where the chips render there is no --photo-viewer-intensity in
   scope, so the fallback of 1 keeps them at full strength.

   `original` deliberately has NO rule: the absence of a filter is the effect.
   ------------------------------------------------------------------------- */
/* IB PHASE 1 — TWO CALLERS, ONE SET OF RULES, ONE VARIABLE (plan §3, risk 4). A card's background layer
   wears the same six looks a photograph does, so these five rules take a SECOND SELECTOR rather than a
   second copy: the vocabulary is one list (HogLodge.Domain/Entities/ImageFilter.cs, which
   PhotoViewerVocabulary.Filters now reads) and the CSS that paints it is one set of declarations.

   THE VARIABLE WAS RENAMED, from --pv-fx to --img-fx. Reusing the rules meant either reusing the variable
   or renaming it in both places, and a copy would be the second list this whole arrangement exists to
   avoid. It is renamed rather than reused-as-is because a card background reading a variable called "pv" is
   the kind of name that makes the next person add a sixth rule instead of a second selector.

   TWO SEEDS AND NOT ONE, because the two callers name their strength differently and both names are already
   public: the gallery's --photo-viewer-intensity is set on the ARTICLE by PhotoViewerRenderTokens (and is
   asserted by name in two test classes), while the card's --image-filter-strength is set on the LAYER by
   ContentBlockBackgroundTokens. Each seed defaults to 1, so a chip preview with neither still shows the
   look at full strength.

   A shared selector list stops DRIFT and not the CASCADE ([[shared-selectors-do-not-share-specificity]]) —
   which is safe here because nothing else in this stylesheet states a `filter` on either element, so there
   is no second rule for a per-selector specificity resolution to go wrong in. */
[data-photo-viewer-filter]{--img-fx:var(--photo-viewer-intensity,1)}
[data-image-filter]{--img-fx:var(--image-filter-strength,1)}
[data-photo-viewer-filter="sepia"],[data-image-filter="sepia"]{filter:sepia(calc(.75 * var(--img-fx))) contrast(calc(1 + .04 * var(--img-fx)))}
[data-photo-viewer-filter="noir"],[data-image-filter="noir"]{filter:grayscale(var(--img-fx)) contrast(calc(1 + .22 * var(--img-fx)))}
[data-photo-viewer-filter="duotoneBrass"],[data-image-filter="duotoneBrass"]{filter:grayscale(var(--img-fx)) sepia(calc(.6 * var(--img-fx))) saturate(calc(1 + 1.2 * var(--img-fx))) hue-rotate(calc(-12deg * var(--img-fx)))}
[data-photo-viewer-filter="warmFilm"],[data-image-filter="warmFilm"]{filter:sepia(calc(.28 * var(--img-fx))) saturate(calc(1 + .18 * var(--img-fx))) contrast(calc(1 + .05 * var(--img-fx)))}
[data-photo-viewer-filter="cool"],[data-image-filter="cool"]{filter:saturate(calc(1 - .15 * var(--img-fx))) hue-rotate(calc(-8deg * var(--img-fx)))}

/* -------------------------------------------------------------------------
   PV motion (Phase 3).

   EVERYTHING that moves is inside the no-preference query below. That is the
   whole of the reduced-motion promise: the rules outside it describe a still
   carousel that still WORKS - slides swap instantly, prev/next and the
   filmstrip select, the counter counts - and the query only changes how the
   swap looks. There is no reduce-branch to keep in step, because "reduced"
   is the base state.
   ------------------------------------------------------------------------- */
@media(prefers-reduced-motion:no-preference){

  /* Crossfade is the default and also what Ken Burns and parallax use for the
     CHANGE itself - those two are ambient motion on the current slide, not a
     different way of getting between slides. */
  .photo-viewer__slide{
    transition:opacity .7s ease;
  }

  /* `slide` swaps the stack for a flex track and translates it by the current
     index, so no per-slide direction bookkeeping is needed: the runtime sets
     one number and the track goes there by the short way or the long way
     exactly as an index change implies. */
  [data-photo-viewer-transition="slide"] .photo-viewer__slides{
    display:flex;
    width:100%;
    height:100%;
    transform:translateX(calc(-100% * var(--photo-viewer-index,0)));
    transition:transform .6s cubic-bezier(.5,0,.2,1);
  }

  [data-photo-viewer-transition="slide"] .photo-viewer__slide{
    position:relative;
    inset:auto;
    flex:0 0 100%;
    height:100%;
    opacity:1;
    transition:none;
  }

  /* Ken Burns: a slow zoom-and-drift on the CURRENT slide only, so an
     off-screen slide is not animating for nothing. `alternate` means it eases
     back rather than snapping at the end of each cycle. */
  [data-photo-viewer-transition="kenBurns"] .photo-viewer__slide.is-current .photo-viewer__image{
    animation:photo-viewer-ken 18s ease-in-out infinite alternate;
  }

  /* Parallax: the image is over-scaled so it has room to drift, and the drift
     itself is --photo-viewer-parallax, which the runtime writes from the
     widget's position in the viewport. With no runtime the variable is unset,
     the fallback is 0, and this is just a slightly tighter crop. */
  [data-photo-viewer-transition="parallax"] .photo-viewer__image{
    transform:scale(1.14) translate3d(0,var(--photo-viewer-parallax,0%),0);
    will-change:transform;
  }
}

@keyframes photo-viewer-ken{
  from{transform:scale(1.02)}
  to{transform:scale(1.12) translate(-1.4%,-1%)}
}

/* ============================================================================
   PV Phase 2 - the photo viewer's EDITOR panels + the shared media picker.
   docs/photo-viewer-widget-plan.md §8.6 Phase 2.

   Both live on admin surfaces (the /Admin/Widgets edit dialog and a modal over
   it), which are deliberately a fixed white sheet rather than theme-tokened -
   the same choice the rest of .widget-edit-dialog makes. The RENDER-side block
   (.photo-viewer, above) is the themed one.

   Note the [hidden] guards below. site.css has no global [hidden] reset, so any
   rule setting `display` beats the attribute and a "hidden" pane stays visible
   (the ML Phase 7 incident). Every container here that toggles with `hidden`
   therefore sits under one of those two scoped resets - do not remove them.
   ============================================================================ */

.widget-photo-editor [hidden],
.media-picker [hidden]{display:none !important}

/* The editor sits in the control stack's SECOND column, next to the sticky
   fixed-settings panel - the same placement the content-block controls take, and
   for the same reason. The stack sets no column gap, so the inset is ours. */
.widget-photo-editor{
  display:grid;
  gap:.7rem;
  grid-column:2;
  align-self:start;
  padding-left:.85rem;
}

.widget-photo-editor__panel{
  border:1px solid rgba(148,163,184,.35);
  border-radius:12px;
  padding:.62rem .7rem .7rem;
  display:grid;
  gap:.5rem;
  background:var(--surface);
}

.widget-photo-editor__panel-head{
  display:flex;
  align-items:center;
  gap:.6rem;
  flex-wrap:wrap;
}

.widget-photo-editor__panel-title{
  font-size:.8rem;
  font-weight:700;
  color:var(--slate-soft);
  text-transform:uppercase;
  letter-spacing:.05em;
}

.widget-photo-editor__panel-head .widget-photo-editor__add{margin-left:auto}

.widget-photo-editor__row{
  display:flex;
  align-items:center;
  gap:.6rem;
  flex-wrap:wrap;
}

.widget-photo-editor__label{
  font-size:.85rem;
  color:var(--slate);
  min-width:7.5rem;
}

.widget-photo-editor__input{
  flex:1 1 12rem;
  min-width:0;
}

.widget-photo-editor__input--narrow{flex:0 0 6rem}

.widget-photo-editor__note{
  margin:0;
  font-size:.82rem;
  line-height:1.45;
  color:var(--slate-soft);
}

/* Segmented button groups (the mockup's `seg`). Buttons, not radios, because the
   whole editor is DOM-as-state and posts one JSON field - a real input here would
   post a name the server does not read. */
.widget-photo-editor__seg{
  display:inline-flex;
  flex-wrap:wrap;
  border:1px solid rgba(148,163,184,.42);
  border-radius:999px;
  padding:2px;
  gap:2px;
  background:var(--surface);
}

.widget-photo-editor__seg button{
  border:0;
  background:transparent;
  border-radius:999px;
  padding:.3rem .72rem;
  font:inherit;
  font-size:.84rem;
  color:var(--slate-soft);
  cursor:pointer;
}

.widget-photo-editor__seg button:hover{color:var(--heading)}

.widget-photo-editor__seg button.is-on{
  background:var(--brass);
  color:var(--on-brass,#1c1204);
  font-weight:600;
}

/* Filter chips: a segment whose options carry a preview swatch. */
.widget-photo-editor__chips{
  display:flex;
  flex-wrap:wrap;
  gap:.4rem;
}

.widget-photo-editor__chip{
  border:1px solid rgba(148,163,184,.42);
  border-radius:10px;
  background:var(--surface);
  padding:.3rem .45rem .35rem;
  display:grid;
  gap:.22rem;
  justify-items:center;
  cursor:pointer;
  font:inherit;
}

.widget-photo-editor__chip.is-on{
  border-color:var(--brass);
  box-shadow:0 0 0 2px rgba(199,162,74,.28);
}

.widget-photo-editor__chip-swatch{
  width:44px;
  height:26px;
  border-radius:6px;
  /* A neutral gradient stands in for a photograph so each filter reads as a
     treatment rather than as a colour. */
  background:linear-gradient(120deg,#8a7f6d 0%,#c9bfae 45%,#6d6355 100%);
}

.widget-photo-editor__chip-name{
  font-size:.72rem;
  color:var(--slate-soft);
}

.widget-photo-editor__chip.is-on .widget-photo-editor__chip-name{
  color:var(--heading);
  font-weight:600;
}

/* The chip previews carry [data-photo-viewer-filter] and are styled by the ONE
   set of filter rules in the PV RENDER section above - the swatch is a sample
   of the real thing, not a decorative guess, and there is no second set to
   drift. Phase 3 folded intensity into those rules as a multiplier; the chips
   sit outside any --photo-viewer-intensity, so they show full strength. */

.widget-photo-editor__slider{flex:1 1 10rem;min-width:0}

.widget-photo-editor__intensity{
  font-size:.82rem;
  color:var(--slate-soft);
  min-width:2.8rem;
  text-align:right;
}

.widget-photo-editor__switches{
  display:flex;
  flex-wrap:wrap;
  gap:.5rem 1.1rem;
}

.widget-photo-editor__switch{
  display:inline-flex;
  align-items:center;
  gap:.5rem;
  font-size:.85rem;
  color:var(--slate);
}

/* ---- The authored photo list ---- */

.widget-photo-editor__list{
  display:grid;
  gap:.42rem;
}

.widget-photo-editor__empty{
  margin:0;
  font-size:.85rem;
  color:var(--slate-soft);
  border:1px dashed rgba(148,163,184,.5);
  border-radius:10px;
  padding:.8rem;
  text-align:center;
}

.widget-photo-editor__item{
  display:grid;
  /* min-width:0 on the track AND on the children: a grid item defaults to
     min-width:auto, which lets a long file name push the row past the panel
     (the ML Phase 7 drawer incident, same cause). */
  grid-template-columns:auto auto minmax(0,1fr) auto;
  align-items:center;
  gap:.55rem;
  border:1px solid rgba(148,163,184,.35);
  border-radius:10px;
  padding:.4rem .5rem;
  background:var(--surface);
}

.widget-photo-editor__item > *{min-width:0}

.widget-photo-editor__item.is-dragging{opacity:.45}

.widget-photo-editor__item.is-unavailable{
  border-color:rgba(111,36,53,.45);
  background:rgba(111,36,53,.04);
}

.widget-photo-editor__grip{
  cursor:grab;
  color:var(--slate-soft);
  font-size:1.05rem;
  line-height:1;
  padding:0 .1rem;
}

.widget-photo-editor__thumb{
  width:66px;
  height:44px;
  border-radius:7px;
  overflow:hidden;
  background:#eceff3;
  display:block;
}

.widget-photo-editor__thumb img{
  width:100%;
  height:100%;
  object-fit:cover;
  display:block;
}

.widget-photo-editor__item-body{
  display:grid;
  gap:.3rem;
  min-width:0;
}

.widget-photo-editor__item-head{
  display:flex;
  align-items:center;
  gap:.45rem;
  flex-wrap:wrap;
}

.widget-photo-editor__item-name{
  font-size:.85rem;
  font-weight:600;
  color:var(--heading);
  overflow:hidden;
  text-overflow:ellipsis;
  white-space:nowrap;
}

.widget-photo-editor__badge{
  font-size:.72rem;
  border-radius:999px;
  padding:.12rem .5rem;
  font-weight:600;
}

.widget-photo-editor__badge--trashed{
  background:rgba(199,162,74,.2);
  color:var(--brass-ink);
}

.widget-photo-editor__badge--missing{
  background:rgba(111,36,53,.14);
  color:var(--claret);
}

.widget-photo-editor__item-field{
  display:flex;
  align-items:center;
  gap:.45rem;
}

.widget-photo-editor__mini{
  font-size:.72rem;
  text-transform:uppercase;
  letter-spacing:.05em;
  color:var(--slate-soft);
  min-width:3.6rem;
}

.widget-photo-editor__alt{
  margin:0;
  font-size:.75rem;
  color:var(--slate-soft);
}

.widget-photo-editor__remove{
  border:1px solid rgba(148,163,184,.42);
  background:var(--surface);
  border-radius:8px;
  width:1.9rem;
  height:1.9rem;
  cursor:pointer;
  color:var(--claret);
  font-size:1rem;
  line-height:1;
}

.widget-photo-editor__remove:hover{background:rgba(111,36,53,.08)}

.widget-photo-editor__folder-name{
  font-size:.88rem;
  font-weight:600;
  color:var(--heading);
}

.widget-photo-editor__folder-name.is-unset{
  font-weight:400;
  color:var(--slate-soft);
}

/* ============================ the shared media picker ====================== */

.media-picker{
  border:none;
  border-radius:16px;
  padding:0;
  width:min(95vw, 1020px);
  max-height:88vh;
  overflow:hidden;
  box-shadow:0 24px 60px rgba(0,0,0,.4);
}

/* Darker than the widget dialog's own backdrop: this one stacks ON TOP of it,
   so a matching value would leave the two indistinguishable. */
.media-picker::backdrop{
  background:rgba(0,0,0,.55);
  backdrop-filter:blur(2px);
}

.media-picker__inner{
  display:grid;
  grid-template-rows:auto auto minmax(0,1fr) auto;
  max-height:88vh;
  background:var(--surface);
}

.media-picker__head{
  display:flex;
  align-items:flex-start;
  gap:.8rem;
  padding:.9rem 1rem .6rem;
  border-bottom:1px solid rgba(148,163,184,.28);
}

.media-picker__title{margin:0;font-size:1.15rem}

.media-picker__sub{
  margin:.15rem 0 0;
  font-size:.82rem;
  color:var(--slate-soft);
  max-width:56ch;
}

.media-picker__close{
  margin-left:auto;
  border:0;
  background:transparent;
  font-size:1.5rem;
  line-height:1;
  cursor:pointer;
  color:var(--slate-soft);
}

.media-picker__toolbar{
  display:flex;
  align-items:center;
  gap:.5rem;
  padding:.6rem 1rem;
  border-bottom:1px solid rgba(148,163,184,.22);
  flex-wrap:wrap;
}

.media-picker__search{flex:1 1 14rem;min-width:0}
.media-picker__sort{flex:0 0 auto}

.media-picker__library-link{
  margin-left:auto;
  font-size:.82rem;
  color:var(--brass-ink);
}

.media-picker__body{
  display:grid;
  grid-template-columns:minmax(0,190px) minmax(0,1fr);
  min-height:0;
  overflow:hidden;
}

.media-picker__rail{
  border-right:1px solid rgba(148,163,184,.22);
  padding:.6rem .5rem;
  overflow-y:auto;
  display:grid;
  gap:.15rem;
  align-content:start;
}

.media-picker__rail-item{
  display:flex;
  align-items:center;
  gap:.5rem;
  width:100%;
  border:0;
  background:transparent;
  border-radius:8px;
  padding:.34rem .5rem;
  font:inherit;
  font-size:.86rem;
  color:var(--slate);
  cursor:pointer;
  text-align:left;
}

.media-picker__rail-item:hover{background:rgba(148,163,184,.14)}

.media-picker__rail-item.is-active{
  background:rgba(199,162,74,.2);
  font-weight:600;
  color:var(--heading);
}

.media-picker__rail-name{
  flex:1 1 auto;
  min-width:0;
  overflow:hidden;
  text-overflow:ellipsis;
  white-space:nowrap;
}

.media-picker__rail-count{
  font-size:.74rem;
  color:var(--slate-soft);
}

.media-picker__rail-empty{
  margin:.3rem .5rem;
  font-size:.8rem;
  color:var(--slate-soft);
}

.media-picker__main{
  display:grid;
  grid-template-rows:auto minmax(0,1fr) auto;
  min-height:0;
  min-width:0;
}

.media-picker__state{
  margin:0;
  padding:1.4rem 1rem;
  text-align:center;
  font-size:.88rem;
  color:var(--slate-soft);
}

.media-picker__grid{
  display:grid;
  grid-template-columns:repeat(auto-fill, minmax(140px, 1fr));
  gap:.6rem;
  padding:.7rem 1rem;
  overflow-y:auto;
  min-height:0;
  align-content:start;
}

.media-picker__tile{
  border:1px solid rgba(148,163,184,.35);
  border-radius:10px;
  background:var(--surface);
  padding:.35rem;
  display:grid;
  gap:.2rem;
  cursor:pointer;
  font:inherit;
  text-align:left;
  min-width:0;
}

.media-picker__tile.is-picked{
  border-color:var(--brass);
  box-shadow:0 0 0 2px rgba(199,162,74,.35);
}

/* Already in the caller's list: a hint, never a block. Adding the same picture
   twice is legitimate (a gallery may want a bookend), so this only says so. */
.media-picker__tile.is-already{border-style:dashed}

.media-picker__tile-frame{
  position:relative;
  display:block;
  aspect-ratio:4 / 3;
  border-radius:7px;
  overflow:hidden;
  background:#eceff3;
}

.media-picker__tile-img{
  width:100%;
  height:100%;
  object-fit:cover;
  display:block;
}

.media-picker__tile-check{
  position:absolute;
  top:6px;
  right:6px;
  width:1.35rem;
  height:1.35rem;
  border-radius:50%;
  background:var(--brass);
  color:var(--on-brass,#1c1204);
  font-size:.85rem;
  line-height:1.35rem;
  text-align:center;
  opacity:0;
  transition:opacity .12s ease;
}

.media-picker__tile.is-picked .media-picker__tile-check{opacity:1}

.media-picker__tile-name{
  font-size:.78rem;
  font-weight:600;
  color:var(--heading);
  overflow:hidden;
  text-overflow:ellipsis;
  white-space:nowrap;
}

.media-picker__tile-meta{
  font-size:.7rem;
  color:var(--slate-soft);
  overflow:hidden;
  text-overflow:ellipsis;
  white-space:nowrap;
}

.media-picker__tile-meta.is-missing{color:var(--claret)}

.media-picker__paging{
  display:flex;
  align-items:center;
  gap:.6rem;
  padding:.5rem 1rem;
  border-top:1px solid rgba(148,163,184,.22);
}

.media-picker__count{
  font-size:.8rem;
  color:var(--slate-soft);
}

.media-picker__foot{
  display:flex;
  align-items:center;
  gap:.6rem;
  padding:.7rem 1rem;
  border-top:1px solid rgba(148,163,184,.28);
}

.media-picker__selection{
  font-size:.84rem;
  color:var(--slate-soft);
}

.media-picker__selection.is-warning{color:var(--claret);font-weight:600}

.media-picker__spacer{flex:1 1 auto}

/* Folder mode picks from the RAIL, so the grid becomes a preview of what the
   chosen folder holds rather than something to click. */
.media-picker--folder-mode .media-picker__tile{cursor:default}
.media-picker--folder-mode .media-picker__tile-check{display:none}

/* Matches the stack's own single-column breakpoint above - the editor has to
   fall back with it, or it keeps asking for a column that no longer exists. */
@media(max-width:980px){
  .widget-photo-editor{
    grid-column:1;
    padding-left:0;
  }
}

@media(max-width:760px){
  .media-picker__body{grid-template-columns:minmax(0,1fr)}
  .media-picker__rail{
    border-right:0;
    border-bottom:1px solid rgba(148,163,184,.22);
    grid-auto-flow:column;
    grid-auto-columns:max-content;
    overflow-x:auto;
  }
  .widget-photo-editor__item{grid-template-columns:auto minmax(0,1fr) auto}
  .widget-photo-editor__thumb{display:none}
}

/* ============================================================================================================
   WD (Phase 2b): the separator + table content blocks — editor controls and home render.

   Both kinds reached the editor entirely through the block-kind registry (Phase 2a), so these rules are the
   only presentation either one needed; nothing here overrides an editor-core class.
   ============================================================================================================ */

/* --- Separator: the editor panel ------------------------------------------------------------------------- */

/* The separator's controls ARE its body (no toggle chip, and no border panel — a rule does not take a border),
   so this is a plain settings stack rather than a collapsible sub-panel. */
.widget-separator-control{
  display:grid;
  gap:.55rem;
}

.widget-separator-control__row{
  display:flex;
  align-items:center;
  flex-wrap:wrap;
  gap:.45rem;
}

.widget-separator-control__label{
  min-width:5.2rem;
  color:var(--slate-soft);
  font-size:.68rem;
  font-weight:700;
  letter-spacing:.08em;
  text-transform:uppercase;
}

.widget-separator-control__choices,
.widget-separator-control__glyphs,
.widget-separator-control__swatches{
  display:flex;
  flex-wrap:wrap;
  gap:.22rem;
}

/* Wide enough for a glyph or two and no wider — this is the "◈" slot, not a text field. */
.widget-separator-control__ornament{
  width:4.2rem;
  border:1px solid rgba(100,116,139,.35);
  border-radius:5px;
  padding:.24rem .4rem;
  font-size:.95rem;
  text-align:center;
}

.btn-separator-glyph{
  min-width:1.9rem;
  font-size:.95rem;
}

/* --- SCC Phase 4a: the data-bound blocks' editor panel ---------------------------------------------------- */

/* ONE family for every data-bound kind (`.widget-databound-control`), not one per kind. Events, Links and
   Charity ask the same shape of question — how many, in what order, what to say when there is nothing — so a
   per-kind family would be three copies free to drift, which is the argument §12.2 makes for composing the
   `card` shell out of `surface` rather than restating it. */
.widget-databound-control{
  display:grid;
  gap:.6rem;
}

.widget-databound-control__row{
  display:flex;
  flex-wrap:wrap;
  gap:.6rem;
}

.widget-databound-control__field{
  display:grid;
  gap:.22rem;
  flex:1;
  min-width:8rem;
}

.widget-databound-control__label{
  color:var(--slate-soft);
  font-size:.68rem;
  font-weight:700;
  letter-spacing:.08em;
  text-transform:uppercase;
}

.widget-databound-control__number,
.widget-databound-control__select,
.widget-databound-control__text{
  width:100%;
  border:1px solid rgba(100,116,139,.35);
  border-radius:5px;
  padding:.28rem .42rem;
  font:inherit;
  font-size:.85rem;
}

.widget-databound-control__text{
  resize:vertical;
  line-height:1.35;
}

.widget-databound-control__check{
  display:flex;
  align-items:center;
  gap:.4rem;
  font-size:.85rem;
}

.widget-databound-control__hint{
  color:var(--slate-soft);
  font-size:.72rem;
}

/* --- Table: the editor builder --------------------------------------------------------------------------- */

.widget-table-builder{
  display:grid;
  gap:.5rem;
}

.widget-table-builder__toolbar{
  display:flex;
  align-items:center;
  flex-wrap:wrap;
  gap:.4rem;
}

.widget-table-builder__toolbar-spacer{
  flex:1 1 auto;
}

.widget-table-builder__header-toggle{
  display:inline-flex;
  align-items:center;
  gap:.35rem;
  color:var(--slate);
  font-size:.75rem;
  cursor:pointer;
}

.widget-table-builder__header-toggle input{
  width:auto;
  margin:0;
}

/* A narrow widget cannot show six columns, so the grid scrolls sideways inside the block rather than forcing
   the dialog to. */
.widget-table-builder__scroll{
  overflow-x:auto;
}

/* One grid template shared by the column strip and every row (site.js sets --table-columns whenever the column
   count changes), which is what lines the columns up without a real <table> element. The trailing track is the
   remove button's. */
.widget-table-builder__columns,
.widget-table-builder__row{
  display:grid;
  grid-template-columns:repeat(var(--table-columns,2),minmax(6.5rem,1fr)) 1.6rem;
  gap:.3rem;
  align-items:center;
}

.widget-table-builder__rows{
  display:grid;
  gap:.3rem;
  margin-top:.3rem;
}

.widget-table-builder__column{
  display:flex;
  align-items:center;
  gap:.2rem;
}

.widget-table-builder__align{
  flex:1 1 auto;
  min-width:0;
  border:1px solid rgba(100,116,139,.3);
  border-radius:5px;
  background:var(--surface);
  padding:.16rem .24rem;
  font-size:.66rem;
}

.widget-table-builder__cell{
  width:100%;
  min-width:0;
  border:1px solid rgba(100,116,139,.32);
  border-radius:5px;
  padding:.26rem .4rem;
  font-size:.8rem;
}

/* The header row is row 0 of the same grid, so "has header" is a preview weight rather than a second region. */
.widget-table-builder__row.is-header .widget-table-builder__cell{
  background:color-mix(in srgb, var(--brass) 10%, var(--surface));
  border-color:rgba(165,129,53,.45);
  font-weight:700;
}

.widget-table-builder__drop{
  appearance:none;
  border:1px solid transparent;
  border-radius:5px;
  background:transparent;
  color:var(--slate-soft);
  font-size:.85rem;
  line-height:1;
  padding:.2rem;
  cursor:pointer;
}

.widget-table-builder__drop:hover{
  border-color:var(--bad-line);
  background:var(--bad-fill);
  color:var(--bad-ink);
}

.widget-table-builder__align:focus-visible,
.widget-table-builder__cell:focus-visible,
.widget-table-builder__drop:focus-visible,
.widget-separator-control__ornament:focus-visible{
  outline:2px solid rgba(165,129,53,.5);
  outline-offset:1px;
}

/* --- Separator: the home render -------------------------------------------------------------------------- */

/* --separator-color is set inline ONLY when the block stored a colour; otherwise the separator takes the
   theme's brass, so it follows a theme switch instead of freezing the hex that was active when authored. */
.content-block-separator{
  --separator-color:var(--brass);
  display:flex;
  align-items:center;
  gap:.85rem;
  margin:22px auto;
  color:var(--separator-color);
}

.content-block-separator--narrow{width:38%}
.content-block-separator--wide{width:78%}
.content-block-separator--full{width:100%}

/* The rules are pseudo-elements so the markup carries only what a reader needs: a role="separator" wrapper and,
   when the style asks for one, the ornament. */
.content-block-separator--line::before,
.content-block-separator--line::after,
.content-block-separator--line-ornament::before,
.content-block-separator--line-ornament::after{
  content:"";
  flex:1 1 auto;
  height:1px;
  background:currentColor;
  opacity:.55;
}

.content-block-separator__ornament{
  flex:0 0 auto;
  font-size:1.05rem;
  line-height:1;
  letter-spacing:.08em;
}

/* The ornament-only style is the glyph centred with no rule at all. */
.content-block-separator--ornament{
  justify-content:center;
}

/* --- Table: the home render ------------------------------------------------------------------------------ */

.content-block-table{
  width:100%;
  margin:18px 0;
  border-collapse:collapse;
  font-size:.92rem;
}

.content-block-table th,
.content-block-table td{
  padding:.5rem .7rem;
  border-bottom:1px solid var(--line-dark);
  vertical-align:top;
}

/* BT Phase 2: the table's two parts. It is the kind the ask did not name and the one that needs this most —
   its cells are typed by an author but the render arm carries no `Html.Raw` anywhere, deliberately, so the
   inline-span route the retired Stats suite used is closed to it (plan §2). */
.content-block-table thead th{
  border-bottom-color:var(--brass);
  color:var(--tp-header-color,var(--brass-soft));
  font-size:var(--tp-header-size,.72rem);
  font-weight:var(--tp-header-weight,700);
  letter-spacing:.14em;
  text-transform:var(--tp-header-case,uppercase);
  font-family:var(--tp-header-font);
  font-style:var(--tp-header-italic);
  text-decoration-line:var(--tp-header-underline);
  line-height:var(--tp-header-line);
}

/* THE BODY CELLS HAD NO RULE OF THEIR OWN AND THIS ONE PAINTS NOTHING. Their size comes from
   `.content-block-table` above and everything else is inherited, so every declaration here is a token with no
   fallback — which is invalid at computed-value time while nothing is set, and therefore computes to exactly
   the inherited value the cell already had. Collapse the tokens out of this rule and it is empty, which is
   the point: it exists so the `body` part has somewhere to land. */
.content-block-table tbody td{
  font-family:var(--tp-body-font);
  font-size:var(--tp-body-size);
  font-weight:var(--tp-body-weight);
  font-style:var(--tp-body-italic);
  text-decoration-line:var(--tp-body-underline);
  text-transform:var(--tp-body-case);
  color:var(--tp-body-color);
  line-height:var(--tp-body-line);
}

.content-block-table tbody tr:last-child th,
.content-block-table tbody tr:last-child td{
  border-bottom:0;
}

/* A table is the one block that can outgrow a narrow widget column, so it scrolls itself rather than pushing
   the page sideways. */
@media(max-width:760px){
  .content-block-table{
    display:block;
    overflow-x:auto;
  }
  .content-block-separator--narrow{width:60%}
}

/* =====================================================================
   FB Phase 1 — Admin › Stripe payments (docs/festive-board-booking-plan.md §7,
   mockup screen 2). Admin chrome uses fixed Heritage-idiom literals, the
   same rule the .tm-* theme page follows (theme plan D3): the admin must
   stay readable whichever theme an administrator has made active.

   NS Phase 1 (2026-08-02): these .sp-* rules are now the SHARED vocabulary for
   credential-holding settings pages, not Stripe's alone — /Admin/Email reuses
   them verbatim, which is what makes the two pages read as siblings (NS D3).
   Renaming the prefix would touch site.css, two views, admin-stripe.js and the
   E2E selectors for no behaviour change, so the prefix stays and this note
   carries the meaning instead. NS-only additions live under .sp-roles / .sp-tag
   at the end of this block.
   ===================================================================== */

.sp-section{padding:6px 28px 60px}

.sp-status-strip{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin:0 0 16px}
.sp-chip{font-size:.66rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;border-radius:999px;padding:4px 12px}
.sp-chip--test{color:var(--warn-ink);background:var(--warn-fill);border:1px solid var(--warn-line)}
.sp-chip--live{color:var(--ok-ink);background:var(--ok-fill);border:1px solid var(--ok-line)}
.sp-chip--none{color:var(--slate-soft);background:var(--parchment-2);border:1px solid var(--line-light)}
.sp-chip--on{color:var(--ok-ink);background:var(--ok-fill);border:1px solid var(--ok-line)}
.sp-chip--off{color:var(--slate-soft);background:var(--surface);border:1px solid rgba(148,163,184,.4)}
.sp-lock{font-size:.66rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--claret);border:1px solid rgba(111,36,53,.35);border-radius:999px;padding:4px 12px;background:rgba(111,36,53,.06)}
.sp-updated{font-size:.76rem;color:var(--slate-soft);margin-left:auto}

.sp-alert{
  background:var(--bad-fill);border:1px solid var(--bad-line);border-radius:10px;
  padding:12px 16px;margin:0 0 16px;font-size:.86rem;color:var(--bad-ink);line-height:1.5;
}

.sp-grid{display:grid;grid-template-columns:minmax(0,1.4fr) minmax(0,1fr);gap:20px;align-items:start}
.sp-aside{display:grid;gap:20px}

.sp-card{background:var(--surface);border:1px solid rgba(148,163,184,.25);border-radius:10px;overflow:hidden}
.sp-card__head{padding:14px 16px;border-bottom:1px solid rgba(148,163,184,.18)}
.sp-card__head h2{font-size:1.1rem;margin:0;color:var(--heading)}
.sp-card__title--sm{font-size:.98rem}
.sp-card__head p{margin:5px 0 0;font-size:.8rem;color:var(--slate-soft);line-height:1.5}
.sp-card__body{padding:16px}

.sp-form{display:grid;gap:18px}
.sp-two{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.sp-two--actions{align-items:end}
.sp-field{display:grid;gap:5px;min-width:0}
.sp-label{font-size:.78rem;font-weight:600;color:var(--slate-soft)}
.sp-input{
  width:100%;box-sizing:border-box;min-width:0;
  border:1px solid rgba(148,163,184,.4);border-radius:6px;
  padding:8px 10px;font-size:.9rem;color:var(--heading);background:var(--surface);
}
.sp-input--key{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.82rem}
.sp-input::placeholder{color:var(--slate-soft);letter-spacing:.02em}
.sp-hint{margin:0;font-size:.74rem;color:var(--slate-soft);line-height:1.5}
.sp-hint code{background:var(--parchment-2);color:var(--heading);border-radius:4px;padding:1px 5px;font-size:.72rem}

/* The enable switch is the checkbox ITSELF, restyled with appearance:none — not a visually hidden
   input behind a decorative track. The first shape failed its own browser check: a 1px transparent
   input sitting under an opaque span is an element no pointer can reach, and while a wrapping label
   rescues a mouse user, "the real control is unclickable" is the wrong thing to ship on a payments
   page. Here the thing you see IS the control, so hit target, focus ring and keyboard all follow the
   native element. */
.sp-switch{display:flex;align-items:center;gap:12px;cursor:pointer}
.sp-switch input{
  appearance:none;-webkit-appearance:none;margin:0;flex:none;border:none;cursor:pointer;
  position:relative;width:46px;height:26px;border-radius:99px;
  background:#CFCABA;transition:background .12s;
}
.sp-switch input::after{
  content:"";position:absolute;top:3px;left:3px;width:20px;height:20px;border-radius:50%;
  background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.3);transition:transform .12s;
}
.sp-switch input:checked{background:#4E7A5A}
.sp-switch input:checked::after{transform:translateX(20px)}
.sp-switch input:focus-visible{outline:2px solid #C7A24A;outline-offset:2px}
.sp-switch__text{font-size:.9rem;color:var(--heading);font-weight:600}
.sp-switch__text small{display:block;font-weight:400;font-size:.78rem;color:var(--slate-soft);line-height:1.5;margin-top:2px}

.sp-actions{display:flex;gap:10px;justify-content:flex-end;flex-wrap:wrap}
.sp-btn{border-radius:8px;padding:9px 16px;font-size:.86rem;font-weight:600;cursor:pointer}
.sp-btn--primary{background:var(--ink);color:var(--on-dark);border:none}
.sp-btn--ghost{background:var(--surface);border:1px solid rgba(148,163,184,.4);color:var(--slate)}
.sp-btn--ghost:hover{border-color:#C7A24A}

.sp-testline{display:grid;gap:8px;border-top:1px dashed rgba(148,163,184,.4);padding-top:16px}
.sp-test{display:flex;gap:9px;align-items:flex-start;margin:0;font-size:.84rem;border-radius:9px;padding:9px 14px;line-height:1.5}
.sp-test--ok{color:var(--ok-ink);background:var(--ok-fill);border:1px solid var(--ok-line)}
.sp-test--warn{color:var(--warn-ink);background:var(--warn-fill);border:1px solid var(--warn-line)}
.sp-test--bad{color:var(--bad-ink);background:var(--bad-fill);border:1px solid var(--bad-line)}

.sp-hookurl{
  display:flex;gap:10px;align-items:center;background:#1B1913;border-radius:9px;
  padding:10px 14px;color:#E6D6A8;font-size:.76rem;
}
.sp-hookurl code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;color:inherit;overflow-x:auto;white-space:nowrap;min-width:0}
.sp-copy{
  margin-left:auto;flex:none;background:rgba(237,228,208,.12);border:1px solid rgba(237,228,208,.25);
  color:#EDE4D0;border-radius:6px;font-size:.72rem;padding:4px 10px;cursor:pointer;
}
.sp-copy:hover{background:rgba(237,228,208,.2)}

.sp-steps{margin:0;padding:0;list-style:none;counter-reset:sp-step}
.sp-steps li{
  counter-increment:sp-step;display:flex;gap:12px;padding:11px 0;
  border-top:1px solid rgba(148,163,184,.18);font-size:.84rem;color:var(--slate);line-height:1.5;
}
.sp-steps li:first-child{border-top:none}
.sp-steps li::before{
  content:counter(sp-step);flex:none;width:22px;height:22px;border-radius:50%;
  background:var(--warn-fill);border:1px solid var(--warn-line);
  display:flex;align-items:center;justify-content:center;font-size:.7rem;font-weight:700;color:var(--warn-ink);
}
.sp-steps code{background:var(--parchment-2);color:var(--heading);border-radius:4px;padding:1px 5px;font-size:.76rem}

/* NS Phase 3 — /Admin/Email/Log. The filter row, the message table and the expanded row. */
.sp-filters{display:flex;flex-wrap:wrap;gap:12px;align-items:flex-end;margin:0 0 16px}
.sp-filters .sp-field{margin:0;flex:1 1 12rem;min-width:0}
.sp-filters .sp-field:last-child{flex:0 0 auto}

/* =====================================================================
   ITEM 64 SLICE 4 — .lst-find, THE FILTER ROW FOR A PAGED LIST.

   ONE CONTROL, SIX SCREENS, and the argument is the one slices 2 and 3 already made twice. The pager is
   `.history-pager` on thirty screens rather than a new `.pager`; the sort bar is `.sp-sortbar` on a dozen.
   The first cut of this slice wrote `hist-find`, `meetings-find`, `vst-find`, `cnv-find`, `rr-find` and
   `rec-seasons` — EIGHTEEN classes across six prefixes for one flex row with a label, a control and a
   button — which is thirty pages' worth of the divergence D7 exists to stop, and six more unmeasured
   controls arriving in the middle of item 44's measurement.

   IT IS THE MEMBERS' AND PUBLIC SIDE'S ROW, and it is deliberately not `.history-size-form`. That class is
   the admin desk's: it sits in the admin chrome section, whose stated rule is fixed Heritage literals that
   do NOT follow the lodge's theme (the .tm-* page's D3). A public page must follow the theme, so this is
   written on the same tokens the members' area uses everywhere else.

   THE 44px FLOOR IS ON THE BUTTON AND THE LINKS, WRITTEN WHEN THE CONTROL WAS BUILT — `.sp-sort`'s own
   reasoning, and item 48 slice 1's. Item 44 is still measuring the controls already on this site; six new
   unmeasured ones arriving mid-measurement is the roster growing while it is being taken. The INPUT and
   the SELECT take it too, because a 30px search box beside a 44px button is the misalignment item 64
   slice 3 shipped on six admin pages and a screenshot had to find.

   `min-width:0` ON THE INPUT IS NOT DECORATION. A flex item's default `min-width` is `auto`, which is its
   content, so an input refuses to shrink below its size attribute and pushes the row wider than the
   screen however much wrapping is allowed above it — /Admin/Mentoring overflowed 360px BY 188px for
   exactly this reason eight hours ago. */
.lst-find{
  display:flex;
  flex-wrap:wrap;
  align-items:center;
  gap:.5rem;
  margin:0 0 1rem;
}

.lst-find label{
  font-size:.78rem;
  color:var(--slate-soft);
  font-weight:600;
  letter-spacing:.03em;
}

.lst-find input[type="search"],
.lst-find input[type="text"]{
  min-width:0;
  flex:1 1 11rem;
  min-height:44px;
  padding:.4rem .7rem;
  border:1px solid var(--line-light);
  border-radius:8px;
  background:var(--surface);
  color:var(--heading);
  font-family:inherit;
  font-size:.9rem;
}

/* `height` AND `overflow:hidden`, NEITHER OF WHICH IS min-height, AND THE FIRST DRAFT OF THIS RULE HAD
   min-height ALONE. Both halves are item 61's measurement from an iPhone 17 Pro Max, recorded ten
   thousand lines below this one, and both were about to be paid for again by a control written eight
   hours later:

   HEIGHT. WebKit lays out a menulist <select> from its own metrics and IGNORES min-height — measured
   21px against an authored 44. So the floor this rule exists to keep would have been kept in Chromium
   and absent on the phone the owner actually reads on, which is the whole shape of that finding.

   OVERFLOW, AND IT IS THE HALF NOTHING COULD HAVE CAUGHT. A <select> in WebKit keeps a scrollable
   overflow the width of its LONGEST OPTION and that overflow propagates to the document: measured
   box=390, scrollWidth=507, the page scrolling sideways by 118px while every element's box was
   correctly inside the viewport. `.lst-find` puts a select on /Meetings/Index whose options are masonic
   body names — "Hull Old Grammarians' Chapter No. 5129" — and on /Members/Visits whose options are
   season labels. The public meetings page would have scrolled sideways on a phone with nothing drawn
   past the edge and nothing to measure.

   "Both are needed and neither is sufficient" is that block's own sentence: height alone leaves the page
   at 508, overflow:hidden alone leaves the control at 21px. Written at EVERY width rather than in a
   phone media query, because this control is new and the floor is going on it from birth — .sp-sort's
   reasoning, and the reason item 44 does not inherit six more unmeasured controls from this slice. */
.lst-find select{
  height:44px;
  overflow:hidden;
  flex:0 1 auto;
  max-width:100%;
  padding:.4rem .6rem;
  border:1px solid var(--line-light);
  border-radius:8px;
  background:var(--surface);
  color:var(--heading);
  font-family:inherit;
  font-size:.9rem;
}

.lst-find__go{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  min-height:44px;
  flex-shrink:0;
  padding:.4rem 1rem;
  border:1px solid var(--line-light);
  border-radius:8px;
  background:var(--surface);
  color:var(--heading);
  font-family:inherit;
  font-size:.86rem;
  font-weight:600;
  white-space:nowrap;
  cursor:pointer;
}

.lst-find__go:hover{ border-color:var(--brass); }

/* The two-or-more-link toggle: "Everything / Unread only", "Every season / 2024-25 / 2023-24", and the
   "Clear" beside a search box. Links rather than a form, which is the members' area's rule — it has no
   script, and a state that is a URL is a state somebody can bookmark. */
.lst-find__modes{
  display:flex;
  flex-wrap:wrap;
  align-items:center;
  gap:.4rem;
  margin:0 0 1rem;
}

.lst-find__label{
  font-size:.78rem;
  color:var(--slate-soft);
  font-weight:600;
  letter-spacing:.03em;
  margin-right:.2rem;
}

.lst-find__mode{
  display:inline-flex;
  align-items:center;
  min-height:44px;
  padding:.3rem .8rem;
  border:1px solid var(--line-light);
  border-radius:999px;
  background:var(--surface);
  color:var(--slate);
  font-size:.82rem;
  font-weight:600;
  text-decoration:none;
}

.lst-find__mode:hover{ border-color:var(--brass); color:var(--heading); }

.lst-find__mode--on{
  border-color:var(--brass);
  background:var(--parchment);
  color:var(--heading);
}

/* "14 of 40 are hidden by this search." A page-level fact on a paged screen — §5C.7 — so it is drawn at
   reading weight rather than as fine print: a sentence nobody notices is the closed drawer again. */
.lst-find__note{
  margin:0 0 1rem;
  font-size:.86rem;
  color:var(--slate);
}

.sp-tablewrap{overflow-x:auto}
.sp-table{width:100%;border-collapse:collapse;font-size:.84rem;min-width:44rem}
.sp-table th{
  text-align:left;font-size:.68rem;letter-spacing:.12em;text-transform:uppercase;font-weight:700;
  color:var(--slate-soft);padding:8px 10px;border-bottom:1px solid rgba(148,163,184,.35);white-space:nowrap;
}
.sp-table td{padding:.7rem .65rem;border-bottom:1px solid rgba(148,163,184,.18);vertical-align:top}
.sp-table tr:last-child td{border-bottom:none}

/* Item 64 slice 3 (D11/D12) — a sortable column header. ONE control across a dozen admin lists, drawn by
   Shared/_SortHeader.cshtml.

   THE 44px FLOOR IS HERE RATHER THAN IN ITEM 44'S SWEEP, and that is deliberate. Item 44 is still in the
   queue and its job is the controls already on the site; this one did not exist an hour ago. Shipping a
   dozen copies of an unmeasured control into the middle of that measurement is how the roster it is
   measuring against grows while it is being taken. `.sp-table th` has 8px of padding and a .68rem font,
   which is a 27px row — the link is the control, so the floor goes on the link.

   The negative margin cancels the cell's own padding so the touch target fills the header rather than
   sitting inside it with a dead 10px border of unpressable cell around three sides. */
.sp-table th:has(.sp-sort){padding:0}
.sp-sort{
  display:inline-flex;align-items:center;gap:.3rem;
  min-height:44px;padding:0 10px;
  color:inherit;text-decoration:none;
}
.sp-sort:hover,.sp-sort:focus-visible{color:var(--brass-ink)}

/* THE CURRENT COLUMN IS MARKED WITHOUT CHANGING ITS COLOUR, and that is a correction rather than a
   preference. The first cut was `color:var(--ink)`, which reads as "the strongest text" and is not what
   the token means: --ink is the DARKEST ink, so on Onyx it resolves to #0b0c0f and painted 1.08:1 on that
   theme's #15161a table. ThemeContrastSweepPlaywrightTests caught it, which is the third time this shape
   of fault has been paid for on this site.

   Inheriting the header's own colour is theme-proof by construction: whatever `.sp-table th` is readable
   in, this is. The mark is the arrow, the underline and `aria-sort` - none of which is a colour. */
.sp-sort--on{text-decoration:underline;text-underline-offset:3px;text-decoration-thickness:2px}
.sp-sort__arrow{font-size:.9em;line-height:1}

/* The same control for a list that is not a table (Shared/_SortBar.cshtml). Same 44px floor, because a
   sort link on a card stack is pressed by the same thumb as one in a table header. */
.sp-sortbar{display:flex;flex-wrap:wrap;align-items:center;gap:.15rem .5rem;margin:0 0 12px}
.sp-sortbar__label{
  font-size:.68rem;letter-spacing:.12em;text-transform:uppercase;font-weight:700;color:var(--slate-soft);
}
.sp-sortbar .sp-sort{font-size:.82rem}

/* Item 28 — the bulk-retry bar on /Admin/Email/Log. Four classes, every value taken from a token this
   screen already paints with, so the control introduces no new colour to the eleven palettes.
   .sp-bulk__live is the press's own answer (Q4/Q6) and uses the ok pair rather than .sp-alert's bad
   pair: a batch that queued forty and held eight back is a report, not a failure. */
.sp-bulk{
  display:flex;flex-wrap:wrap;gap:10px;align-items:center;
  background:var(--parchment-2);border:1px solid var(--line-light);border-radius:8px;
  padding:10px 14px;margin:0 0 14px;
}
.sp-bulk__count{font-size:.8rem;color:var(--slate-soft);margin:0;font-variant-numeric:tabular-nums}
.sp-bulk__sep{font-size:.76rem;color:var(--slate-soft)}
.sp-bulk__live{
  margin:0 0 14px;font-size:.82rem;line-height:1.5;border-radius:8px;padding:9px 14px;
  color:var(--ok-ink);background:var(--ok-fill);border:1px solid var(--ok-line);
}
.sp-selall{display:inline-flex;align-items:center;gap:.45rem;font-size:.8rem;color:var(--slate);white-space:nowrap;cursor:pointer}
.sp-chk{width:1.05rem;height:1.05rem;accent-color:var(--brass-ink);cursor:pointer;margin:0}
.sp-table td.sp-pick{width:2.4rem}

.sp-detail{background:var(--surface);border:1px solid rgba(148,163,184,.28);border-radius:8px;padding:14px 16px}
.sp-detail h3{margin:16px 0 6px}
.sp-detail h3:first-of-type{margin-top:10px}
.sp-detail__actions{margin-top:14px}
.sp-detail__actions form{display:flex;flex-wrap:wrap;gap:8px}

/* NS Phase 3 — the members' dashboard email-preference toggle. Sits in the same slot an admin-list row
   normally puts a button in, so the card reads as one list rather than two kinds of row. */
.pref-toggle{display:inline-flex;align-items:center;gap:.5rem;font-size:.9rem;white-space:nowrap;cursor:pointer}
.pref-toggle input{width:1.05rem;height:1.05rem;accent-color:var(--brass, #8A6A2B);cursor:pointer}

/* The rendered text part, shown as sent. Wrapped rather than scrolled: a reset URL that runs off the
   right edge is the one line anybody actually needs to read here. */
.sp-pre{
  margin:0;padding:12px;background:var(--surface);border:1px solid rgba(148,163,184,.28);border-radius:6px;
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;font-size:.76rem;line-height:1.55;
  white-space:pre-wrap;overflow-wrap:anywhere;max-height:22rem;overflow-y:auto;
}

@media(max-width:980px){
  .sp-grid{grid-template-columns:1fr}
  .sp-two{grid-template-columns:1fr}
  .sp-actions{justify-content:flex-start}
  .sp-updated{margin-left:0;flex-basis:100%}
}

/* =====================================================================
   FB Phase 2 — the members' festive board booking page
   (docs/festive-board-booking-plan.md §6, mockup screen 1) and the admin
   meal details editor (§7, mockup screen 3).

   The members page is MEMBER-facing, so unlike the .sp-* admin block above
   it is built on the theme tokens: whichever theme is active has to look
   deliberate here. The .fb-details-* editor sits inside the existing .fb-*
   admin card and follows its neighbours' token usage for the same reason.
   ===================================================================== */

/* ---- meal hero facts (in .page-hero--light) ---- */
.fbm-facts{
  display:flex;flex-wrap:wrap;gap:1.5rem 2.5rem;margin:1.25rem 0 .25rem;
}
.fbm-fact{display:flex;flex-direction:column;gap:2px}
.fbm-fact strong{
  font-family:var(--serif);font-size:1.35rem;line-height:1.1;color:var(--heading);
  font-variant-numeric:tabular-nums;
}
.fbm-fact span{
  font-size:.7rem;letter-spacing:.12em;text-transform:uppercase;color:var(--brass-ink);font-weight:600;
}

/* ---- page-level errors ----
   Keyed off .validation-summary-errors, NOT :not(:empty): a valid ModelOnly summary still renders a
   <ul> with a hidden <li> in it, so an :empty test is false for it and the page grew an empty claret
   box above the form on every clean load. */
.fbm-errors.validation-summary-errors{
  background:rgba(111,36,53,.06);border:1px solid rgba(111,36,53,.3);border-radius:8px;
  padding:.9rem 1.25rem;margin:0 0 1.5rem;color:var(--claret);font-size:.92rem;
}
.fbm-errors ul{margin:0;padding-left:1.1rem}

/* =====================================================================
   ONE UPCOMING BOARD, REPEATED (user instruction, 2026-09-19). The page
   rendered exactly one board until now, so its date, price and cuisine
   lived in the page hero and its sidebar held the member's own booking
   history. Neither can be a page-level thing once there are several.

   padding:0 IS LOAD-BEARING, for .fbm-card's reason one screen down: this
   is a <section>, and the global `section{padding:74px 0}` would open
   ~90px of blank space above every board's heading.
 ===================================================================== */
.fbm-board{
  padding:0;
  display:grid;
  gap:1.25rem;
}

/* The separator is on the JOIN rather than on every board, so a page of
   one looks exactly as it did and a page of three reads as three. */
.fbm-board + .fbm-board{
  margin-top:2.5rem;
  padding-top:2.5rem;
  border-top:1px solid var(--line-light);
}

.fbm-board__title{
  font-family:var(--serif);font-size:1.6rem;line-height:1.15;margin:0;color:var(--heading);
}

/* The date inside the heading. Quieter than the name it follows, because the name is what the thing IS
   and the date is which one — but it is in the heading rather than under it, so that three boards called
   "Festive Board" are three different headings to anybody reading or listening. */
.fbm-board__when{
  font-family:var(--sans);font-size:1rem;font-weight:500;color:var(--slate-soft);white-space:nowrap;
}

.fbm-board__sub{
  margin:.35rem 0 0;font-size:.95rem;color:var(--slate-soft);line-height:1.65;max-width:60ch;
}

/* The member's own history, once, under every board. */
.fbm-past-card{
  margin-top:2.5rem;
}

.fbm-grid{
  display:grid;grid-template-columns:minmax(0,1.45fr) minmax(0,1fr);gap:2rem;align-items:start;
}
.fbm-main{display:grid;gap:1.5rem;min-width:0}
.fbm-aside{display:grid;gap:1.5rem;min-width:0}

/* padding:0 is load-bearing: these cards are <section> elements and the global `section{padding:74px 0}`
   rule would otherwise open ~90px of blank space above every card heading. */
.fbm-card{
  background:var(--surface);border:1px solid var(--line-light);border-radius:10px;overflow:hidden;padding:0;
}
.fbm-card--yours{border-left:4px solid var(--brass)}
.fbm-card__head{padding:1.1rem 1.35rem;border-bottom:1px solid var(--line-light)}
.fbm-card__body{padding:1.35rem}
.fbm-card__title{font-family:var(--serif);font-size:1.35rem;margin:0;color:var(--heading)}
/* The cards inside a board render their title as an h3; the rule is on the class, so it follows. */
.fbm-card__title--sm{font-size:1.05rem}
.fbm-card__sub{margin:.35rem 0 0;font-size:.86rem;color:var(--slate-soft);line-height:1.6}

/* ---- your booking ---- */
.fbm-yours__head{display:flex;justify-content:space-between;align-items:flex-start;gap:1rem;flex-wrap:wrap}
.fbm-dl{display:flex;flex-wrap:wrap;gap:1.5rem 2.5rem;margin:1.25rem 0 0}
.fbm-dl div{display:flex;flex-direction:column;gap:3px}
.fbm-dl dt{
  font-size:.7rem;letter-spacing:.1em;text-transform:uppercase;color:var(--slate-soft);font-weight:600;
}
.fbm-dl dd{margin:0;font-size:1.1rem;color:var(--heading);font-weight:600;font-variant-numeric:tabular-nums}
.fbm-yours__diet{margin:1rem 0 0;font-size:.9rem;color:var(--slate)}
.fbm-yours__foot{
  display:flex;justify-content:space-between;align-items:flex-end;gap:1.25rem;flex-wrap:wrap;
  margin-top:1.1rem;padding-top:1.1rem;border-top:1px dashed var(--line-light);
}
.fbm-yours__cancel{flex:none}
/* FB Phase 3: the state card can now carry two controls — "Pay now" resuming an unfinished card
   payment, and Cancel. They sit together at the foot, primary first, and stack on a phone. */
.fbm-yours__actions{display:flex;align-items:center;gap:.65rem;flex:none;flex-wrap:wrap}
.fbm-yours__actions form{margin:0}

/* A quiet secondary button for a LIGHT surface. .btn-ghost cannot be reused here: it sets
   `color: var(--on-dark) !important`, which is the near-white token for dark bands, so on this white
   card its label is invisible — the render-mapping trap the theme work already documented. */
.fbm-btn-quiet{
  display:inline-block;background:var(--surface);border:1px solid var(--line-light);
  color:var(--slate);border-radius:8px;padding:.55rem 1.1rem;font-size:.88rem;font-weight:600;
  font-family:inherit;cursor:pointer;text-decoration:none;
}
.fbm-btn-quiet:hover{border-color:var(--brass);color:var(--heading)}
.fbm-btn-quiet:focus-visible{outline:2px solid var(--brass);outline-offset:2px}

/* ---------------------------------------------------------------------
   ITEM 64 HALF A (slice 6) — HOW MANY PLACES
   ---------------------------------------------------------------------
   The one control this card never had. It sits between the facts and the names-and-choices panel,
   boxed off the surrounding white so a member scanning the card reads "here is a thing you can
   change" rather than three paragraphs of prose with a number in one of them.

   THE 44px FLOOR IS WRITTEN HERE, WHEN THE CONTROL IS BUILT, and not left for item 44 to find. That
   item is measuring the controls already on this site; a new unmeasured one arriving mid-measurement
   is the roster growing while it is being taken. Four things carry it: the number input, Update, and
   each Remove.

   `height` AND `overflow:hidden` ON THE NUMBER INPUT rather than `min-height` — item 61 measured from
   an iPhone that WebKit ignores `min-height` on a form control and that the control can then keep a
   scrollable overflow which propagates to the document. Both are needed and neither is sufficient, and
   the suite is Chromium so neither fault is visible to it.
   --------------------------------------------------------------------- */
.fbm-size{
  margin-top:1.15rem;padding:1.1rem 1.25rem;border-radius:10px;
  background:var(--parchment-2);border:1px solid var(--line-light);
}
.fbm-size__h{
  margin:0 0 .75rem;font-size:.78rem;font-weight:700;letter-spacing:.08em;
  text-transform:uppercase;color:var(--slate-soft);
}
.fbm-size__form{display:flex;align-items:center;gap:.65rem;flex-wrap:wrap;margin:0 0 .85rem}
.fbm-size__label{font-size:.88rem;font-weight:600;color:var(--heading)}
.fbm-size__n{
  width:5.5rem;height:44px;overflow:hidden;box-sizing:border-box;
  padding:0 .7rem;font-family:inherit;font-size:1rem;font-variant-numeric:tabular-nums;
  color:var(--heading);background:var(--surface);
  border:1px solid var(--line-light);border-radius:8px;
}
.fbm-size__n:focus-visible{outline:2px solid var(--brass);outline-offset:2px}
.fbm-size__form .btn{min-height:44px;display:inline-flex;align-items:center}

/* The balance sentences sit on the white card rather than inside the boxed control, because they are
   a fact about the booking and not part of changing it. Tinted so the number is not lost in the prose
   under it, and `max-width` inherited from .fbm-hint keeps the measure readable. */
.fbm-size__owed{
  margin:1.1rem 0 0;padding:.75rem 1rem;border-radius:8px;
  background:var(--warn-fill);border:1px solid var(--warn-line);color:var(--warn-ink);
}
.fbm-size__k{margin:0 0 .4rem;font-size:.84rem;font-weight:600;color:var(--slate)}
.fbm-size__guests{list-style:none;margin:0 0 .85rem;padding:0;display:flex;flex-direction:column;gap:.45rem}
.fbm-size__guest{
  display:flex;align-items:center;justify-content:space-between;gap:.9rem;flex-wrap:wrap;
  padding:.35rem 0;border-bottom:1px solid var(--line-light);
}
.fbm-size__guest:last-child{border-bottom:none}
.fbm-size__guest form{margin:0}
.fbm-size__who{font-size:.92rem;color:var(--heading);font-weight:600}
.fbm-size__why{font-size:.8rem;color:var(--slate-soft);font-style:italic}
.fbm-size__guest .fbm-btn-quiet{min-height:44px;display:inline-flex;align-items:center}

.fbm-chip{
  font-size:.66rem;font-weight:700;letter-spacing:.09em;text-transform:uppercase;
  border-radius:999px;padding:4px 11px;white-space:nowrap;
}
.fbm-chip--pending{color:var(--warn-ink);background:var(--warn-fill);border:1px solid var(--warn-line)}
.fbm-chip--paid{color:var(--ok-ink);background:var(--ok-fill);border:1px solid var(--ok-line)}
.fbm-chip--refunded{color:var(--slate-soft);background:var(--parchment-2);border:1px solid var(--line-light)}
.fbm-chip--cancelled{color:var(--bad-ink);background:var(--bad-fill);border:1px solid var(--bad-line)}

.fbm-hint{margin:0;font-size:.84rem;color:var(--slate-soft);line-height:1.6;max-width:62ch}
.fbm-note{
  margin:0;font-size:.88rem;color:var(--slate-soft);line-height:1.6;
  background:var(--parchment-2);border-radius:8px;padding:.85rem 1.15rem;
}

/* ---- closed / full ---- */
.fbm-closed{
  background:var(--parchment-2);border:1px solid var(--line-light);border-left:4px solid var(--claret);
  border-radius:10px;padding:1.5rem 1.75rem;
}
/* h2 AND h3. Each board now owns the h2 — its own name — so the cards inside it descend a level, and
   HeadingOutlinePlaywrightTests is the guard that made that necessary rather than optional. The old
   selector is kept because .fbm-closed is not only rendered inside a board. */
.fbm-closed h2,
.fbm-closed h3{font-family:var(--serif);font-size:1.35rem;margin:0 0 .5rem;color:var(--heading)}
.fbm-closed p{margin:0;font-size:.95rem;color:var(--slate-soft);line-height:1.65;max-width:60ch}

/* ---- the booking form ---- */
.fbm-form{display:grid;gap:1.4rem}
.fbm-field{display:grid;gap:.4rem;min-width:0}
.fbm-field label,
.fbm-field__legend{font-size:.82rem;font-weight:600;color:var(--slate)}
.fbm-field label .hint,
.fbm-field__legend .hint{font-weight:400;color:var(--slate-soft)}
.fbm-field input[type=number],
.fbm-field input[type=text],
.fbm-field textarea{
  width:100%;box-sizing:border-box;border:1px solid var(--line-light);border-radius:6px;
  padding:.55rem .7rem;font-size:.95rem;font-family:inherit;color:var(--heading);background:var(--surface);
}
.fbm-field input:focus-visible,
.fbm-field textarea:focus-visible{outline:2px solid var(--brass);outline-offset:1px}
.fbm-num{max-width:8rem;font-variant-numeric:tabular-nums}

.fbm-pay{display:grid;grid-template-columns:1fr 1fr;gap:.75rem}
.fbm-pay--single{grid-template-columns:1fr;background:var(--parchment-2);border-radius:8px;padding:.9rem 1.15rem}
/* Radio beside its label, not stranded above it. FB Phase 2 authored this rule but only ever rendered
   the single-method variant, so the two-up group was first seen when Phase 3 turned card payments on —
   and a control centred over its own heading reads as a decoration rather than as the thing you press. */
.fbm-pay__opt{
  display:grid;grid-template-columns:auto 1fr;column-gap:.6rem;row-gap:3px;align-items:start;
  border:1px solid var(--line-light);border-radius:8px;padding:.8rem 1rem;cursor:pointer;
}
.fbm-pay__opt:has(input:checked){border-color:var(--brass);background:var(--parchment-2)}
.fbm-pay__opt input{margin:.2rem 0 0;grid-column:1;grid-row:1}
.fbm-pay__opt .fbm-pay__name,.fbm-pay__opt .fbm-pay__detail{grid-column:2}
.fbm-pay__name{margin:0;font-size:.95rem;font-weight:600;color:var(--heading)}
.fbm-pay__detail{margin:0;font-size:.82rem;color:var(--slate-soft);line-height:1.55}

.fbm-total{
  display:flex;justify-content:space-between;align-items:center;gap:1.25rem;flex-wrap:wrap;
  border-top:1px solid var(--line-light);padding-top:1.25rem;
}
.fbm-total__amt{display:flex;flex-direction:column;gap:2px}
.fbm-total__amt small{font-size:.76rem;color:var(--slate-soft)}
.fbm-total__amt span{
  font-family:var(--serif);font-size:1.75rem;line-height:1;color:var(--heading);font-variant-numeric:tabular-nums;
}

/* ---- aside ---- */
.fbm-docs{display:grid;grid-template-columns:1fr 1fr;gap:.85rem}
.fbm-docs figure{margin:0}
.fbm-docs img{width:100%;height:auto;border-radius:6px;border:1px solid var(--line-light);display:block}
.fbm-docs figcaption{margin-top:.35rem;font-size:.76rem;color:var(--slate-soft)}
/* Item 30 Q8, answered (a). The two documents were 149.6px wide each on a 390px phone, 14.6% of the
   flyer's own 1024px, and their captions sat 14.8px out of line because the flyer and the menu have
   different aspect ratios. One column below 900px gives each about 356px and puts the captions in
   separate rows, where they cannot disagree. The pair is still right on a desk, where the aside is
   narrow - see the 900px block below, which no longer re-states the two columns. */
.fbm-doc{
  display:block;width:100%;padding:0;border:0;background:none;cursor:pointer;font-family:inherit;
  text-align:left;border-radius:6px;
}
.fbm-doc:focus-visible{outline:2px solid var(--brass);outline-offset:3px}
.fbm-doc-cap{
  display:flex;align-items:center;justify-content:space-between;gap:.5rem;
  margin-top:.45rem;font-size:.8rem;color:var(--slate-soft);
}
.fbm-doc-cap b{color:var(--heading);font-weight:600}
.fbm-doc-cap em{
  font-style:normal;font-size:.72rem;color:var(--brass-ink);font-weight:700;letter-spacing:.06em;
  text-transform:uppercase;
}
.fbm-past{list-style:none;margin:0;padding:0;display:grid;gap:.6rem}
.fbm-past li{
  display:flex;justify-content:space-between;align-items:center;gap:.75rem;
  font-size:.86rem;color:var(--slate);padding-bottom:.6rem;border-bottom:1px solid var(--line-light);
}
.fbm-past li:last-child{border-bottom:none;padding-bottom:0}

@media(max-width:900px){
  .fbm-grid{grid-template-columns:1fr}
  .fbm-pay{grid-template-columns:1fr}
  /* Item 30 Q8: this rule used to re-state `1fr 1fr`, which is why a phone got two postage stamps. */
  .fbm-docs{grid-template-columns:1fr;gap:1.1rem}
  .fbm-yours__foot{flex-direction:column;align-items:stretch}
  .fbm-yours__actions{flex-direction:column;align-items:stretch}
  .fbm-yours__actions .btn,.fbm-yours__actions .fbm-btn-quiet{width:100%;text-align:center}
  /* Item 64 slice 6. The label, the box and the button each take the width rather than wrapping into
     a ragged three-line row; the guest rows keep their name and Remove on one line, because a name and
     a short verb fit at 390px and stacking them would double the list's height for nothing. */
  .fbm-size__form{flex-direction:column;align-items:stretch}
  .fbm-size__n{width:100%}
  .fbm-size__form .btn{width:100%;justify-content:center}
  .fbm-facts{gap:1rem 1.75rem}
}

/* ---------------------------------------------------------------------
   Admin › Festive Board — the meal details editor on each board card.
   --------------------------------------------------------------------- */

.fb-details{
  border-top:1px solid var(--line-light);
  margin-top:1.1rem;
  padding-top:.25rem;
}
.fb-details__summary{
  display:flex;align-items:center;gap:1rem;flex-wrap:wrap;
  cursor:pointer;list-style:none;padding:.75rem 0;
}
.fb-details__summary::-webkit-details-marker{display:none}
.fb-details__summary::before{
  content:"▸";color:var(--brass-ink);font-size:.8rem;flex:none;transition:transform .12s;
}
.fb-details[open] > .fb-details__summary::before{transform:rotate(90deg)}
.fb-details__summary:focus-visible{outline:2px solid var(--brass);outline-offset:2px;border-radius:4px}
.fb-details__summary-label{font-size:.9rem;font-weight:600;color:var(--heading)}
.fb-details__facts{display:flex;gap:.5rem;flex-wrap:wrap;margin-left:auto}
.fb-details__fact{
  font-size:.74rem;color:var(--slate-soft);background:var(--parchment-2);
  border:1px solid var(--line-light);border-radius:999px;padding:3px 10px;white-space:nowrap;
}
.fb-details__fact--warn{color:var(--warn-ink);background:var(--warn-fill);border-color:var(--warn-line);font-weight:600}

.fb-details__body{padding:0 0 .5rem}
.fb-details__note{
  margin:0 0 1rem;font-size:.84rem;line-height:1.6;color:var(--slate-soft);
  background:var(--parchment-2);border-radius:8px;padding:.75rem 1rem;
}
.fb-details__note--warn{
  color:var(--warn-ink);background:var(--warn-fill);border:1px solid var(--warn-line);
}
.fb-details__grid{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:1rem;
}
.fb-details__field{display:grid;gap:.3rem;min-width:0;align-content:start}
.fb-details__field--wide{grid-column:1/-1}
.fb-details__field label{font-size:.78rem;font-weight:600;color:var(--slate-soft)}
.fb-details__opt{font-weight:400}
.fb-details__field input,
.fb-details__field textarea{
  width:100%;box-sizing:border-box;border:1px solid var(--line-light);border-radius:6px;
  padding:.5rem .65rem;font-size:.9rem;font-family:inherit;color:var(--heading);background:var(--surface);
}
.fb-details__field input:focus-visible,
.fb-details__field textarea:focus-visible{outline:2px solid var(--brass);outline-offset:1px}
.fb-details__num{font-variant-numeric:tabular-nums}
.fb-details__hint{margin:0;font-size:.72rem;color:var(--slate-soft);line-height:1.5}
.fb-details__err{margin:0;font-size:.75rem;color:var(--claret);font-weight:600}
.fb-details__actions{display:flex;justify-content:flex-end;margin-top:1.1rem}

/* ---- "When this board is" ----
   Its own top border and margin, matching .fb-details, so the card reads as three stacked sections
   (when / what it costs / what it offers) rather than as a form floating under the heading.

   The FIELDS are .fb-details__field on purpose and there is no second vocabulary here: item 41's
   `@media(max-width:700px)` block already floors `.fb-details__field input` at 44px, and it selects by
   class rather than by descent, so those rules reach these two inputs wherever they are drawn. */
.fb-when{
  border-top:1px solid var(--line-light);
  margin-top:1.1rem;
  padding-top:1rem;
}
/* The button is laid out as a field so it sits on the grid's baseline beside the two inputs instead of
   stretching to the full height of a grid row. `align-content:end` puts it level with the boxes, whose
   labels are a line taller than nothing. */
.fb-when__act{align-content:end}
.fb-when__act .btn{width:100%;min-height:44px}
.fb-when__warn{margin:1rem 0 0}

@media(max-width:700px){
  .fb-details__facts{margin-left:0;flex-basis:100%}
  .fb-details__actions{justify-content:stretch}
  .fb-details__actions .btn{width:100%}
}

/* =====================================================================
   THE ADMIN ROSTER FAMILY (.fbr-*).

   RS Phase 4 note, read this first: despite the prefix, .fbr-* is no
   longer festive-board-only. Admin › Raffle tickets is the same object -
   a picker, a totals strip, and a table of money with per-row actions -
   and it uses these classes rather than a second copy of them under a
   different prefix. The prefix is kept because renaming a family across
   two pages and 100 lines of CSS to gain a letter is churn, not clarity.
   If you change a rule here, LOOK AT BOTH PAGES:
     * Pages/Admin/FestiveBoard/Bookings.cshtml (and Tonight.cshtml)
     * Pages/Admin/RaffleTickets.cshtml

   FB Phase 4 - Admin › Festive Board › Bookings (the roster) and
   Admin › Festive Board › On the night (docs/festive-board-booking-plan.md
   §7, mockup screens 3 and 4).

   These follow their .fb-* NEIGHBOURS and use theme tokens, not the fixed
   Heritage literals the .sp-* and .tm-* pages use: they hang off the festive
   board card page, whose .fb-details editor is already token-built, and a
   family that switches convention halfway is a family nobody can edit
   safely. Tokens here are chosen per [[theme-render-mapping-tokens]] - no
   --on-dark on a light surface, and no .btn-ghost on a card (it forces the
   near-white token and disappears).

   SCC Phase 7m: ".sp-* and .tm-*" is spelt out because writing the two
   prefixes as a pair separated by a slash puts a star-slash inside this
   comment, which CLOSES it — everything from there to the line of equals
   signs became garbage, error recovery swallowed .fbr-section, and its
   `padding:0 0 4rem` was dropped. Build record §28.
   ===================================================================== */

.fbr-section{padding:0 0 4rem}

.fbr-toolbar{
  display:flex;align-items:center;gap:.75rem;flex-wrap:wrap;margin:1.5rem 0 1.25rem;
}
.fbr-toolbar__right{display:flex;gap:.5rem;flex-wrap:wrap;margin-left:auto}
.fbr-picker{display:flex;align-items:center;gap:.5rem}
.fbr-picker label{font-size:.78rem;font-weight:600;color:var(--slate-soft)}
.fbr-picker select{
  border:1px solid var(--line-light);border-radius:6px;background:var(--surface);color:var(--heading);
  padding:.45rem .6rem;font-size:.86rem;font-family:inherit;max-width:min(22rem,60vw);
}

/* ---- the status filter and name box (item 30, Q1 and Q2 answered) ----
   Deliberately inside the .fbr-* family and on THEME TOKENS rather than adopting the .sp-* filter block:
   /Admin/RaffleTickets is the same roster with the same four status chips and no filter either, so this
   row has a second customer already and must be readable in every palette. */
.fbr-filter{display:flex;align-items:flex-end;gap:.75rem;flex-wrap:wrap;margin:0 0 1rem}
.fbr-filter__f{display:grid;gap:4px;min-width:0}
.fbr-filter__f label{
  font-size:.7rem;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--slate-soft);
}
.fbr-filter select,.fbr-filter input{
  border:1px solid var(--line-light);border-radius:6px;background:var(--surface);color:var(--heading);
  padding:.45rem .6rem;font-size:.86rem;font-family:inherit;
}
.fbr-filter select{min-width:min(20rem,70vw)}
.fbr-filter input{min-width:min(15rem,70vw)}
.fbr-filter select:focus-visible,.fbr-filter input:focus-visible{outline:2px solid var(--brass);outline-offset:2px}
/* The count is the sentence that stops the default lying about the database. It is not decoration. */
.fbr-filter__count{margin:0;padding-bottom:.5rem;font-size:.76rem;color:var(--slate-soft)}
.fbr-filter__count b{color:var(--heading);font-variant-numeric:tabular-nums}

@media(max-width:700px){
  .fbr-filter{gap:.6rem}
  .fbr-filter__f{flex:1 1 100%}
  .fbr-filter select,.fbr-filter input{width:100%;min-width:0}
  .fbr-filter__count{padding-bottom:0}
}

/* ---- totals strip ---- */
.fbr-totals{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(9.5rem,1fr));gap:.75rem;margin:0 0 1.25rem;
}
.fbr-tot{
  background:var(--surface);border:1px solid var(--line-light);border-radius:10px;padding:.8rem .95rem;
}
.fbr-tot--flag{border-color:rgba(154,106,31,.45);background:rgba(154,106,31,.07)}
.fbr-tot__k{
  margin:0;font-size:.68rem;font-weight:700;letter-spacing:.09em;text-transform:uppercase;
  color:var(--slate-soft);
}
.fbr-tot__v{
  margin:.3rem 0 0;font-size:1.28rem;font-weight:700;color:var(--heading);
  font-variant-numeric:tabular-nums;line-height:1.2;
}
.fbr-tot__v small{font-size:.72rem;font-weight:500;color:var(--slate-soft);margin-left:.3rem}

.fbr-note{
  margin:0 0 1.25rem;font-size:.82rem;line-height:1.6;color:var(--slate-soft);
  background:var(--parchment-2);border-radius:8px;padding:.75rem 1rem;
}
.fbr-note--warn{color:var(--warn-ink);background:var(--warn-fill);border:1px solid var(--warn-line)}

/* ---- the table ----
   A roster has seven columns and the last of them holds buttons, so on a narrow
   viewport it scrolls ITSELF rather than pushing the page sideways - the same
   rule .content-block-table follows. */
.fbr-card{
  background:var(--surface);border:1px solid var(--line-light);border-radius:10px;
  overflow:hidden;margin:0 0 1rem;
}
.fbr-scroll{overflow-x:auto}
.fbr-table{width:100%;border-collapse:collapse;font-size:.86rem;min-width:46rem}
.fbr-table th{
  text-align:left;font-size:.68rem;font-weight:700;letter-spacing:.09em;text-transform:uppercase;
  color:var(--slate-soft);padding:.7rem .85rem;border-bottom:1px solid var(--line-light);
  background:var(--parchment-2);white-space:nowrap;
}
.fbr-table td{padding:.7rem .85rem;border-bottom:1px solid var(--line-light);vertical-align:top}
.fbr-table tr:last-child td{border-bottom:none}
.fbr-num{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}
.fbr-row--history{opacity:.6}
.fbr-name{display:block;font-weight:600;color:var(--heading)}
.fbr-sub{display:block;font-size:.72rem;color:var(--slate-soft);line-height:1.5}
.fbr-sub--quote{font-style:italic}
.fbr-diet{font-size:.8rem;color:var(--slate-soft);max-width:14rem}
.fbr-tag{
  display:inline-block;font-size:.62rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
  color:var(--brass-ink);border:1px solid var(--line-light);border-radius:999px;padding:1px 7px;
  margin-left:.35rem;vertical-align:middle;
}

.fbr-chip{
  display:inline-block;font-size:.64rem;font-weight:700;letter-spacing:.09em;text-transform:uppercase;
  border-radius:999px;padding:3px 10px;white-space:nowrap;
}
.fbr-chip--paid{color:var(--ok-ink);background:var(--ok-fill);border:1px solid var(--ok-line)}
.fbr-chip--pending{color:var(--warn-ink);background:var(--warn-fill);border:1px solid var(--warn-line)}
.fbr-chip--refunded{color:var(--slate-soft);background:var(--parchment-2);border:1px solid var(--line-light)}
.fbr-chip--cancelled{color:var(--bad-ink);background:var(--bad-fill);border:1px solid var(--bad-line)}

.fbr-acts{display:flex;gap:.4rem;flex-wrap:wrap;align-items:center}
.fbr-acts__none{color:var(--slate-soft)}
.fbr-btn{
  border:1px solid var(--line-light);background:var(--surface);color:var(--heading);
  border-radius:7px;padding:.35rem .7rem;font-size:.76rem;font-weight:600;font-family:inherit;
  cursor:pointer;white-space:nowrap;
}
/* Also used on <a> in the toolbar, because .btn-ghost forces the near-white
   --on-dark token and vanishes on a light surface. */
a.fbr-btn{display:inline-flex;align-items:center;gap:.35rem;text-decoration:none}
.fbr-btn:hover{border-color:var(--brass)}
.fbr-btn:focus-visible{outline:2px solid var(--brass);outline-offset:2px}
.fbr-btn[disabled]{opacity:.45;cursor:not-allowed}
.fbr-btn--primary{background:var(--brass);border-color:var(--brass);color:var(--on-brass)}
.fbr-btn--danger{color:var(--claret);border-color:rgba(111,36,53,.35)}

@media(max-width:700px){
  .fbr-toolbar__right{margin-left:0;flex-basis:100%}
  .fbr-picker{flex-basis:100%}
  .fbr-picker select{max-width:100%;flex:1}
}

/* =====================================================================================
   ITEM 41 — THE ROSTER BECOMES CARDS ON A PHONE (gate Q1(a), Q2 700px, Q4(a), Q5(a)).

   EVERYTHING HERE HANGS OFF .fbr-section--phone, AND THAT IS THE MOST IMPORTANT LINE IN
   THIS BLOCK. Read the family's header comment above: .fbr-* is shared with
   /Admin/RaffleTickets, which uses every class named below except .fbr-filter and
   .fbr-diet. It is the same object — a picker, a totals strip, a seven-column table of
   money with per-row actions — and it has the same defect. It was not measured, it is not
   covered by item 41's sweep, and restyling it on the strength of a family name is how a
   page nobody looked at ships a phone layout nobody checked. The modifier is on the two
   pages the gate covers. RaffleTickets is BOOKED.

   WHY CARDS AND NOT A PINNED COLUMN. The table's own comment said it: "a roster has seven
   columns and the last of them holds buttons, so on a narrow viewport it scrolls ITSELF
   rather than pushing the page sideways." That kept the PAGE honest and made the READER's
   job impossible — Actions is the seventh column, so on a 390px phone the four buttons the
   page exists for began at roughly x=600 and were two screen-widths right of the thumb, per
   row. Measured: the table rendered 757px wide, with a min-width:46rem floor under it.

   ONE RENDERING, NOT TWO. The plan's only argument against cards was "two renderings of one
   list to keep in step". There is one: the same <table> is re-laid-out as a grid per row,
   the column heads are moved into ::before from data-label, and MealRosterSelection,
   MealRosterTotals and MealRosterCsv are untouched — item 30's decision D1 and
   RosterFilterDepthTests still hold.
   ===================================================================================== */
@media screen and (max-width:700px){

  /* THE PICKER SHRINKS, AND IT TAKES THE WHOLE LINE. Both halves were reported by the user.
     Letting the select shrink (the first fix) stopped the page sliding and left the control too
     narrow to read its own value: at 320 it rendered "14 Sep 2026 - E2E Phone Sweep boa", clipped
     mid-word, so the one thing a board picker exists to tell you — WHICH BOARD — was the thing it
     cut off. Claim 2 cannot see that, because clipped content overflows nothing. flex-wrap puts
     the label on its own line and leaves the select the width of the form.
     A <select> is a flex item with min-width:auto and will not shrink below its widest OPTION;
     .fbr-picker is itself a flex item of .fbr-toolbar with the same automatic minimum, computed
     from what is inside it. The picker slid BOTH the roster and the catering sheet 15px sideways
     at 320 — §2 recorded the roster as a page that "does NOT overflow", which is true at 390 and
     at 430 and false at 320, the width it was never checked at.

     THIS RULE SURVIVED MUTATION, WAS DELETED AS INERT, AND THE DELETION WAS WRONG. It looked
     redundant because the wrap rule below puts the select on its own full-width line, which was
     enough for a fixture whose board was called "E2E Phone Sweep board". Given a board name the
     length a lodge actually uses, the FORM goes to 540px on a 430px phone — 110px of sideways
     scroll on two pages. **A mutant can only kill a rule the fixture can provoke**, and a short
     name could not provoke this one. The fixture's board is named at realistic length now and
     this rule dies to a mutant.

     It is on the FORM alone, and the wrap beside it is the other half. Two more declarations were
     tried here and both are gone: min-width:0 on the SELECT (redundant once the form carries it)
     and flex-basis:100% on the select (redundant with the select's own automatic minimum, which
     is already wider than a shared line and so wraps it onto its own). Both survived mutation with
     the realistic board name in place, which is the test that matters. What is left is two
     declarations and a mutant kills each. */
  .fbr-section--phone .fbr-picker{ min-width:0; flex-wrap:wrap; }


  .fbr-section--phone .fbr-picker select,
  .fbr-section--phone .fbr-filter select,
  .fbr-section--phone .fbr-filter input,
  .fbr-section--phone .fbr-btn{ min-height:44px; }

  /* ---- the table stops being a table ---- */
  .fbr-section--phone .fbr-scroll{ overflow-x:visible; }
  .fbr-section--phone .fbr-table{ display:block; min-width:0; font-size:.9rem; }
  .fbr-section--phone .fbr-table tbody{ display:block; }

  /* THE HEAD GOES, AND display:none IS THE RIGHT TOOL RATHER THAN A VISUALLY-HIDDEN BOX.
     This was first written as the usual position:absolute + clip-path:inset(50%) clip, on the
     reasoning that the <th> row should stay in the accessibility tree. Both halves of that
     were wrong. A <table> set to display:block loses its table role in every engine, so by
     the time this rule applies the <th>s are not headers to anything — the per-cell
     data-label ::before is what names each fact. And clip-path clips PAINTING, not layout:
     the identical rule on the catering sheet left its <th> cells sitting at a real 114px with
     a right edge of 464 on a 320px phone, which is exactly what claim 2 is for. The roster's
     copy escaped only because the tr{display:grid} rule below happened to catch the head row
     too, which is an accident and not a design. */
  .fbr-section--phone .fbr-table thead{ display:none; }

  /* One card per booking. The areas are the mockup: name and status on the first line, the
     small facts on the second, the dietary note in full across the third, and the actions
     across the fourth. The cells are addressed by position because the column order is the
     table's and there is exactly one table. */
  .fbr-section--phone .fbr-table tr{
    display:grid;
    grid-template-columns:1fr 1fr auto;
    grid-template-areas:
      "name   name   status"
      "places method amount"
      "diet   diet   diet"
      "acts   acts   acts";
    gap:.45rem .7rem;
    padding:.95rem .9rem;
    border-bottom:1px solid var(--line-light);
  }
  .fbr-section--phone .fbr-table tr:last-child{ border-bottom:none; }

  .fbr-section--phone .fbr-table td{ display:block; padding:0; border-bottom:none; text-align:left; }
  .fbr-section--phone .fbr-table td:nth-child(1){ grid-area:name; }
  .fbr-section--phone .fbr-table td:nth-child(2){ grid-area:places; }
  .fbr-section--phone .fbr-table td:nth-child(3){ grid-area:diet; }
  .fbr-section--phone .fbr-table td:nth-child(4){ grid-area:method; }
  .fbr-section--phone .fbr-table td:nth-child(5){ grid-area:status; justify-self:end; align-self:start; }
  .fbr-section--phone .fbr-table td:nth-child(6){ grid-area:amount; text-align:right; }
  .fbr-section--phone .fbr-table td:nth-child(7){ grid-area:acts; }

  /* The column head, moved into the cell. Only the four labelled cells get one: the name
     cell names itself, the status chip says what it is, and the actions are their own words. */
  .fbr-section--phone .fbr-table td[data-label]::before{
    content:attr(data-label);
    display:block;
    font-size:.62rem; font-weight:700; letter-spacing:.09em; text-transform:uppercase;
    color:var(--slate-soft);
  }

  /* Q4(a): the note is shown in full, and there is no max-width to clip it against on a
     card. An EMPTY one goes entirely: the dash exists so a column stays aligned, and a card
     has no column to align with. */
  .fbr-section--phone .fbr-diet{ max-width:none; }

  /* `td.` IS LOAD-BEARING AND WAS MISSING. Written as `.fbr-section--phone .fbr-diet--none`
     this rule is two classes, 0-2-0, and it LOSES to `.fbr-section--phone .fbr-table td`
     three lines above at 0-2-1 — so every card drew an empty "Dietary —" line and nothing
     said so. The mutation run found it sideways: a mutant that set display:none on the same
     selector also failed to hide anything, which is how a selector announces it was never
     winning in the first place. */
  .fbr-section--phone .fbr-table td.fbr-diet--none{ display:none; }

  /* Q5(a): all four actions, wrapped rather than hidden behind a menu. They are already
     conditional per booking — CanMarkPaid, CanRefundToCard, CanMarkRefunded, CanCancel — so a
     typical card draws one or two and a cancelled one draws none.

     There is no min-height here. A `.fbr-section--phone .fbr-acts .fbr-btn{min-height:44px}`
     was written and mutating it away changed nothing: the .fbr-btn rule above already reaches
     every button on the page, row buttons included. A redundant rule that a mutant cannot
     kill is a rule the next reader has to work out is redundant. */
  .fbr-section--phone .fbr-acts{ margin-top:.25rem; gap:.5rem; }

  /* ITEM 54 HALF B. The editor's row must NOT become a card: every other <tr> here is laid
     out on the three-column grid above, and its single full-width <td> would be handed
     `grid-area:name` by the nth-child(1) rule and drawn in a third of the width. `tr.` and
     `> td` are both load-bearing - at 0-3-1 this would only TIE the nth-child rule and win
     on source order, and a rule that depends on where it sits in the file is a rule the next
     edit breaks silently. */
  .fbr-section--phone .fbr-table tr.fbr-row--panel{ display:block; padding:0; }
  .fbr-section--phone .fbr-table tr.fbr-row--panel > td{ display:block; grid-area:auto; }

  /* The panel's controls are the reason a Steward opened the page on a phone at all, so they
     get item 44's floor like every other control in this section. */
  .fbr-section--phone .stw__field input[type=text],
  .fbr-section--phone .stw__field select,
  .fbr-section--phone .stw__acts .btn{ min-height:44px; }
}

/* ITEM 64 SLICE 7 (D13) - THE PLACE-COUNT ROW, ABOVE THE SEAT FORM AND OUTSIDE IT.

   Its own form, because a form inside a form is markup browsers discard - slice 3 shipped exactly that
   on /Admin/History and seven claims went red on the OUTER one. And its own POST, because changing the
   count MOVES MONEY while saving names does not: one button for both would mean a Steward correcting a
   spelling could not tell what he was about to be charged for.

   The 44px floor is on the input, written when the control was built - .sp-sort's reasoning and item 48
   slice 1's. The button is `.btn btn-brass`, which is what this panel's own Save already wears, so it
   inherits a measured control rather than adding an unmeasured one to item 44's roster.

   `flex-wrap` and `min-width:0` from the start: item 64 slice 3 overflowed /Admin/Mentoring by 188px
   with a nowrap flex row, and a number input's default min-width is its content. */
.stw__places{
  display:flex;
  flex-wrap:wrap;
  align-items:center;
  gap:.5rem;
  margin:0 0 1rem;
  padding:0 0 1rem;
  border-bottom:1px solid var(--line-light);
}

.stw__places label{
  font-size:.78rem;
  color:var(--slate-soft);
  font-weight:600;
  letter-spacing:.03em;
}

.stw__places input[type="number"]{
  min-width:0;
  width:5rem;
  min-height:44px;
  padding:.4rem .6rem;
  border:1px solid var(--line-light);
  border-radius:8px;
  background:var(--surface);
  color:var(--heading);
  font-family:inherit;
  font-size:.9rem;
}

.stw__places .btn{ min-height:44px; }

/* =====================================================================================
   ITEM 54 HALF B - THE STEWARD'S SEAT EDITOR, INSIDE THE ROSTER (gate Q15(a) to Q17(a)).

   IT IS A ROW OF THE ROSTER'S OWN TABLE, WHICH IS WHAT THESE RULES ARE FOR. A panel
   rendered after the table would sit at the bottom of the list whichever row was open,
   and on a phone the table becomes one card per booking, so the editor has to fall into
   the same stack under the person it is about. One <tr class="fbr-row--panel"> with one
   full-width <td> does both, and costs two rules in the phone block below.

   THE BOXES ARE DRAWN EXPLICITLY. A bare <input> and a bare <select> inherit no box from
   this stylesheet - backlog §55 is a whole item about a control with no box on eight
   shipped pages, and item 42 slice 4 found a field with no box because two colour tokens
   did not exist. Every token named here is one the roster beside it already paints with.
   ===================================================================================== */
.fbr-row--panel > td{background:var(--parchment-2);padding:0}

.stw{padding:1.1rem 1rem 1.2rem}
.stw__head{margin:0 0 .85rem}
.stw__title{font-family:var(--serif);font-size:1.15rem;color:var(--heading);margin:0}
.stw__sub{margin:.2rem 0 0;font-size:.85rem;color:var(--slate-soft)}

.stw__seat{
  border:1px solid var(--line-light);border-radius:10px;padding:.85rem .9rem;
  margin:0 0 .7rem;background:var(--surface);
}
.stw__seat--booker{border-left:4px solid var(--brass)}
.stw__place{
  margin:0 0 .6rem;font-size:.66rem;font-weight:700;letter-spacing:.1em;
  text-transform:uppercase;color:var(--slate-soft);
}

/* A seat already ticked on the register. It is a STATEMENT and not a warning colour: the
   tick is the ordinary state of a board that has started, and the refusal it causes is
   spelled out in words under the panel. */
.stw__chip{
  display:inline-block;margin-left:.45rem;padding:.1rem .45rem;border-radius:999px;
  border:1px solid var(--line-light);background:var(--parchment-2);
  font-size:.6rem;letter-spacing:.06em;color:var(--slate);
}

/* Three across at a desk, one on a phone. A party is at most eleven seats and a course is
   at most a handful, so this never needs to become a table of its own. */
.stw__grid{display:grid;gap:.7rem;grid-template-columns:repeat(auto-fit,minmax(13rem,1fr))}
.stw__field{display:grid;gap:.35rem;min-width:0}
.stw__field label,
.stw__label{font-size:.82rem;font-weight:600;color:var(--slate)}
.stw__opt{font-weight:400;color:var(--slate-soft)}
.stw__field input[type=text],
.stw__field select{
  width:100%;box-sizing:border-box;border:1px solid var(--line-light);border-radius:6px;
  padding:.55rem .7rem;font-size:.95rem;font-family:inherit;color:var(--heading);
  background:var(--surface);
}
.stw__field input:focus-visible,
.stw__field select:focus-visible{outline:2px solid var(--brass);outline-offset:1px}

/* A locked value still shows, with the reason beside it. Hiding it would leave a Steward
   looking at an empty panel on the one screen that is supposed to tell him what a man ate. */
.stw__fixed{margin:0;font-weight:600;color:var(--heading)}
.stw__why{display:block;font-weight:400;color:var(--slate-soft);font-size:.8rem}

.stw__note{
  margin:.2rem 0 .9rem;font-size:.85rem;color:var(--slate);line-height:1.55;
}
.stw__acts{display:flex;flex-wrap:wrap;gap:.6rem;align-items:center}

/* ---------------------------------------------------------------------
   On the night. Mobile FIRST: the base rules are the phone, and the only
   media query widens it for the desk. Touch targets are >=44px because the
   page is used one-handed, standing up, by someone holding a cash tin.
   --------------------------------------------------------------------- */

.fbt-section{padding:1.25rem 0 3rem;max-width:34rem}

/* /Admin/Journeys. THE SAME PADDING AS `.fbt-section` WITH NO `max-width`, which is what every other admin
   table page already does -- `.fbr-section`, `.sp-section` and `.mn-section` all set padding alone and take
   the site's --maxw of 1180px. This page wore `.fbt-section` and inherited a measure written for a phone
   held in one hand; its three tables were dealt 702px on a 1440px screen. */
.lj-section{padding:1.25rem 0 3rem}

/* .fbt-top / .fbt-back / .fbt-eyebrow / .fbt-title / .fbt-sub retired: this page renders the shared
   admin band (_AdminPageHeader.cshtml) like every other page under /Admin. */

.fbt-search{display:flex;gap:.5rem;align-items:center;margin:0 0 1rem}
.fbt-search input[type=search]{
  flex:1;min-width:0;border:1px solid var(--line-light);border-radius:8px;background:var(--surface);
  color:var(--heading);padding:.7rem .8rem;font-size:1rem;font-family:inherit;
}
.fbt-search__go{
  border:1px solid var(--line-light);background:var(--surface);color:var(--heading);border-radius:8px;
  padding:.7rem 1rem;font-size:.88rem;font-weight:600;font-family:inherit;cursor:pointer;min-height:44px;
}
.fbt-search__clear{font-size:.8rem;color:var(--slate-soft)}

.fbt-errors{
  margin:0 0 1rem;padding:.75rem 1rem;border-radius:8px;
  background:rgba(111,36,53,.07);border:1px solid rgba(111,36,53,.3);color:var(--claret);
  font-size:.86rem;line-height:1.5;
}
.fbt-errors ul{margin:0;padding-left:1.1rem}

.fbt-list{list-style:none;margin:0 0 1.25rem;padding:0;display:grid;gap:.7rem}
.fbt-none{
  font-size:.86rem;color:var(--slate-soft);background:var(--parchment-2);border-radius:8px;
  padding:1rem;text-align:center;
}
.fbt-row{
  background:var(--surface);border:1px solid var(--line-light);border-radius:10px;padding:.85rem .9rem;
}
.fbt-row__top{display:flex;justify-content:space-between;gap:.6rem;align-items:baseline}
.fbt-row__name{font-weight:700;color:var(--heading);font-size:1rem}
.fbt-row__places{
  font-size:.8rem;color:var(--slate-soft);font-variant-numeric:tabular-nums;white-space:nowrap;
}
.fbt-row__meta{margin:.25rem 0 0;font-size:.76rem;color:var(--slate-soft);line-height:1.5}
.fbt-row__acts{display:flex;gap:.5rem;margin-top:.7rem}
.fbt-cash-form{flex:1}
.fbt-cash{
  width:100%;min-height:52px;border:none;border-radius:9px;background:var(--brass);
  color:var(--on-brass);font-size:1rem;font-weight:700;font-family:inherit;cursor:pointer;
}
.fbt-cash:active{transform:translateY(1px)}
.fbt-cash:focus-visible,
.fbt-qr:focus-visible{outline:2px solid var(--brass);outline-offset:2px}
.fbt-qr{
  min-width:64px;min-height:52px;border:1px solid var(--line-light);border-radius:9px;
  background:var(--surface);color:var(--heading);font-size:.88rem;font-weight:600;font-family:inherit;
  cursor:pointer;
}

/* The QR is sized here, not by the library: the SVG carries a viewBox so it
   scales to whatever box it is given, and 190px is comfortably scannable from
   arm's length on a phone screen. */
.fbt-qr-panel{
  display:flex;gap:.9rem;align-items:flex-start;margin-top:.85rem;padding-top:.85rem;
  border-top:1px dashed var(--line-light);
}
.fbt-qr-panel__code{width:190px;flex:none;background:var(--surface);border-radius:8px;padding:6px}
.fbt-qr-panel__code svg{display:block;width:100%;height:auto}
.fbt-qr-panel__side{flex:1;min-width:0}
.fbt-qr-panel__side p{margin:0 0 .6rem;font-size:.78rem;color:var(--slate-soft);line-height:1.5}
.fbt-check{
  border:1px solid var(--line-light);background:var(--surface);color:var(--heading);border-radius:8px;
  padding:.6rem .8rem;font-size:.82rem;font-weight:600;font-family:inherit;cursor:pointer;min-height:44px;
}

.fbt-settled-head{
  margin:1.5rem 0 .6rem;font-size:.7rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;
  color:var(--slate-soft);
}
.fbt-list--done{gap:.45rem}
.fbt-row--done{background:var(--parchment-2);padding:.65rem .9rem}
.fbt-row--done .fbt-row__name{font-size:.9rem;font-weight:600}
.fbt-row__done{margin:.2rem 0 0;font-size:.76rem;color:var(--ok-ink);background:var(--ok-fill);border-radius:5px;padding:1px 6px;display:inline-block;font-weight:600}

.fbt-walkin{
  background:var(--surface);border:1px solid var(--line-light);border-radius:10px;
  padding:0 .9rem;margin:0 0 1.25rem;
}
.fbt-walkin__summary{
  cursor:pointer;list-style:none;padding:.9rem 0;font-size:.95rem;font-weight:700;color:var(--brass-ink);
}
.fbt-walkin__summary::-webkit-details-marker{display:none}
.fbt-walkin__summary:focus-visible{outline:2px solid var(--brass);outline-offset:2px;border-radius:6px}
.fbt-walkin__form{display:grid;gap:.85rem;padding:0 0 1rem}
.fbt-field{display:grid;gap:.3rem;min-width:0}
.fbt-field--half{max-width:8rem}
.fbt-field label{font-size:.78rem;font-weight:600;color:var(--slate-soft)}
.fbt-field input,
.fbt-field select{
  width:100%;box-sizing:border-box;border:1px solid var(--line-light);border-radius:8px;
  background:var(--surface);color:var(--heading);padding:.65rem .75rem;font-size:1rem;font-family:inherit;
}
.fbt-field input:focus-visible,
.fbt-field select:focus-visible{outline:2px solid var(--brass);outline-offset:1px}
.fbt-hint{margin:0;font-size:.72rem;color:var(--slate-soft);line-height:1.5}
.fbt-err{font-size:.75rem;color:var(--claret);font-weight:600}
.fbt-check-row{display:flex;align-items:center;gap:.6rem;font-size:.9rem;color:var(--heading)}
.fbt-check-row input{width:22px;height:22px;flex:none}
.fbt-add{
  min-height:50px;border:none;border-radius:9px;background:var(--brass);color:var(--on-brass);
  font-size:.95rem;font-weight:700;font-family:inherit;cursor:pointer;
}

/* The footer totals are the last thing read and the thing read most often, so
   they are large, tabular, and stuck to the bottom of the viewport on a phone. */
.fbt-foot{
  position:sticky;bottom:0;display:flex;gap:1rem;justify-content:space-between;
  background:var(--surface);border:1px solid var(--line-light);border-radius:10px;
  padding:.8rem 1rem;box-shadow:0 -2px 10px rgba(0,0,0,.06);
}
.fbt-foot__k{
  margin:0;font-size:.66rem;font-weight:700;letter-spacing:.09em;text-transform:uppercase;
  color:var(--slate-soft);
}
.fbt-foot__v{
  margin:.2rem 0 0;font-size:1.2rem;font-weight:700;color:var(--heading);
  font-variant-numeric:tabular-nums;
}
.fbt-foot__v--owe{color:var(--warn-ink);background:var(--warn-fill);border-radius:5px;padding:0 6px;display:inline-block;width:fit-content}
.fbt-note{margin:.9rem 0 0;font-size:.76rem;color:var(--slate-soft);line-height:1.6}

/* Room for the sticky footer to sit over. Without it the footer pins to the bottom of the
   viewport and covers whatever the page ends with - which is the walk-in control, the one
   thing a Steward reaches for after everybody in the list has been dealt with. */
.fbt-spacer{height:5.5rem}

@media(min-width:640px){
  .fbt-section{max-width:44rem}
  .fbt-foot{position:static;box-shadow:none}
  .fbt-spacer{display:none}
}

/* ==========================================================================================
   MJ Phase 3 — the register (.att-*), shared by /Admin/FestiveBoard/Tonight's second mode and
   by /Admin/Attendance. Mobile FIRST like the .fbt-* rules above it, and for the same reason:
   this is worked standing up, one-handed, by somebody holding a cash tin.

   TWO "on" treatments, deliberately different, because they mean different things:
     a MODE chip is a BRASS FILL under --on-brass — "this is what you are looking at". Never
       var(--ink): Phase 2 shipped an --ink filter chip that DISAPPEARED on Midnight, whose
       --ink (#0B0F19) is darker than its --parchment (#131824), and only a screenshot caught it.
     a PRESENCE toggle is the fixed --ok pair — "this person is accounted for". It is the same
       pair .fbt-row__done uses for "Cash taken" one line lower on a collections row, and the
       status pairs are the untinted set that works on every palette by construction.
   Brass stays reserved for the button that moves money, so nothing competes with it at a door.
   ========================================================================================== */

.att-modes{display:flex;gap:6px;flex-wrap:wrap;margin:0 0 1rem}
.att-mode{
  font:inherit;font-size:.82rem;font-weight:600;text-decoration:none;
  color:var(--slate);background:var(--surface);border:1px solid var(--line-light);
  border-radius:999px;padding:8px 16px;min-height:44px;display:inline-flex;align-items:center;gap:7px;
}
.att-mode:hover{border-color:var(--brass)}
.att-mode:focus-visible{outline:2px solid var(--brass);outline-offset:2px}
.att-mode.is-on{background:var(--brass);color:var(--on-brass);border-color:var(--brass)}
.att-mode__n{font-variant-numeric:tabular-nums;opacity:.75;font-weight:700}

/* The collections-row control: full width, above the money, 48px so the same thumb reaches both. */
.att-toggle-form{margin-top:.7rem}
.att-toggle{
  width:100%;min-height:48px;display:flex;align-items:center;justify-content:center;gap:.5rem;
  border:1px solid var(--line-light);border-radius:9px;background:var(--surface);color:var(--heading);
  font-size:.92rem;font-weight:600;font-family:inherit;cursor:pointer;
}
.att-toggle:hover{border-color:var(--brass)}
.att-toggle:focus-visible,
.att-row__toggle:focus-visible{outline:2px solid var(--brass);outline-offset:2px}
.att-toggle[aria-pressed="true"]{background:var(--ok-fill);border-color:var(--ok-line);color:var(--ok-ink)}
.att-toggle__mark{font-size:1.05rem;line-height:1}
.att-toggle__when{font-weight:500;opacity:.8;font-size:.8rem;font-variant-numeric:tabular-nums}
.att-guests-hint{margin:.45rem 0 0;font-size:.74rem;color:var(--slate-soft);line-height:1.5}

/* The register list: one row per PERSON, and no money anywhere in it. */
.att-reg{list-style:none;margin:0 0 1rem;padding:0;display:grid;gap:.5rem}
.att-reg__head{
  margin:1.3rem 0 .55rem;font-size:.7rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;
  color:var(--slate-soft);display:flex;gap:.6rem;align-items:baseline;flex-wrap:wrap;
}
.att-reg__head span{font-weight:400;letter-spacing:0;text-transform:none;font-size:.75rem}
/* Wrapping is what lets a flagged row put the member's sentence on a line of its own underneath
   (MJ Phase 4); without it the note squeezes in beside the button and neither is readable. */
.att-row{
  display:flex;align-items:center;gap:.8rem;background:var(--surface);flex-wrap:wrap;
  border:1px solid var(--line-light);border-radius:10px;padding:.6rem .7rem .6rem .9rem;
}
/* A guest is dashed and chipped so the eye can skip them; they are marked exactly like anybody else. */
.att-row--guest{background:var(--parchment-2);border-style:dashed}
.att-row--empty{justify-content:center;color:var(--slate-soft);font-size:.88rem;background:var(--parchment-2)}
.att-row__who{flex:1;min-width:0}
.att-row__name{display:block;font-weight:700;color:var(--heading);font-size:.98rem}
.att-row__meta{display:block;font-size:.75rem;color:var(--slate-soft);line-height:1.45}
.att-row__toggle{
  flex:none;min-width:7.4rem;min-height:44px;display:inline-flex;align-items:center;justify-content:center;
  gap:.4rem;border:1px solid var(--line-light);border-radius:9px;background:var(--surface);
  color:var(--heading);font-size:.86rem;font-weight:600;font-family:inherit;cursor:pointer;
  text-decoration:none;text-align:center;
}
.att-row__toggle:hover{border-color:var(--brass)}
.att-row__toggle[aria-pressed="true"]{background:var(--ok-fill);border-color:var(--ok-line);color:var(--ok-ink)}

.att-chip{
  display:inline-flex;align-items:center;font-size:.6rem;font-weight:700;letter-spacing:.08em;
  text-transform:uppercase;border-radius:999px;padding:2px 8px;white-space:nowrap;margin-left:8px;
  vertical-align:1px;color:var(--slate-soft);background:var(--surface);border:1px solid var(--line-light);
}
.att-chip--on{color:var(--ok-ink);background:var(--ok-fill);border-color:var(--ok-line)}

/* MJ Phase 4 (D5): what a member said about their own row, where the Secretary can act on it. The fixed
   --warn pair, the same one the footer uses for "3 booked, not here" — a note, not an error. */
.att-row__flag{
  flex-basis:100%;display:flex;gap:.5rem;align-items:flex-start;margin:.1rem 0 0;
  padding:.5rem .65rem;border-radius:9px;font-size:.78rem;line-height:1.5;
  color:var(--warn-ink);background:var(--warn-fill);border:1px solid var(--warn-line);
}
.att-row__flag>span:last-child{flex:1;min-width:0}

/* A seat that was paid for and never named. It cannot be marked until somebody types the name — the
   guest half of the unique index is (EventId, GuestName), so a blank one could exist exactly once per
   event and the evening's SECOND unnamed guest would have failed at the door. */
.att-name-form{flex:none;display:flex;gap:.4rem;align-items:center;flex-wrap:wrap;max-width:16rem}
.att-name-form__label{
  font-size:.66rem;font-weight:700;letter-spacing:.09em;text-transform:uppercase;color:var(--slate-soft);
  flex-basis:100%;
}
.att-name-form input[type=text]{
  flex:1;min-width:7rem;border:1px solid var(--line-light);border-radius:8px;background:var(--surface);
  color:var(--heading);padding:.5rem .6rem;font-size:.92rem;font-family:inherit;
}
.att-name-form input[type=text]:focus-visible{outline:2px solid var(--brass);outline-offset:1px}
.att-name-form .att-toggle{width:auto;padding:0 .9rem}

/* The rest of the roll, folded away on a night when most of it is irrelevant. An ordinary <details>,
   so the fold works with no scripting at all — the same constraint that makes every action a form post. */
.att-roll{background:var(--surface);border:1px solid var(--line-light);border-radius:10px;padding:0 .9rem;margin:0 0 1rem}
.att-roll__summary{
  cursor:pointer;list-style:none;padding:.9rem 0;font-size:.92rem;font-weight:700;color:var(--brass-ink);
  display:flex;justify-content:space-between;gap:.8rem;align-items:baseline;
}
.att-roll__summary::-webkit-details-marker{display:none}
.att-roll__summary:focus-visible{outline:2px solid var(--brass);outline-offset:2px;border-radius:6px}
.att-roll__summary span{font-weight:400;font-size:.78rem;color:var(--slate-soft)}
.att-roll__body{padding:0 0 1rem}

.att-find{background:var(--surface);border:1px solid var(--line-light);border-radius:10px;padding:0 .9rem;margin:0 0 1.25rem}
.att-find__summary{cursor:pointer;list-style:none;padding:.9rem 0;font-size:.95rem;font-weight:700;color:var(--brass-ink)}
.att-find__summary::-webkit-details-marker{display:none}
.att-find__summary:focus-visible{outline:2px solid var(--brass);outline-offset:2px;border-radius:6px}
.att-find__body{padding:0 0 1rem}
.att-find__form{display:grid;gap:.85rem}

.att-foot__v{
  margin:.2rem 0 0;font-size:1.2rem;font-weight:700;font-variant-numeric:tabular-nums;
  color:var(--ok-ink);background:var(--ok-fill);border-radius:5px;padding:0 6px;
  display:inline-block;width:fit-content;
}
.att-foot__v--todo{color:var(--warn-ink);background:var(--warn-fill)}

.att-empty{
  font-size:.86rem;color:var(--slate-soft);background:var(--parchment-2);border-radius:8px;
  padding:1rem;text-align:center;margin:0 0 1rem;
}
.att-fallback,
.att-crosslink{
  margin:0 0 1rem;padding:.7rem .9rem;border-radius:8px;font-size:.8rem;line-height:1.55;
  color:var(--slate-soft);background:var(--parchment-2);border:1px solid var(--line-light);
}
.att-fallback strong{color:var(--heading)}

/* NS Phase 1 — the /Admin/Email role picker (D15). Additions to the shared .sp-* settings-page
   vocabulary; everything else on that page reuses the FB Phase 1 rules above verbatim. */
.sp-roles{display:flex;flex-wrap:wrap;gap:8px;margin:2px 0 0}
.sp-tag{display:inline-flex;align-items:center;gap:7px;padding:6px 12px;border-radius:999px;
        border:1px solid rgba(148,163,184,.45);background:var(--surface);cursor:pointer;font-size:.86rem;color:var(--heading)}
.sp-tag:hover{border-color:rgba(154,106,31,.5)}
.sp-tag input{margin:0;accent-color:#9A6A1F}
.sp-tag:has(input:checked){border-color:rgba(154,106,31,.65);background:rgba(154,106,31,.1);font-weight:600}
.sp-tag:focus-within{outline:2px solid rgba(154,106,31,.55);outline-offset:2px}

/* EP Phase 3 — the /Admin/Email provider selector (decision E13, docs/email-provider-plan.md §10).
   A radio PAIR rather than a select, so both providers and their one-line blurbs are readable without
   interacting with anything — which is the point: choosing where the lodge's mail goes out from should
   not need a click to find out what the other option is.

   Built from .sp-tag's vocabulary deliberately (same border, same brass check state, same :has() and
   :focus-within rules) so it inherits a look the theme-contrast sweep has already measured. Every colour
   here is a theme token or the brass rgba .sp-tag already uses; nothing new pairs two theme tokens
   against each other, which is where the TC sweep found its real failures. */
.sp-providers{display:grid;gap:10px;margin:2px 0 0}
.sp-provider{display:flex;align-items:flex-start;gap:11px;padding:11px 13px;border-radius:9px;
             border:1px solid rgba(148,163,184,.45);background:var(--surface);cursor:pointer}
.sp-provider:hover{border-color:rgba(154,106,31,.5)}
.sp-provider input{margin:3px 0 0;accent-color:#9A6A1F;flex:0 0 auto}
.sp-provider:has(input:checked){border-color:rgba(154,106,31,.65);background:rgba(154,106,31,.08)}
.sp-provider:focus-within{outline:2px solid rgba(154,106,31,.55);outline-offset:2px}
.sp-provider__text{display:grid;gap:3px;min-width:0}
.sp-provider__name{display:flex;flex-wrap:wrap;align-items:center;gap:8px;font-size:.92rem;font-weight:600;color:var(--heading)}
.sp-provider__text small{font-size:.78rem;color:var(--slate-soft);line-height:1.5}
.sp-provider__badge{font-style:normal;font-size:.62rem;font-weight:700;letter-spacing:.09em;text-transform:uppercase;
                    border-radius:999px;padding:3px 9px;white-space:nowrap;
                    color:var(--slate-soft);background:var(--parchment-2);border:1px solid var(--line-light)}
.sp-provider__badge--on{color:var(--ok-ink);background:var(--ok-fill);border-color:var(--ok-line)}
.sp-provider__badge--off{color:var(--warn-ink);background:var(--warn-fill);border-color:var(--warn-line)}
/* A single-token provider has no second credential box, so its key field takes the whole row rather than
   half of one beside an empty column. */
.sp-two--single{grid-template-columns:1fr}

/* NS Phase 5 — the announcement composer (D9). One rule, because the composer is deliberately built
   from the settings-page vocabulary above: .sp-tag carries the audience radios as well as the role
   checkboxes (:has(input:checked) does not care which), and only the message box needed anything new. */
.sp-textarea{min-height:12rem;line-height:1.6;resize:vertical;font-family:inherit}

/* NS Phase 5 — the members' preferences card: the three categories nobody can switch off are rows with
   a badge rather than a disabled toggle. A control that cannot move is worse than a statement. */
.pref-always{display:inline-flex;align-items:center;padding:4px 12px;border-radius:999px;
             font-size:.7rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
             color:var(--slate-soft);background:var(--parchment-2);border:1px solid var(--line-light);white-space:nowrap}

/* ==========================================================================================
   THE MEMBERS PORTAL (.mp-*) — MR Phase 2.
   docs/members-portal-plan.md §7.7, mockup docs/mockups/members-portal.html.

   READ THIS BEFORE EDITING A COLOUR HERE.

   The mockup hardcodes its palette (--chrome:#0E1116, --on-dark:#EDE4D0, --brass-soft, --muted,
   --faint, --panel:#fff). None of those survive contact with the theme system, and the ceremonial
   hero is the EXACT shape [[theme-render-mapping-tokens]] warns about: a fixed dark surface meeting
   theme text tokens. Every rule below is therefore written on the theme contract's own tokens, and
   the pairing is what makes it safe — the themes move --surface, --heading, --slate and --claret
   TOGETHER, so:

     * dark band  → background var(--ink)/var(--ink-deep), text var(--on-dark), hairline
                    var(--line-dark), accent TEXT var(--brass-soft), accent FILL var(--brass)
                    with var(--on-brass) on top of it
     * light card → background var(--surface), heading var(--heading), body var(--slate),
                    muted var(--slate-soft), hairline var(--line-light)

   THE ACCENT-TEXT RULE ABOVE IS PHASE 3'S CORRECTION, AND IT WAS A REAL DEFECT.
   Phase 2 wrote "accent var(--brass)" for the dark band, which holds on Heritage (brass on ink is
   7.8:1) and fails badly the moment a theme's brass is itself dark: on College --brass is #204E79
   against --ink #14273B, so the hero's eyebrow and the meeting panel's label rendered at 1.75:1 and
   2.95:1 on Ashlar, the user's own active theme. --brass-soft is the accent that survives all eleven
   (8.3:1 at worst). --brass stays correct as a FILL, where --on-brass is computed against it
   (ThemeStyleComposer.ComputeOnBrass) and therefore cannot be dark-on-dark.

   Small ACCENT text on a light card has the mirror problem: --brass-ink on --parchment-2 is 3.27:1 on
   Heritage, so "View all" is var(--slate). --brass-ink is used 19 times across this stylesheet and
   most of those are the same shape; fixing them is a site-wide job, not a members-portal one, and it
   is written up in docs/members-portal-plan.md §9.

   Verified against the theme table rather than assumed: on Midnight, Evergreen, Garnet and Onyx
   --surface is DARK (#1C2333 and friends) while --heading, --slate and --claret are all light, so
   "claret text on --surface" reads correctly in both directions. That is why the alert card tints
   its BORDER and its value rather than its background — a claret fill would have to be readable
   against light claret text on those four themes, and it is not.

   Two mockup colours have no token and are deliberately not invented:
     * its green "ok" pip → var(--brass). The pip says "there is something here"; the colour was
       not carrying meaning, and a hardcoded #4E7A5A goes muddy on half the themes.
     * its pure-white panels → var(--surface).

   .btn-ghost is NOT used anywhere on this page. It forces color:var(--on-dark) — the near-white
   token for dark bands — so on a parchment surface it renders as an empty pill with no text in it.
   The portal's own .mp-cta--ghost is a bordered light-surface button instead.
   ========================================================================================== */

/* Present to a screen reader, absent to the eye (MR Phase 3). A duplicate of
   .admin-visually-hidden rather than a use of it: that class is named for the admin area, and borrowing
   an "admin-" prefix onto a members' page is the kind of small lie that makes the next editor think this
   markup came from somewhere else. Absolute positioning also keeps the hidden heading OUT of
   .mp-glance's grid, so naming the strip does not add a fifth column to it. */
/* The screen-reader-only label. It is 1px and clipped, but it is also ABSOLUTELY POSITIONED, and an
   absolutely positioned box is clipped by an `overflow` ancestor only when that ancestor is also its
   containing block. Inside a wide scrolling table one of these sat at x=409 in a 360px viewport and pushed
   the DOCUMENT sideways by 49px — invisible, unreachable, and enough to make a phone scroll. `left:0` pins it
   to its own cell instead of to the static position at the far right of a 549px table.

   Found by MJ Phase 7's 360px assertion on /Admin/Journeys, on a table Phase 5 shipped. */
.mp-sr{
  position:absolute;left:0;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0;
}

/* ---- the ceremonial hero ---- */
.mp-hero{
  position:relative;overflow:hidden;
  background:
    radial-gradient(120% 130% at 88% -20%, color-mix(in srgb, var(--brass) 16%, transparent), transparent 55%),
    linear-gradient(180deg, var(--ink), var(--ink-deep));
  /* AFTER the shorthand, which resets background-color to transparent. This is the opaque backdrop
     under the gradients — a fallback if they ever fail to paint, and the thing that lets the contrast
     guard in MembersPortalPlaywrightTests resolve what is actually behind the hero's buttons. Without
     it the band's computed background-color is rgba(0,0,0,0) and an ancestor walk lands on the
     parchment body, i.e. on the wrong answer by the whole width of the palette. */
  background-color:var(--ink);
  color:var(--on-dark);
  border-bottom:1px solid color-mix(in srgb, var(--brass) 28%, transparent);
}
/* The brass rule along the foot of the band. A gradient rather than a solid 3px border (the
   .page-hero convention) because the glance strip below overlaps it by 26px and a hard edge
   through the middle of a card reads as a mistake. */
.mp-hero::after{
  content:"";position:absolute;left:0;right:0;bottom:0;height:2px;
  background:linear-gradient(90deg,transparent,var(--brass) 30%,var(--brass-soft) 50%,var(--brass) 70%,transparent);
}
.mp-hero__wrap{
  display:grid;grid-template-columns:1.5fr 1fr;gap:34px;align-items:center;
  padding:44px 24px 52px;
}
/* A hero with NOTHING in its second column. The portal, the archive and the journey all put a panel
   there; the ritual room has none until Phase 6b builds the mentor panel, and a reserved empty column
   left the lead wrapping at two-thirds width beside a third of a screen of nothing. Opt-in, so the
   pages that DO draw a panel are untouched — including the journey, whose panel is absent more often
   than not and where the reserved column is what keeps the hero the same height either way. */
.mp-hero__wrap--solo{grid-template-columns:1fr}
/* --brass-soft, not --brass: see the accent-text correction in the header note. */
.mp-hero .eyebrow{color:var(--brass-soft)}

.mp-crestline{
  display:flex;align-items:center;gap:12px;margin:0 0 12px;
  color:var(--brass-soft);opacity:.9;font-size:.9rem;line-height:1;
}
.mp-crestline span{height:1px;width:44px;background:linear-gradient(90deg,var(--brass-soft),transparent)}
.mp-crestline span:first-child{background:linear-gradient(90deg,transparent,var(--brass-soft))}

.mp-hero__title{
  font-family:var(--serif);font-weight:600;color:var(--on-dark);
  font-size:clamp(2.1rem,4.4vw,3.1rem);line-height:1.04;margin:12px 0 10px;text-wrap:balance;
}
.mp-hero__title em{font-style:italic;color:var(--brass-soft)}
.mp-hero__lead{
  color:color-mix(in srgb, var(--on-dark) 78%, transparent);
  max-width:48ch;margin:0;
}
.mp-rank{
  display:inline-flex;align-items:center;gap:8px;margin-top:16px;
  font-size:.78rem;letter-spacing:.04em;color:var(--brass-soft);
  border:1px solid color-mix(in srgb, var(--brass) 40%, transparent);
  border-radius:999px;padding:5px 13px;
}

/* ---- next meeting panel (inside the dark band) ---- */
.mp-meeting{
  background:color-mix(in srgb, var(--on-dark) 5%, transparent);
  border:1px solid var(--line-dark);
  border-radius:14px;padding:20px 22px;
}
.mp-meeting__k{
  margin:0 0 10px;font-size:.62rem;letter-spacing:.18em;text-transform:uppercase;
  color:var(--brass-soft);font-weight:700;
}
.mp-meeting__date{font-family:var(--serif);font-size:2rem;line-height:1.05;margin:0;color:var(--on-dark)}
.mp-meeting__sub{
  margin:8px 0 0;font-size:.88rem;
  color:color-mix(in srgb, var(--on-dark) 72%, transparent);
}
.mp-meeting__countdown{
  display:inline-flex;align-items:center;gap:7px;margin-top:14px;font-size:.78rem;
  color:var(--brass-soft);background:color-mix(in srgb, var(--brass) 14%, transparent);
  border-radius:999px;padding:5px 12px;
}
.mp-meeting__acts{display:flex;gap:9px;margin-top:16px}
.mp-meeting__acts:empty{display:none}
.mp-meeting__act{
  flex:1;text-align:center;text-decoration:none;font-size:.82rem;font-weight:600;
  border-radius:9px;padding:10px;
}
.mp-meeting__act--primary{background:var(--brass);color:var(--on-brass)}
.mp-meeting__act--ghost{
  border:1px solid color-mix(in srgb, var(--on-dark) 28%, transparent);color:var(--on-dark);
}
.mp-meeting__act:focus-visible{outline:2px solid var(--brass-soft);outline-offset:2px}

/* ---- at a glance strip ---- */
/* padding:0 is LOAD-BEARING, and it is the third place in this stylesheet to say so (see the widget
   cards around line 8080 and the panel at 2905). The strip became a <section> in Phase 3 so it could
   carry a name for assistive technology, and the page-wide `section{padding:74px 0}` at line 126
   immediately put 74px inside it, and that does not look like a padding bug: it looks like the strip has
   stopped overlapping the hero band, because the -26px margin then pulls up a box whose content starts
   74px lower. Nothing failed: not the build, not the four E2E selectors that count the cards. Only the
   screenshot. */
/* THREE columns since 2026-08-04, not four: the "Raffle tickets" tile went with the sale, and a
   four-column grid holding three tiles left a quarter of the strip empty against a hero panel that is
   full-width above it. Caught by a screenshot, exactly like the padding note above — the card COUNT
   assertions were updated to 3 and passed either way. Back to 4 if a fourth tile ever earns the place. */
.mp-glance{
  display:grid;grid-template-columns:repeat(3,1fr);gap:14px;padding:0;
  margin:-26px auto 0;position:relative;z-index:2;
}
.mp-gcard{
  background:var(--surface);border:1px solid var(--line-light);border-radius:13px;padding:15px 16px;
  box-shadow:0 20px 44px -30px rgba(0,0,0,.6);
}
.mp-gcard__k{
  margin:0;font-size:.6rem;letter-spacing:.12em;text-transform:uppercase;
  color:var(--slate-soft);font-weight:700;
}
.mp-gcard__v{
  font-family:var(--serif);font-size:1.6rem;line-height:1.15;margin:7px 0 2px;
  /* LINING figures, added 2026-08-13. Cormorant Garamond's default figures are old-style, where 1 is a
     short stroke that reads as a capital I: "1 raffle ticket" came out as "I RAFFLE TICKET" and a member
     read it as a letter. Applied ONLY where a bare number stands next to its own label — this card, the
     portal's wide tile and the journey's year band. Prose, dates and headings keep the old-style figures
     the eleven palettes were drawn around; "12 years" in a sentence is not being read as a quantity. */
  color:var(--heading);font-variant-numeric:tabular-nums lining-nums;
}
.mp-gcard__n{margin:0;font-size:.8rem;color:var(--slate-soft)}
/* Border and value only — see the header note on why this does not tint its background. */
.mp-gcard--alert{border-color:color-mix(in srgb, var(--claret) 45%, var(--line-light))}
.mp-gcard--alert .mp-gcard__v{color:var(--claret)}
.mp-pip{
  display:inline-block;width:7px;height:7px;border-radius:50%;
  background:var(--brass);margin-right:6px;vertical-align:middle;
}

/* ---- section heads ---- */
.mp-sect{padding:42px 0 8px}
.mp-sect__row{display:flex;align-items:baseline;justify-content:space-between;gap:16px;flex-wrap:wrap}
.mp-sect h2{font-family:var(--serif);font-weight:600;font-size:1.7rem;margin:0;color:var(--heading)}
.mp-sect__hint{color:var(--slate-soft);font-size:.86rem;margin:0}

/* ---- the feature grid ---- */
/* The LIVE row was four wide because there were four live tiles — three features plus the
   email-preferences link decision Q3 moved off the page — and a fourth tile dropping onto a row of its
   own beside two empty columns reads as a grid that failed. THREE since 2026-08-04: the raffle tile went
   with the sale, and the same reasoning now points the other way, because three tiles in a four-column
   grid leave a quarter of the row empty. The count and the columns have to move together; a screenshot
   is what notices when they do not. The coming-soon grid stays at three: six tiles make two clean rows.

   FOUR again since MJ Phase 2 (2026-08-11): "Summons & minutes" left the coming-soon list and became a
   live tile, so live is 4 across one row and coming-soon is FIVE in a three-wide grid — 3 + 2. Plan rule
   §12.1.7 locks the next two moves as well: 5 live at repeat(3) after Phase 4, 6 live at repeat(3) after
   Phase 6, with coming-soon dropping to 4 at repeat(4).

   THREE since MJ Phase 4 (2026-08-12), which is that first locked move: "My Masonic journey" is a FIFTH
   live tile — a new one, not a filled-in placeholder, so coming-soon stays at five — and five tiles in a
   four-column grid leave one hanging alone under three. 3 + 2 in both grids now.

   AND THE END STATE, at MJ Phase 6a: "The ritual room" is the SIXTH live tile and the second placeholder
   to be built, so live is six in two clean rows of three and coming-soon is FOUR across one row. Both
   counts are what §12.1.7 locked at plan time and what the approved portal screen draws.

   AMENDED AT MJ PHASE 7 (2026-08-13, approved supplement screen H1): a SEVENTH live tile, "Your year, and
   your record", which spans the whole row — `.mp-feat--wide`. Seven equal tiles leave a hole in every square
   arrangement (3+3+1 at three across, 4+3 at four), and a lone tile under two full rows reads as one that
   failed to load. The wide tile is also the only one of the seven with FIGURES to show, so the width is
   earned rather than a way to fill a row. The counts are therefore: live SIX at repeat(3) plus one full-width
   tile; coming-soon FOUR at repeat(4), unchanged. */
/* minmax(0,1fr), not 1fr: a bare `1fr` is `minmax(auto,1fr)`, so a track never shrinks below its content's
   min-content width and a long word pushes the whole grid past its container. Found at MJ Phase 7 by the
   cross-theme screenshot pass — the coming-soon row overflowed 1280px by 49px on ONYX and on no other palette,
   because that theme's typeface is wider and "Subscriptions & dues" then would not fit a 1fr quarter. The
   tiles wrap their words instead. */
.mp-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px;padding-bottom:16px}
.mp-grid--live{grid-template-columns:repeat(3,minmax(0,1fr))}
/* ITEM 49 SLICE 1. A member of another lodge loses the journey tile and the ritual room, and four equal
   tiles at repeat(3) is 3 + 1 with a two-cell hole — the exact arithmetic slip rule §12.1.7 exists to stop
   and the one only a screenshot catches. Two rows of two instead.

   It is deliberately a SEPARATE class of equal weight rather than a `:not()` on the one above, and it sits
   here rather than after the media queries: both queries below name `.mp-grid--live`, they come later in
   source, and at equal specificity source order decides — so they still win, and this rule bites only on
   the wide screen where the hole would show. That is the same trap the comment under .mp-grid--soon
   records, written the other way round. */
.mp-grid--live-4{grid-template-columns:repeat(2,minmax(0,1fr))}
/* ITEM 61 S3. The visiting tile takes the top of the range to SEVEN, and seven at repeat(3) is 3 + 3 + 1 —
   a tile sitting alone under two full rows, which is the arrangement the comment above calls "one that
   failed to load" and records that only a screenshot ever notices. Seven belongs at repeat(4): 4 + 3.

   A SEPARATE CLASS OF EQUAL WEIGHT, BESIDE `--live-4` AND FOR THAT RULE'S REASON, AND IT SITS HERE RATHER
   THAN AFTER THE MEDIA QUERIES. Both queries below name `.mp-grid--live`, they come later in source, and at
   equal specificity source order decides — so they still win on a phone and a tablet, and this rule bites
   only on the wide screen where the hole would show. Written after them it would beat both and pin a phone
   at four columns.

   ITEM 62 TOOK IT BACK TO THREE, AND THE OWNER ACCEPTED THE HOLE RATHER THAN THE CURE. Four across was
   §12.1.7's arithmetic applied without asking what it does to a tile's CONTENTS, and what it does is
   measurable: the live grid is 1132px at every desk width, so four columns are 271px with 229px inside a
   tile's padding. A 46px icon, a 13px gap, a heading and a `white-space:nowrap` pill do not fit 229px, so
   EVERY badged tile dropped its badge onto a second line of `.mp-feat__top` — and the two tiles that carry
   no badge never got that line, so their description began 38px above their neighbours'. That is the fault
   the owner reported off the running app on 2026-09-20, in both halves and in one sentence.

   Three columns are 367px with 325px inside the padding, which every badge fits. The hole on row three
   comes back with them, and it was offered as 1 + 3 + 3 with the first tile spanning — `--live-odd`'s trick
   one band up — and REFUSED: *"honestly, I'd rather have a third row of 3 … the third row would only have
   1 tile"*. So this grid is deliberately 3 + 3 + 1 with a narrow tile alone on the last row, which is the
   one arrangement rule §12.1.7 forbids. It is a decision and not a slip, it is the only one of its kind on
   the site, and `NoPortalTileSitsAloneUnderAFullRow` names it as a single exemption by that count so that
   an EIGHTH tile fails the guard again instead of inheriting the permission. */
.mp-grid--live-7{grid-template-columns:repeat(3,minmax(0,1fr))}
/* The coming-soon row is its OWN modifier since Phase 6a rather than a `:not(.mp-grid--live)`, and that
   is a defect a 360px screenshot found: `.mp-grid:not(.mp-grid--live)` in the 1080px query has higher
   specificity than the plain `.mp-grid` in the 600px one, so the four tiles stayed two-wide on a phone
   and the whole portal scrolled sideways. Two modifiers of equal weight, and the media queries decide
   by order, which is the only way this stays legible. */
/* repeat(3) since 2026-08-13, tracking the tile count down from four to three ("Subscriptions & dues"
   left the roadmap). The column count has to track the tile count or the row shows a hole, which is the
   arithmetic rule §12.1.7 exists to keep — and only a screenshot ever notices when it slips. */
.mp-grid--soon{grid-template-columns:repeat(3,minmax(0,1fr))}
.mp-feat{
  position:relative;background:var(--surface);border:1px solid var(--line-light);border-radius:15px;
  padding:20px;display:flex;flex-direction:column;gap:12px;
  box-shadow:0 20px 44px -30px rgba(0,0,0,.6);
  transition:transform .15s, box-shadow .15s, border-color .15s;
}
.mp-feat--live:hover{transform:translateY(-3px);border-color:var(--brass)}

/* ITEM 62 — TILES ON ONE ROW AGREE WHERE THEIR TEXT STARTS, AND THE BROWSER DOES IT RATHER THAN A NUMBER
   SOMEBODY CHOSE. Each equal tile is a column of four parts — head, description, status, button — and until
   here each one took its own natural height, so a tile whose head ran to two lines pushed its own text down
   and nothing else's. That is the SECOND half of what the owner reported: the two tiles with no badge (Email
   preferences always; Notifications whenever nothing is unread) have a one-line head where their neighbours
   have two, and their description began 38px higher.

   Three columns settle it at 1280px because nothing wraps at 325px, and they do NOT settle it anywhere else:
   measured on the running app, the step is 38px at 1024px and 38px through the narrow half of the 601-900px
   two-column band — a tablet in portrait — in Chromium and in WebKit alike. It has been there since long
   before the visiting tile, which is why the fix is structural and not another column count.

   SUBGRID, so the four parts are four rows of the grid the tiles sit in and every tile in a row shares them.
   Its cost is stated rather than hidden: on a row where one tile's badge wraps, the tiles with no badge
   reserve that line too, so their head sits further from their description than it would alone. That is the
   arrangement being agreed on, and it is the point.

   `span 4` IS A CONTRACT WITH THE MARKUP, and it has a guard of its own —
   `EveryPortalTileHoldsAsManyPartsAsItsSubgridSpans` — because a fifth part added to one tile would
   silently push its button out of the shared row rather than fail anything. `row-gap` is declared here because a subgrid inherits its parent's gutters otherwise, and the
   parent's 16px is the gap BETWEEN tiles, not between a tile's own parts.

   The wide year tile is excluded by name: it holds one child, not four, and a tile that spans its row has
   nobody to agree with. `.mp-grid--soon`'s tiles are three parts rather than four and get the same treatment
   with their own count, three rows down. */
.mp-grid--live > .mp-feat--live:not(.mp-feat--wide){
  display:grid;grid-row:span 4;grid-template-rows:subgrid;row-gap:12px;
}
.mp-grid--soon > .mp-feat--soon{display:grid;grid-row:span 3;grid-template-rows:subgrid;row-gap:12px}

/* MJ Phase 7 — the seventh tile, spanning the row. Brass-tinted so the row reads as a band rather than as a
   tile that grew, and its inner layout is a flex row that becomes a column on a phone. No flex-basis on any
   child: under the 600px rule this row IS a column, and a basis there becomes a minimum height. */
/* NEUTRAL, not brass-tinted, and that is a screenshot's finding rather than a preference — the third time in
   two phases. `--brass` is #C60000 on the Grammarians palette, so `color-mix(brass 7%)` under a 45%-brass
   border rendered the row as a PALE PINK BOX WITH A RED EDGE, which reads as a warning rather than as the
   page's most inviting tile. `--parchment-2` is the surface every palette moves with it. */
.mp-feat--wide{grid-column:1/-1;background:var(--parchment-2)}
.mp-feat__wide{display:flex;gap:22px;align-items:center;flex-wrap:wrap}
/* flex-basis 0, NOT auto. With auto the body's hypothetical size is its max-content — the description is
   a long sentence — so it claimed 973px of a 1090px row, pushed the figures block clean off the right
   edge (it began AT x=1090 and ran 95px past it, landing under the NEW badge) and bumped the buttons onto
   a second line. It only reads as deliberate when a season has three figures to show; with ONE it reads
   as a broken card, which is the state a real member's first season is in. Basis 0 lets the figures and
   the actions keep the line and gives the body whatever is left, floored by min-width. */
.mp-feat__wideBody{flex:1 1 0;min-width:15rem}
.mp-feat__wideBody .mp-feat__desc{margin-bottom:0}
.mp-feat__figs{display:flex;gap:22px;flex-wrap:wrap;flex:none}
.mp-feat__fig b{
  display:block;font-family:var(--serif);font-size:1.35rem;font-weight:600;color:var(--heading);
  /* lining-nums — see the note on .mp-gcard__v. */
  font-variant-numeric:tabular-nums lining-nums;line-height:1.1;
}
.mp-feat__fig span{
  font-size:.66rem;letter-spacing:.1em;text-transform:uppercase;color:var(--slate-soft);
}
.mp-feat__wideActs{display:flex;gap:9px;flex-wrap:wrap;flex:none}
.mp-feat__wideActs .mp-cta{margin-top:0}
/* Movement is opt-in. A member who has asked their system to stop animating gets the colour
   change and no lift, which is the whole of the information the hover carries. */
@media(prefers-reduced-motion:reduce){
  .mp-feat{transition:border-color .15s}
  .mp-feat--live:hover{transform:none}
}
/* WRAP, because a nowrap badge beside an unshrinkable heading is not a row that always fits. MJ Phase 8
   measured "Coming soon" escaping the "Subscriptions & dues" card by 35px on EVERY palette through the
   two-column window (601-740px), and by 52px on Onyx — far enough there to push the whole portal
   sideways, which is the only reason a viewport probe saw it at all. The icon is flex:none and the badge
   is white-space:nowrap, so the heading is the only item that could give, and its min-content is one long
   word. Wrapping costs nothing at any width where the approved row fits, and is the only fix that does
   not either break a word mid-way or move the badge on screens where it was never wrong. */
.mp-feat__top{display:flex;align-items:flex-start;gap:13px;flex-wrap:wrap}
.mp-feat__top h3{
  font-family:var(--serif);font-size:1.28rem;font-weight:600;margin:2px 0 0;color:var(--heading);
  min-width:0;
}
.mp-icon{
  width:46px;height:46px;border-radius:12px;flex:none;
  display:flex;align-items:center;justify-content:center;font-size:1.35rem;
  background:var(--parchment-2);border:1px solid var(--line-light);
}
.mp-feat--live .mp-icon{background:color-mix(in srgb, var(--brass) 22%, var(--parchment-2))}
.mp-badge{
  margin-left:auto;font-size:.58rem;letter-spacing:.08em;text-transform:uppercase;font-weight:700;
  padding:4px 9px;border-radius:999px;white-space:nowrap;
}
/* The claret is carried by the FILL and the border; the 9px uppercase label on top of them is
   var(--heading). Claret text on a 12% claret tint is 8.6:1 on Heritage and 3.28:1 on Midnight, whose
   claret (#B4637A) is a light pink over a dark surface. It is the same "a token moved and took the pairing
   with it" failure the header note is about, one step further in. */
.mp-badge--count{
  color:var(--heading);background:color-mix(in srgb, var(--claret) 12%, var(--surface));
  border:1px solid color-mix(in srgb, var(--claret) 30%, transparent);
}
.mp-badge--soon{color:var(--slate-soft);background:var(--parchment-2);border:1px solid var(--line-light)}
/* MJ Phase 2: a tile that has just gone live. The fixed ok pair rather than a brass tint, for the reason
   the note above --count gives — a theme token paired with a tint of itself is where contrast dies, and
   the ok/warn/bad triples are the untinted set that measures the same on all eleven palettes. */
.mp-badge--new{color:var(--ok-ink);background:var(--ok-fill);border:1px solid var(--ok-line)}
.mp-feat__desc{color:var(--slate-soft);font-size:.9rem;margin:0}
.mp-feat__status{
  font-size:.82rem;color:var(--slate);display:flex;align-items:center;gap:8px;padding-top:2px;margin:0;
}
.mp-dot{width:6px;height:6px;border-radius:50%;background:var(--brass);flex:none}
.mp-cta{
  margin-top:auto;display:inline-flex;align-items:center;justify-content:center;gap:8px;
  text-decoration:none;font-weight:600;font-size:.88rem;border-radius:10px;padding:11px 16px;
}
.mp-cta--brass{background:var(--brass);color:var(--on-brass)}
.mp-cta--dark{background:var(--ink);color:var(--on-dark)}
.mp-cta--ghost{border:1px solid var(--line-light);color:var(--slate);background:var(--surface)}
.mp-cta--ghost:hover{border-color:var(--brass)}
.mp-cta:focus-visible{outline:2px solid var(--brass);outline-offset:2px}

.mp-feat--soon .mp-icon{filter:saturate(.6)}
.mp-cta--none{
  color:var(--slate-soft);border:1px dashed var(--line-light);background:transparent;cursor:default;
}
.mp-feat--soon::after{
  content:"";position:absolute;inset:0;border-radius:15px;pointer-events:none;
  background:repeating-linear-gradient(135deg,
    transparent, transparent 12px,
    color-mix(in srgb, var(--slate) 4%, transparent) 12px,
    color-mix(in srgb, var(--slate) 4%, transparent) 24px);
}

/* ---- recent updates ---- */
.mp-activity{
  background:var(--surface);border:1px solid var(--line-light);border-radius:15px;
  box-shadow:0 20px 44px -30px rgba(0,0,0,.6);overflow:hidden;margin-bottom:56px;
}
.mp-activity__head{
  display:flex;align-items:center;gap:10px;padding:15px 20px;
  border-bottom:1px solid var(--line-light);background:var(--parchment-2);
}
.mp-activity__head h3{font-family:var(--serif);font-size:1.1rem;margin:0;font-weight:600;color:var(--heading)}
/* var(--slate), not var(--brass-ink): the brass meant for light surfaces is 3.27:1 on Heritage's own
   --parchment-2, and this is 13px text. The affordance is carried by the weight, the arrow and the
   underline on hover. */
.mp-activity__head a{margin-left:auto;font-size:.82rem;color:var(--slate);text-decoration:none;font-weight:600}
.mp-activity__head a:hover{text-decoration:underline}

.mp-arow{
  display:flex;align-items:center;gap:14px;padding:13px 20px;
  border-top:1px solid var(--line-light);text-decoration:none;color:inherit;
}
.mp-arow:first-of-type{border-top:0}
.mp-arow:hover{background:color-mix(in srgb, var(--brass) 6%, var(--surface))}
.mp-arow:focus-visible{outline:2px solid var(--brass);outline-offset:-2px}
.mp-arow__icon{
  width:34px;height:34px;border-radius:9px;background:var(--parchment-2);
  display:flex;align-items:center;justify-content:center;flex:none;
}
.mp-arow__body{flex:1;min-width:0}
.mp-arow__body b{font-weight:600;color:var(--heading)}
.mp-arow__body span{display:block;font-size:.82rem;color:var(--slate-soft)}
.mp-arow__when{
  font-size:.76rem;color:var(--slate-soft);white-space:nowrap;font-variant-numeric:tabular-nums;
}
.mp-arow--unread{background:color-mix(in srgb, var(--brass) 5%, var(--surface))}
.mp-arow--unread .mp-arow__icon{background:color-mix(in srgb, var(--brass) 20%, var(--surface))}
.mp-arow--empty{cursor:default}

@media(max-width:1080px){
  /* Four across needs the room for it; below that the coming-soon row folds to two. The LIVE row stays
     three until 900px — it has three tiles per row, not four, since Phase 6a. */
  .mp-grid--soon{grid-template-columns:repeat(2,minmax(0,1fr))}

  /* AND THE COMING-SOON ROW HAS THE SAME HOLE, WHICH THE SAME SCREENSHOT FOUND ONE GRID DOWN. Three
     placeholder tiles at two across is 2 + 1, and `.mp-grid--soon`'s own comment above already says what
     that is: "the column count has to track the tile count or the row shows a hole ... and only a
     screenshot ever notices when it slips". It has been three tiles since 2026-08-13.

     THIS ONE CAN ASK CSS TO COUNT AND THE LIVE GRID CANNOT, which is the whole reason they are spelled
     differently. `:has(> :nth-child(odd):last-child)` means "the last child sits at an odd position", i.e.
     there is an odd number of them - and it is SELF-MAINTAINING, so a fourth coming-soon tile needs no
     second edit. The live grid's children are conditional and its last child may be the full-width year
     tile, so the same selector would be answering a different question there; its parity comes from the
     page, which is the only thing that knows the count. */
  .mp-grid--soon:has(> .mp-feat:nth-child(odd):last-child) > .mp-feat:first-child{grid-column:1 / -1}

  /* IT SITS IN THIS QUERY AND NOT THE 900px ONE BECAUSE THAT IS WHERE THIS ROW ACTUALLY FOLDS, and the
     first draft got it wrong: written beside the live row's parity rule below, it left 1024px — between
     the two breakpoints — still drawing 2 + 1, and the claim said so. */
}
@media(max-width:900px){
  .mp-hero__wrap{grid-template-columns:1fr;gap:24px}
  .mp-glance{grid-template-columns:repeat(2,1fr)}
  .mp-grid,.mp-grid--live,.mp-grid--soon{grid-template-columns:repeat(2,minmax(0,1fr))}

  /* ITEM 61 S3, AND THIS ONE WAS ALREADY WRONG BEFORE THAT SLICE TOUCHED IT.
     Everything rule §12.1.7 has ever said about holes was said about the WIDE screen, and this band —
     601px to 900px, two columns — was never checked. An ODD number of equal tiles at two across is
     2 + 2 + 1, the same lone tile under full rows the comment beside `.mp-grid--live-4` calls "one that
     failed to load". A guest ritualist has had five tiles since item 49 slice 2 and has been reading that
     hole on a tablet ever since; item 61 S3's seventh tile makes it 2 + 2 + 2 + 1 for a full member too.

     THE FIRST TILE TAKES THE ROW RATHER THAN THE LAST, and that is a CSS fact before it is a taste:
     "the last equal tile" cannot be named here, because the full-width year tile follows them and is
     conditional — so it is `:last-child` for one reader and `:nth-last-child(2)` for another. `:first-child`
     is the one position that means the same thing for everybody. It also puts the festive board, the tile
     a member opens most, across the top of a tablet, which is a promotion rather than a consolation.

     The page supplies the parity because the page is what counts the tiles (`LiveTileCount`); CSS cannot
     count children that are conditionally rendered. */
  .mp-grid--live-odd > .mp-feat--live:first-child{grid-column:1 / -1}

}
@media(max-width:600px){
  .mp-hero__wrap{padding:32px 24px 40px}
  .mp-glance{grid-template-columns:1fr 1fr;margin-top:-18px}
  .mp-grid,.mp-grid--live,.mp-grid--soon{grid-template-columns:1fr}
  .mp-meeting__acts{flex-direction:column}

  /* MR Phase 3. This used to be display:none, which is the difference between a layout that reflows and
     one that DELETES information: "yesterday" against a notice is how a member decides whether they have
     already dealt with it, and it was the phone that lost it. The row wraps instead and the timestamp
     takes the third line, indented to the icon's width (34px + the 14px gap) so it reads as belonging to
     the update above it rather than as a new one. */
  .mp-arow{flex-wrap:wrap;align-items:flex-start}
  .mp-arow__when{flex-basis:100%;margin-left:48px;margin-top:3px}

  /* A 44px minimum on anything a thumb has to hit (WCAG 2.5.5). The desktop padding gives ~40px, which
     is a miss often enough to be noticed on the one control most members press: "Book festive board". */
  .mp-cta,.mp-meeting__act{min-height:44px}

  /* MJ Phase 7's wide tile: one column at 600px, and its two actions full width. `flex:1 1 auto` on the
     body, never a basis — a basis on a child of a column is a minimum HEIGHT, which is the defect Phase 6b
     shipped in three boxes at once. */
  .mp-feat__wide{flex-direction:column;align-items:stretch;gap:14px}
  .mp-feat__wideBody{min-width:0}
  .mp-feat__figs{gap:18px}
  .mp-feat__wideActs{flex-direction:column}
  .mp-feat__wideActs .mp-cta{width:100%;justify-content:center}
}

/* ==========================================================================================
   MJ Phase 2 — the lodge's papers (docs/members-journey-plan.md §12.3, approved mockup screens
   1, 1b and 5).

   Two families:
     .ld-*  the members' archive at /Members/Summons, AND NOTHING ELSE. Item 58 renamed the admin
            half of this prefix to .pap-, because `.ld-` had been two unrelated families sharing a
            name, interleaved across one stretch of this file: eleven classes here, eight on
            /Admin/Documents, and a reader of either had to check which. The one genuinely shared
            thing is .ld-chip and its --ok/--warn modifiers, kept below under a banner saying so.
     .ma-*  /Admin/MembersArea's three settings cards.

   Every colour is a THEME token. The status pairs (--ok/--warn/--bad) are the fixed, deliberately
   untinted set, exactly as the mockup draws them. .btn-ghost is NOT used on any light surface here:
   it forces color:var(--on-dark), the near-white token meant for dark bands, and .admin-sub's
   override is what rescues it on the two admin pages — the members' page never reaches for it.
   ========================================================================================== */

/* ---- shared chips: the members' archive here, and /Admin/Documents's .pap-row__state. The ONE
       part of `.ld-` that item 58 deliberately did not rename, because it is genuinely both. ---- */
.ld-chip{
  display:inline-flex;align-items:center;gap:6px;
  font-size:.62rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
  border-radius:999px;padding:4px 10px;white-space:nowrap;
}
.ld-chip--body{
  color:var(--heading);
  background:color-mix(in srgb,var(--brass) 16%,var(--surface));
  border:1px solid color-mix(in srgb,var(--brass) 45%,transparent);
}
.ld-chip--neutral{color:var(--slate-soft);background:var(--parchment-2);border:1px solid var(--line-light)}
.ld-chip--ok{color:var(--ok-ink);background:var(--ok-fill);border:1px solid var(--ok-line)}
.ld-chip--warn{color:var(--warn-ink);background:var(--warn-fill);border:1px solid var(--warn-line)}

/* ---- /Members/Summons: hero extras ---- */

/* The breadcrumb sits in the hero's eyebrow, on var(--ink). --brass-soft, never --brass: brass as
   text on ink measures 1.75:1 on the College palette. */
.ld-crumb{color:var(--brass-soft);text-decoration:none}
.ld-crumb:hover{text-decoration:underline}

.ld-fine{
  margin:12px 0 0;font-size:.72rem;
  color:color-mix(in srgb,var(--on-dark) 60%,transparent);
}

/* ---- /Members/Summons: the filter row ---- */
.ld-toolbar{
  display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin:26px 0 4px;
}
.ld-toolbar__form{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
/* ITEM 61'S SWEEP FOUND THESE, AND THEY ARE ITEM 44'S DEFECT ON A PAGE ITEM 44 NEVER MEASURED.
   /Members/Summons is a members' page, so §44's roster -- which drove the 39 ADMIN routes -- never
   reached it, and no phone sweep existed for it until this one. Both controls came back at 36-37px at
   EVERY width in BOTH engines.

   FIXED HERE RATHER THAN BOOKED, which is item 55's own sentence: "leaving eleven short so that item 44
   can raise them is shipping a known fault twice." Two rules, and the page these controls sit on is the
   page item 61 put a door on.

   `height` AND NOT `min-height` ON THE SELECT, AND THAT IS THE WHOLE REPAIR RATHER THAN A PREFERENCE.
   WebKit ignores `min-height` on a <select> -- measured again here at 21px against Chromium's 36 -- which
   is the finding [[the-suite-is-chromium-only-and-the-owner-reads-on-an-iphone]] recorded and the reason
   the owner's engine has to be in the net at all. */
.ld-select,.ld-search{
  font:inherit;font-size:.86rem;color:var(--slate);background:var(--surface);
  border:1px solid var(--line-light);border-radius:9px;padding:8px 12px;
  min-height:44px;
}

select.ld-select{
  height:44px;
}
.ld-search{min-width:200px}
.ld-select:focus-visible,.ld-search:focus-visible{outline:2px solid var(--brass);outline-offset:2px}

/* THE SEARCH PRESS BESIDE THEM, 74x37 at every width. Scoped to this toolbar and not to `.btn--s`
   itself, which is item 55's gate Q12 answered once more: `.btn--s` is also the widget editor's stepper
   size, and a 44px floor on that class would repaint every minus and plus in a dense settings panel
   through a rule nobody had looked at on a screenshot. */
.ld-toolbar .btn--s{
  min-height:44px;
}

.ld-chips{display:flex;gap:6px;flex-wrap:wrap}
.ld-fchip{
  font:inherit;font-size:.78rem;font-weight:600;text-decoration:none;
  color:var(--slate);background:var(--surface);
  border:1px solid var(--line-light);border-radius:999px;padding:6px 14px;
}
.ld-fchip:hover{border-color:var(--brass)}
/* The pressed chip is a BRASS FILL under --on-brass, which is the pairing the theme rules sanction.
   It was var(--ink)/var(--on-dark) first — the mockup's own colours — and a Midnight screenshot showed
   why that is wrong on a shipped page and not in a mockup: Midnight's --ink (#0B0F19) is darker than its
   --parchment (#131824), so the pressed pill DISAPPEARED into the page and the filter looked unset. The
   mockup draws on a light body, where the same rule reads as a strong dark pill. Measured, not reasoned. */
.ld-fchip.is-on{background:var(--brass);color:var(--on-brass);border-color:var(--brass)}
.ld-fchip:focus-visible{outline:2px solid var(--brass);outline-offset:2px}

.ld-empty{
  color:var(--slate-soft);font-size:.92rem;
  background:var(--parchment-2);border:1px solid var(--line-light);border-radius:12px;
  padding:18px 20px;margin:22px 0;
}

/* ---- /Members/Summons: seasons and meeting cards ---- */
.ld-season{
  font-family:var(--serif);font-size:1.15rem;color:var(--heading);
  margin:30px 0 12px;display:flex;align-items:center;gap:12px;
}
.ld-season::after{content:"";flex:1;height:1px;background:var(--line-light)}

/* ITEM 35 — a BODY's heading inside a season, and the same block above a band of the ritual room
   (gate Q1a/Q2a/Q5c, approved drawing docs/mockups/chapter-papers-gate.html plates B and D).

   ONE new rule and no new colour, no new box and no new token: .ld-season's own shape a step down in
   weight, because a body is a subdivision of a season rather than a peer of it. The name says what it is,
   and the <b> inside it carries the heading colour so the rule reads as a caption with a name in it. It is
   deliberately shared by the archive and the ritual room — the two screens are asking the same question of
   the same table, and a second copy of this under an .rr- name is how two headings for one fact come to
   drift apart. */
.ld-body{
  display:flex;align-items:center;gap:10px;margin:22px 0 10px;
  font-family:var(--serif);font-size:.98rem;color:var(--slate-soft);
}
.ld-body::after{content:"";flex:1;height:1px;background:var(--line-light)}
.ld-body b{color:var(--heading);font-weight:600}

.ld-mtg{
  background:var(--surface);border:1px solid var(--line-light);border-radius:15px;
  box-shadow:0 20px 44px -30px rgba(0,0,0,.6);
  margin-bottom:16px;overflow:hidden;
}
.ld-mtg__head{
  display:flex;align-items:center;gap:16px;padding:16px 20px;flex-wrap:wrap;
  border-bottom:1px solid var(--line-light);background:var(--parchment-2);
}
.ld-mtg__date{
  font-family:var(--serif);font-size:1.05rem;color:var(--heading);
  min-width:104px;line-height:1.2;margin:0;
}
.ld-mtg__date b{display:block;font-size:1.35rem;font-variant-numeric:tabular-nums}
.ld-mtg__title{flex:1;min-width:200px}
.ld-mtg__title h3{font-size:1.22rem;margin:0;display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.ld-mtg__title p{margin:2px 0 0;font-size:.82rem;color:var(--slate-soft)}
.ld-mtg__cal{
  font-size:.82rem;font-weight:600;color:var(--slate);text-decoration:none;
  border:1px solid var(--line-light);border-radius:9px;padding:8px 13px;background:var(--surface);
}
.ld-mtg__cal:hover{border-color:var(--brass)}
.ld-mtg__cal:focus-visible{outline:2px solid var(--brass);outline-offset:2px}

.ld-doc{display:flex;align-items:center;gap:14px;padding:13px 20px;border-top:1px solid var(--line-light)}
.ld-doc:first-of-type{border-top:0}
.ld-doc:hover{background:color-mix(in srgb,var(--brass) 6%,var(--surface))}
.ld-doc__kind{min-width:92px}
.ld-doc__body{flex:1;min-width:0}
.ld-doc__body b{display:block;font-weight:600;color:var(--heading)}
.ld-doc__body span{display:block;font-size:.8rem;color:var(--slate-soft)}
.ld-doc__get{font-size:.82rem;font-weight:600;color:var(--slate);text-decoration:none;white-space:nowrap}
.ld-doc__get:hover{text-decoration:underline}
.ld-doc__get:focus-visible{outline:2px solid var(--brass);outline-offset:2px}

.ld-doc--pending{
  color:var(--slate-soft);font-size:.85rem;padding:11px 20px;margin:0;
  border-top:1px dashed var(--line-light);
}

.ld-lockline{display:flex;align-items:center;gap:10px;color:var(--slate-soft);font-size:.8rem;margin:26px 0 56px}

/* ==========================================================================================
   ITEM 58 — /Admin/Documents, THE LODGE PAPERS DESK (approved gate revision 1,
   https://claude.ai/artifact/Nj64TjkxdZkU4N1vUApMa5, all six decisions on the recommendation).

   .pap-* IS THIS ADMIN PAGE AND ITS TWO PARTIALS, AND NOTHING ELSE. Before item 58 the `.ld-`
   prefix meant two unrelated things interleaved in one stretch of this file: eleven classes that
   belong only to the members' archive at /Members/Summons, and eight that belong only here. A
   reader of either half had to check which. The admin half is `.pap-` now, so above this block
   `.ld-` means the members' archive and nothing else — except `.ld-chip` and its --ok/--warn
   modifiers, which are genuinely shared and stay where they are under a banner that says so.

   THE SHAPE: the list is the page. A toolbar carries search, four state chips and both forms as
   <details> drawers that arrive SHUT (decision 1), and a row is two lines and two controls
   instead of five and six (decision 3 moved Replace and Remove into the editor).

   NO SCRIPT IS REQUIRED BY ANY OF IT. Every drawer and every editor is a <details>, which is the
   browser's own disclosure; the search box and the chips are the one enhancement, and they are
   rendered `hidden` and revealed by admin-documents-filter.js, so a reader with script blocked
   meets no control that does nothing rather than a dead one.
   ========================================================================================== */

/* The page's one vertical rhythm. It replaces three `style="margin-top:2rem"` attributes, which
   were the thing that actually made this page's CSS feel scattered: inline spacing on a page that
   has a stylesheet is a rule nobody can find. */
.pap-stack{display:flex;flex-direction:column;gap:1.35rem}

/* ---- the toolbar ---- */
.pap-bar{
  display:flex;align-items:center;gap:.7rem;flex-wrap:wrap;
  background:var(--surface);border:1px solid var(--line-light);border-radius:10px;
  padding:.7rem .8rem;
}
/* Search and the chips together, because they are one question asked two ways and the script that
   powers them reveals or leaves both. */
.pap-bar__find{display:flex;align-items:center;gap:.7rem;flex-wrap:wrap;flex:1 1 20rem;min-width:0}
.pap-bar__search{position:relative;flex:1 1 11rem;min-width:9rem}
.pap-bar__search .admin-input{padding-left:2rem}
.pap-bar__search svg{
  position:absolute;left:.6rem;top:50%;transform:translateY(-50%);
  color:var(--slate-soft);pointer-events:none;
}
.pap-bar__acts{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;margin-left:auto}

.pap-chips{display:flex;gap:.3rem;flex-wrap:wrap}
.pap-chip{
  font:inherit;font-size:.76rem;font-weight:600;line-height:1;cursor:pointer;
  padding:7px 11px;border-radius:999px;
  background:var(--parchment);color:var(--slate);border:1px solid var(--line-light);
}
/* Item 64 slice 3 - the chips are LINKS now rather than toggle buttons, so the on state is a class the
   server writes rather than one a script sets. The two `[aria-pressed]` rules below stay beside it: same
   paint, same tokens, and deleting them would repaint nothing while costing the next reader the history.
   `cursor:pointer` on .pap-chip is now redundant on an anchor and harmless. */
.pap-chip--on{background:var(--brass);color:var(--on-brass);border-color:var(--brass)}
.pap-chip--on .pap-chip__n{opacity:.75}
a.pap-chip{text-decoration:none;display:inline-flex;align-items:center;gap:.35rem;min-height:44px}
.pap-chip:hover{border-color:var(--brass)}
.pap-chip:focus-visible{outline:2px solid var(--brass);outline-offset:2px}
.pap-chip[aria-pressed="true"]{background:var(--brass);color:var(--on-brass);border-color:var(--brass)}
/* tabular-nums so a count going 8 -> 11 does not move the word beside it. */
.pap-chip__n{opacity:.62;margin-left:.35rem;font-variant-numeric:tabular-nums}
.pap-chip[aria-pressed="true"] .pap-chip__n{opacity:.75}

/* ---- a drawer ---- */
/* Shut it is a button in the toolbar strip; open it takes the whole strip. flex-basis does both,
   so there is one markup shape and no script. */
.pap-drawer{flex:0 0 auto}
.pap-drawer>summary{
  display:inline-flex;align-items:center;gap:.4rem;cursor:pointer;list-style:none;
  font:inherit;font-size:.78rem;font-weight:600;line-height:1;
  padding:.45rem .85rem;border-radius:999px;
  color:var(--heading);background:var(--surface);border:1px solid var(--line-light);
}
.pap-drawer>summary::-webkit-details-marker{display:none}
.pap-drawer>summary:hover{border-color:var(--brass);background:var(--parchment)}
.pap-drawer>summary:focus-visible{outline:2px solid var(--brass);outline-offset:2px}
/* The one drawer that starts something rather than settling something. */
.pap-drawer--go>summary{background:var(--brass);color:var(--on-brass);border-color:var(--brass)}
.pap-drawer--go>summary:hover{background:var(--brass-soft);color:var(--ink-deep);border-color:var(--brass-soft)}
.pap-drawer__caret{width:.95rem;height:.95rem;flex:0 0 auto;transition:transform .15s}
.pap-drawer[open]>summary .pap-drawer__caret{transform:rotate(90deg)}
.pap-drawer[open]{flex:1 1 100%}
.pap-drawer[open]>summary{margin-bottom:.6rem}
.pap-drawer__body{border-top:1px solid var(--line-light);padding-top:.9rem}
@media(prefers-reduced-motion:reduce){.pap-drawer__caret{transition:none}}

/* ---- the upload form, and the field primitive both forms share ---- */
/* ONE grid and ONE field, where the upload form and the row editor used to have two of each doing
   the same job (.ld-upload__row + .ld-upload__field, and .ld-edit__grid + .ld-edit__field). */
.pap-form,.pap-edit__grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.85rem 1.1rem}
.pap-field{display:grid;gap:.3rem;min-width:0;align-content:start}
.pap-field--wide{grid-column:1/-1}
.pap-field>label{font-size:.8rem;font-weight:600;color:var(--heading)}
.pap-field__hint{margin:.1rem 0 0;font-size:.76rem;color:var(--slate-soft);line-height:1.45}

/* The file input, given a box of its own: it is the one field on the form without which the press
   does nothing, and a native file input among five text fields reads as the least of them. */
.pap-file{
  grid-column:1/-1;
  display:flex;align-items:center;gap:.7rem;flex-wrap:wrap;
  border:1px dashed var(--brass);border-radius:8px;background:var(--parchment);
  padding:.7rem .8rem;
}
.pap-file>label{font-size:.8rem;font-weight:600;color:var(--heading);flex:0 0 auto}
.pap-file input{flex:1 1 12rem;min-width:0}
.pap-file__note{font-size:.74rem;color:var(--slate-soft);margin:0 0 0 auto}

/* The conditional fields, gathered. Before item 58 the visiting lodge's name sat loose among the
   ordinary fields and the two ritual ones were in a fieldset of their own, so the form said
   "conditional" once for two of the three. All three are here now.

   NEUTRAL, never brass-tinted, and that is a screenshot's finding this project has made twice
   (.ld-ritual and .ld-edit__ritual both carried the note): --brass is #C60000 on the Grammarians
   palette, so a brass wash under a brass border renders as a pink box with a red border, which on
   a form reads as validation failure. */
.pap-sub{
  grid-column:1/-1;margin:0;padding:.85rem 1rem 1rem;
  border:1px solid var(--line-light);border-radius:8px;background:var(--parchment-2);
  display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.75rem 1.1rem;
}
.pap-sub>legend,.pap-sub__head{
  grid-column:1/-1;
  display:flex;align-items:baseline;gap:.5rem;flex-wrap:wrap;
  font-size:.78rem;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
  color:var(--slate-soft);padding:0;
}

/* ---- the audience block ---- */
/* The roles are a WRAPPING ROW and the brethren a SCROLLING COLUMN, which is item 49 slice 5's
   measurement kept: the roles are seven or eight short words the lodge invented, and the brethren
   are however many the lodge has, which is the number that grows. Left to wrap, one picker would
   be several screens tall and the Save button under it would be off the bottom of the page. */
.pap-aud{display:grid;gap:.5rem}
/* THE SECOND LABEL IN THIS BLOCK IS A BARE ONE. "Who may see it" sits in a .pap-field and gets that
   rule; "Or name brethren" is inside the audience block itself, because it names the second half of one
   answer rather than a field of its own — so without this it rendered at panel size next to a small bold
   one. A screenshot is the only thing that finds a missing rule: every claim on the page was green. */
.pap-aud>label{font-size:.8rem;font-weight:600;color:var(--heading)}
.pap-aud__set{display:flex;flex-wrap:wrap;gap:.4rem .9rem;padding-top:.2rem}
.pap-aud__set .admin-check{font-weight:500;font-size:.85rem}
/* max-height in rem because a row count is not a thing CSS can express here; at .85rem text that
   is about six brethren before it scrolls, which is enough to see that the list IS a list.
   overflow-y:auto makes overflow-x auto too [[overflow-hidden-stops-a-reader-not-a-script]], which
   is why a long display name wraps rather than pushing a sideways scrollbar onto the panel. */
.pap-aud__people{
  display:flex;flex-direction:column;gap:.15rem;
  max-height:11rem;overflow-y:auto;
  padding:.45rem .6rem;
  border:1px solid var(--line-light);border-radius:6px;background:var(--parchment-2);
}
.pap-aud__people .admin-check{font-weight:400;font-size:.85rem;overflow-wrap:anywhere}
.pap-aud__empty{margin:0;font-size:.78rem;color:var(--slate-soft)}

/* THE COUNT. aria-live is on the element, so a Secretary who cannot see it hears the number
   change; this rule only has to keep it from reading as another hint. It is the one line here
   that answers "and how many is that". */
.pap-reach{
  display:block;margin:.4rem 0 0;
  font-size:.8rem;font-weight:600;color:var(--heading);
}

/* ---- the form's own action row ---- */
.pap-acts{grid-column:1/-1;display:flex;align-items:center;gap:.75rem;flex-wrap:wrap;margin-top:.3rem}
.pap-acts__note{flex:1 1 16rem;margin:0;font-size:.76rem;color:var(--slate-soft);line-height:1.5}

/* ==========================================================================================
   THE LIST, WHICH IS NOW THE PAGE
   ========================================================================================== */

/* A rule with a word on it. Drafts are pinned above published papers (decision 2) and a pin with
   nothing naming it is just a different order. */
.pap-group{
  display:flex;align-items:center;gap:.6rem;margin:0 0 .35rem;
  font-size:.66rem;font-weight:700;letter-spacing:.14em;text-transform:uppercase;
  color:var(--slate-soft);
}
.pap-group::after{content:"";flex:1;height:1px;background:var(--line-light)}

.pap-list{list-style:none;margin:0 0 1.1rem;padding:0}
.pap-list:last-child{margin-bottom:0}
.pap-row{border-bottom:1px solid var(--line-light)}
.pap-row:last-child{border-bottom:0}

/* The row is a FLEX THAT WRAPS, and the editor's flex-basis is what gives it a line of its own
   when it opens. That is the whole mechanism: no script, no second markup shape. */
.pap-row__top{display:flex;align-items:flex-start;gap:.75rem;flex-wrap:wrap;padding:.62rem .2rem}
.pap-row:hover>.pap-row__top{background:color-mix(in srgb,var(--brass) 5%,var(--surface))}
.pap-row__state{flex:0 0 5.6rem;padding-top:.1rem}
.pap-row__main{flex:1 1 13rem;min-width:0;display:grid;gap:.15rem}
.pap-row__title{font-weight:600;font-size:.92rem;color:var(--heading);text-decoration:none;overflow-wrap:anywhere}
.pap-row__title:hover{text-decoration:underline;text-decoration-color:var(--brass)}
.pap-row__title:focus-visible{outline:2px solid var(--brass);outline-offset:2px}
.pap-row__facts{font-size:.76rem;color:var(--slate-soft)}
.pap-row__pills{display:flex;flex-wrap:wrap;gap:.3rem;margin-top:.15rem}
.pap-row__acts{flex:0 0 auto;display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;padding-top:.05rem}

/* The draft row's Publish and the box that qualifies it. A rule is needed rather than nothing:
   .admin-check is display:flex, so on a plain block <form> the label would take a line of its own
   and the button would sit under it, in a row whose other controls are all side by side. */
.pap-publish{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap;margin:0}
.pap-publish .admin-check{font-size:.78rem;font-weight:500;color:var(--slate-soft)}

/* The two facts worth acting on. A CLOSED audience is tinted and an open one is not, so a
   restricted paper reads at a glance; "nobody has been told" is the one that wants doing, so it
   takes the fixed warn triple. Background, ink and border all come from that one triple, never
   mixed with a palette token — the rule in this file's own header, guarded by
   StylesheetPairingTests. */
.pap-pill{
  display:inline-flex;align-items:center;gap:.3rem;white-space:nowrap;
  font-size:.7rem;padding:3px 9px;border-radius:999px;
  background:var(--parchment);color:var(--slate-soft);border:1px solid var(--line-light);
}
.pap-pill--closed{background:var(--parchment-2);color:var(--brass-ink);border-color:var(--brass-soft);font-weight:600}
.pap-pill--todo{background:var(--warn-fill);color:var(--warn-ink);border-color:var(--warn-line);font-weight:600}

/* AN AUTHOR `display` BEATS `[hidden]`, EXACTLY AS IT BEATS THE UA RULE THAT HIDES A CLOSED
   <details> — the trap item 46 slice 3 spent a session on [[an-author-display-beats-a-closed-details]].
   The filter script hides rows, lists and group rules by setting `hidden`, and three of the four
   selectors below carry a `display` of their own, so without this rule the chips would visibly do
   nothing. Every element the script can hide is named here. */
.pap-bar__find[hidden],.pap-group[hidden],.pap-list[hidden],.pap-row[hidden],.pap-empty[hidden]{display:none}

/* What the filter says when it has hidden everything. Without it the page answers a search that
   matches nothing with a blank, which reads as a page that failed to load. */
.pap-empty{margin:.5rem 0 0;font-size:.84rem;font-style:italic;color:var(--slate-soft)}

/* ==========================================================================================
   THE ROW EDITOR (and the defaults panel's, which is the same disclosure one level up)
   ========================================================================================== */

/* Shut, the editor is a pill sitting in the row's action group. Open, flex-basis gives it the
   whole width. Both from one declaration pair. */
.pap-edit{flex:0 0 auto}
/* Open, it takes the whole width — and the control STAYS WHERE IT WAS. Without the text-align the pill
   jumped from the row's right edge to the panel's left edge the moment it was pressed, so "Close" was
   nowhere near where "Edit" had been. The body below puts the alignment back for its own contents. */
.pap-row__top>.pap-edit[open]{flex:1 1 100%;text-align:right}

/* The summary IS the Edit button. list-style and the webkit marker are both cleared, because a
   triangle beside a pill reads as two controls. */
.pap-edit__toggle{
  display:inline-flex;align-items:center;gap:.35rem;cursor:pointer;list-style:none;
  font:inherit;font-size:.78rem;font-weight:600;line-height:1;
  padding:.4rem .85rem;border-radius:999px;
  color:var(--brass-ink);background:transparent;border:1px solid var(--brass);
}
.pap-edit__toggle::-webkit-details-marker{display:none}
.pap-edit__toggle:hover{background:var(--parchment-2)}
.pap-edit__toggle:focus-visible{outline:2px solid var(--brass);outline-offset:2px}

/* Which word the control shows is the disclosure state, so CSS answers it rather than script. */
.pap-edit__toggle-open{display:none}
.pap-edit[open]>.pap-edit__toggle>.pap-edit__toggle-shut{display:none}
.pap-edit[open]>.pap-edit__toggle>.pap-edit__toggle-open{display:inline}

.pap-edit__body{
  text-align:left;
  margin:.6rem 0 .5rem;padding:.9rem 1rem 1rem;
  border:1px solid var(--line-light);border-radius:9px;background:var(--parchment);
}
.pap-edit__note{margin:.25rem 0 0;font-size:.76rem;color:var(--slate-soft);line-height:1.5}
/* The foot note sits inside the grid, under the action row, so it has to be told to span it. */
.pap-edit__grid>.pap-edit__note{grid-column:1/-1}

.pap-edit__acts{
  grid-column:1/-1;display:flex;align-items:center;gap:.6rem;flex-wrap:wrap;
  margin-top:.5rem;padding-top:.75rem;border-top:1px solid var(--line-light);
}
.pap-edit__gap{flex:1 1 auto}
.pap-edit__cancel{font-size:.8rem;color:var(--slate-soft);text-decoration:none;border-bottom:1px solid var(--line-light)}
.pap-edit__cancel:hover{color:var(--slate)}
.pap-edit__cancel:focus-visible{outline:2px solid var(--brass);outline-offset:2px}

/* Replace and Remove, which decision 3 brought in off the row. They sit AFTER the gap, away from
   Save: one of them destroys the file and the other destroys the paper, and neither should be a
   near-miss for the button an operator presses every time. */
.pap-edit__file{display:inline-flex;align-items:center;gap:.4rem;font-size:.76rem;color:var(--slate-soft)}
.pap-edit__file input{max-width:11rem;font-size:.74rem;min-width:0}
/* --claret with --bad-fill would be a palette ink on a fixed status wash, which is the pairing
   this stylesheet's header forbids. The wash is a tint of the button's OWN colour instead. */
.pap-danger{
  font:inherit;font-size:.76rem;font-weight:600;line-height:1;cursor:pointer;
  padding:.4rem .85rem;border-radius:999px;
  color:var(--claret);background:transparent;border:1px solid var(--claret);
}
.pap-danger:hover{background:color-mix(in srgb,var(--claret) 8%,var(--surface))}
.pap-danger:focus-visible{outline:2px solid var(--claret);outline-offset:2px}

/* ---- the defaults panel ---- */
/* Item 49 slice 4's seven rows, denser and inside a drawer. The ELEMENT TREE IS UNCHANGED: five
   E2E cases reach into this panel by hook, so item 58 restyles it rather than rebuilding it as a
   real <table>, which is a departure from the gate's drawing and the reason is that the panel's
   length stopped costing the page anything the moment it went behind a shut drawer. */
.pap-defaults{margin:0;padding:0;list-style:none}
.pap-defaults>li{
  display:flex;align-items:flex-start;gap:.75rem;flex-wrap:wrap;
  padding:.55rem .2rem;border-bottom:1px solid var(--line-light);
}
.pap-defaults>li:last-child{border-bottom:0}
.pap-defaults__info{flex:1 1 15rem;min-width:0;display:grid;gap:.1rem}
.pap-defaults__info>strong{font-size:.88rem;color:var(--heading)}
.pap-defaults .pap-reach{margin:.15rem 0 0;font-size:.76rem}
/* The same alignment the row editor needs, one level up: shut, Change sits at the row's right edge, so
   open, Close has to be in the same place rather than at the panel's left. */
.pap-defaults .pap-edit[open]{flex:1 1 100%;text-align:right}

/* ==========================================================================================
   PHONE
   ========================================================================================== */
@media(max-width:960px){
  .pap-form,.pap-edit__grid,.pap-sub{grid-template-columns:1fr}
}

@media(max-width:640px){
  .pap-bar{gap:.55rem}
  .pap-bar__find{flex:1 1 100%}
  .pap-bar__search{flex:1 1 100%}
  .pap-bar__acts{width:100%;margin-left:0}
  .pap-bar__acts .pap-drawer{flex:1 1 auto}
  .pap-bar__acts .pap-drawer>summary{width:100%;justify-content:center}

  /* The row REFLOWS rather than compressing: at 360px the title had about 60px to wrap into and the
     screenshot showed the buttons sitting on top of the words.

     THE INFORMATION GOES FIRST AND THE CONTROLS SECOND, which is the answer the members' archive row
     and the old admin row both already give. Drawing it the other way up — chip and controls first,
     title last — is what item 58's first build did, and the screenshot showed the Edit pill stranded
     alone on a line between the Publish button and the title it belongs to. */
  .pap-row__main{flex:1 1 100%;order:1}

  /* THREE LINES, and the third is deliberate rather than a wrap. The chip and the editor toggle pair
     off on one line, and the publish control takes the next whole one: at 390px "DRAFT", a checkbox
     reading "Email members" and a Publish button already fill the width, so left to wrap the Edit pill
     ended up stranded on a line of its own with nothing beside it. */
  .pap-row__state{flex:0 0 auto;order:2}
  .pap-row__top>.pap-edit{order:3;margin-left:auto}
  .pap-row__acts{order:4;flex:1 1 100%}

  /* 44px minimum on anything a thumb has to hit (WCAG 2.5.5). */
  .pap-chip,.pap-edit__toggle,.pap-danger,.pap-drawer>summary{min-height:44px}
  .pap-aud__set .admin-check,.pap-aud__people .admin-check{min-height:44px}
  .pap-edit__acts .btn,.pap-acts .btn{min-height:44px}
  .pap-publish .admin-check{min-height:44px}

  /* Taller on a phone, because every row inside it is now a 44px touch target and six rows of 44
     is 264px before the box is worth scrolling at all. */
  .pap-aud__people{max-height:15rem}
}

/* The defaults panel's row still wraps so its editor can take a line of its own. */
.admin-list__item--edits{flex-wrap:wrap}


/* ---- /Admin/MembersArea ---- */
/* STRETCH, not start — and minmax(0,1fr), not a bare 1fr.
   With align-items:start every card kept its own height, so the row containing the Journey panel (which
   carries two sections, the switch AND the maintainer roles) left its two neighbours stopping half way up
   a row twice their height. The page read as three cards with a hole torn out of it. Stretching makes each
   row's cards agree, which is what a row of settings groups should look like; the short last row is then
   just a last row.
   The bare 1fr was the same trap found three other times on 2026-08-13 (both portal grids and the public
   home page's widget grid): a 1fr track cannot shrink below its content. */
/* TWO columns, not three. There are FOUR panels in this grid ("The lodge's year" is full-width and sits
   outside it), so three columns is 3 + 1 — one card alone in a second row with two holes beside it, which
   is what the page was reported looking like. Two is 2 + 2 exactly, and the wider track suits a column of
   seven role checkboxes better than a third of the page did.
   align-items:start is kept deliberately: a pair whose heights differ is a short card, and stretching it
   just moves the empty space inside the card. Two columns keeps that difference small.
   minmax(0,1fr), never a bare 1fr — the fourth instance of that trap found on 2026-08-13. */
.ma-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:1.5rem;align-items:start;margin-bottom:1.5rem}
.ma-roles{display:flex;flex-direction:column;gap:.45rem;padding-top:.35rem}
.ma-role{font-weight:500}
.ma-locked{font-size:.72rem;color:var(--slate-soft)}
.ma-save{display:flex;align-items:center;gap:1rem;flex-wrap:wrap;margin:0 0 2rem}

/* MJ Phase 7 — the lodge's year (approved supplement docs/mockups/journey-phase7.html, screen G). Full
   width under the three permission cards rather than a fourth column: it is one control and two sentences,
   and the sentence explaining what the month MEANS is the only check an administrator gets before saving. */
.ma-year{margin-bottom:1.5rem}
.ma-year__row{display:flex;gap:1.25rem;align-items:flex-end;flex-wrap:wrap;padding-top:.75rem}
.ma-year__field{display:flex;flex-direction:column;gap:.35rem;min-width:0}
.ma-year__field label{font-size:.7rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--slate-soft)}
.ma-year__field select{font:inherit;font-size:.95rem;color:var(--slate);background:var(--surface);
  border:1px solid var(--line-light);border-radius:9px;padding:.5rem .7rem;min-width:12rem}
.ma-year__field select:focus-visible{outline:2px solid var(--brass);outline-offset:1px}
/* flex-basis is deliberately auto: under the 640px rule this row becomes a column, and `flex:1 1 18rem`
   here would make a two-line sentence an 18rem-TALL empty box (the defect MJ Phase 6b shipped). */
.ma-year__means{flex:1 1 auto;min-width:16rem;margin:0;font-size:.88rem;color:var(--slate)}
.ma-year__means strong{color:var(--heading)}
.ma-year__warn{margin:1rem 0 0;padding:.8rem 1rem;border-radius:10px;font-size:.87rem;
  color:var(--warn-ink);background:var(--warn-fill);border:1px solid var(--warn-line)}
.ma-year__warn strong{color:var(--warn-ink)}

@media(max-width:960px){
  .ma-grid{grid-template-columns:1fr}
}

@media(max-width:640px){
  .ma-year__row{flex-direction:column;align-items:stretch}
  .ma-year__field select{min-width:0;width:100%;min-height:44px}
}

@media(max-width:640px){
  .ld-mtg__head{gap:10px}
  .ld-mtg__date{min-width:0;display:flex;gap:6px;align-items:baseline}
  .ld-mtg__date b{display:inline;font-size:1.05rem}

  /* The document row REFLOWS rather than compressing. Keeping the three parts on one line at 360px left
     the title about 110px wide and wrapped "Summons for the regular meeting" onto four lines — the row
     technically fitting and not actually working. The chip and the download share the first line; the
     title and its meta take the whole of the second. Only a screenshot catches this. */
  .ld-doc{flex-wrap:wrap;gap:6px 12px}
  .ld-doc__kind{min-width:0;order:1}
  .ld-doc__get{order:2;margin-left:auto}
  .ld-doc__body{order:3;flex-basis:100%}

  /* 44px minimum on anything a thumb has to hit (WCAG 2.5.5). */
  .ld-mtg__cal,.ld-fchip,.ld-doc__get{min-height:44px;display:inline-flex;align-items:center}

  /* The shared admin list row reflows too (MJ Phase 6a). At 360px a row's four controls floated up
     beside a title with about 60px to wrap into, and the screenshot showed the buttons sitting ON
     TOP of the words. The info takes the whole first line and the actions the whole second, which
     is the same answer the members' archive row already gives.

     ITEM 58 left these THREE and took the rest: they are .admin-list, every admin page's row, and
     the papers page's own reflow is .pap-row's, in the .pap-* block's own 640px query. */
  .admin-list__item{flex-wrap:wrap;gap:.6rem}
  .admin-list__info{flex-basis:100%;min-width:0}
  .admin-list__actions{flex-basis:100%;justify-content:flex-start}
}

/* ==========================================================================================
   MJ Phase 3b — what a board offers, who is at the table, and the sheet that gets printed
   (docs/members-journey-plan.md §12.4.2, mockup docs/mockups/tonight-attendance.html screens
   4, 5 and 6).

   The .att-* family continues Phase 3a's: the register and the menu are two halves of one
   evening, and a second prefix for the second half would only make them look unrelated.
   Everything here is on theme tokens — measured on Heritage and on a dark palette, because a
   fixed surface colour meeting a theme text token is the render-mapping trap this project has
   now hit five times.
   ========================================================================================== */

/* ---- what's on offer (the board card) ---- */
.att-group{border:1px solid var(--line-light);border-radius:10px;padding:.85rem .9rem;margin:0 0 .7rem;background:var(--surface)}
.att-group__top{display:flex;gap:.6rem;align-items:center;flex-wrap:wrap;margin:0 0 .6rem}
.att-group__title{flex:1;min-width:9rem;border:1px solid var(--line-light);border-radius:8px;background:var(--surface);
  color:var(--heading);padding:.5rem .65rem;font-size:.95rem;font-family:inherit;font-weight:700}
.att-group__req{display:inline-flex;align-items:center;gap:.4rem;font-size:.78rem;color:var(--slate-soft);white-space:nowrap}
.att-group__req input{width:20px;height:20px}
.att-group__empty{margin:0;font-size:.8rem;color:var(--slate-soft);font-style:italic}
.att-opts{list-style:none;margin:0;padding:0;display:grid;gap:.4rem}
.att-opt{display:flex;gap:.5rem;align-items:center}
.att-opt input[type=text]{flex:1;min-width:0;border:1px solid var(--line-light);border-radius:8px;background:var(--surface);
  color:var(--heading);padding:.45rem .6rem;font-size:.9rem;font-family:inherit}
.att-opt__note{flex:1;min-width:0;max-width:12rem}
.att-opt__count{flex:none;font-size:.72rem;color:var(--slate-soft);white-space:nowrap}
.att-opt__x{flex:none;min-width:44px;min-height:38px;border:1px solid var(--line-light);border-radius:8px;
  background:var(--surface);color:var(--slate-soft);font-family:inherit;font-size:.9rem;cursor:pointer}
.att-opt__x:hover{border-color:var(--claret);color:var(--claret)}
.att-opt__x:focus-visible{outline:2px solid var(--brass-ink);outline-offset:2px}
.att-group__add{margin-top:.55rem;min-height:40px;border:1px dashed var(--line-light);border-radius:8px;background:transparent;
  color:var(--brass-ink);font-size:.82rem;font-weight:600;font-family:inherit;cursor:pointer;padding:0 .8rem}
.att-group__add:hover{border-color:var(--brass)}
.att-offer__acts{display:flex;gap:.6rem;flex-wrap:wrap;align-items:center;margin-top:.9rem}
/* Its own class, and that is the finding rather than the styling: "Add a choice" first borrowed
   .fb-doc2__upload-submit for its look, which is display:none — that form auto-submits on file change,
   so its button is deliberately invisible. Two controls on this panel were therefore unreachable, and
   only driving the page in a browser showed it. */
.att-offer__btn{
  min-height:40px;padding:0 1rem;border:1px solid var(--line-light);border-radius:8px;
  background:var(--surface);color:var(--brass-ink);font-family:inherit;font-size:.85rem;font-weight:600;cursor:pointer;
}
.att-offer__btn:hover{border-color:var(--brass)}
.att-offer__btn:focus-visible{outline:2px solid var(--brass-ink);outline-offset:2px}
.att-offer__copy{display:inline-flex;gap:.45rem;align-items:center;flex-wrap:wrap}
.att-offer__copy-label{font-size:.78rem;color:var(--slate-soft)}

/* ---- ITEM 41: the meal offer editor on a phone ----------------------------------------
   The board card's THIRD panel, and the plan's measurement missed it entirely: §2.1 named
   .fb-board-card__head-acts as "one element and it is not a table" and said the page was two
   declarations wide. It was two declarations plus this.

   THE OVERFLOW IS A GRID TRACK, NOT A FLEX ITEM. .att-opts is `display:grid` with no
   grid-template-columns, so it gets ONE IMPLICIT COLUMN SIZED TO MAX-CONTENT. Measured on a
   320px phone: the <ul> was 183px wide and its single track was 434px, so every li.att-opt
   sat 251px outside its own parent. min-width:0 on the li could never have fixed it — the li
   was not refusing to shrink, it was filling a track that was already too big. minmax(0,1fr)
   is what gives an implicit track a zero minimum.

   The four controls are the floor, and all four are authored just under it: the option delete
   at min-height:38px and three add/copy buttons at exactly 40. */
@media screen and (max-width:700px){
  .att-opts{ grid-template-columns:minmax(0,1fr); }
  .att-opt__x,
  .att-offer__btn,
  .att-group__add{ min-height:44px; }
}

/* ---- one block per seat: the booking form's second step, and the member's own edit panel ---- */
.att-place{border:1px solid var(--line-light);border-radius:10px;padding:.9rem;margin:0 0 .7rem;background:var(--surface)}
.att-place--you{border-left:4px solid var(--brass)}
.att-place__k{margin:0 0 .6rem;font-size:.66rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--slate-soft)}
.att-place__grid{display:grid;gap:.7rem}
.att-place__name{font-weight:700;color:var(--heading);font-size:1rem;margin:0 0 .2rem}
/* A locked value still shows, with the reason beside it — hiding it would leave a member wondering
   what they had chosen at the moment they can no longer change it. */
.att-place__fixed{margin:0;font-weight:600;color:var(--heading)}
.att-place__why{font-weight:400;color:var(--slate-soft);font-size:.8rem}

/* ---- the member's edit panel ---- */
.fbm-edit{border-top:1px solid var(--line-light);margin:.9rem 0 0}
.fbm-edit__summary{cursor:pointer;list-style:none;padding:.85rem 0 .2rem;font-size:.92rem;font-weight:700;color:var(--brass-ink)}
.fbm-edit__summary::-webkit-details-marker{display:none}
.fbm-edit__summary:focus-visible{outline:2px solid var(--brass-ink);outline-offset:3px}
.fbm-edit__body{padding:.6rem 0 .4rem}
/* The panel's form is a grid, so an unqualified button stretches the full width of the card and reads as
   the page's primary action rather than as "save these names". */
.fbm-edit__body .btn{justify-self:start}

/* ---- the catering sheet ---- */
.att-sheet__lead{margin:0 0 1rem;color:var(--slate-soft);font-size:.92rem}
.att-sheet{width:100%;border-collapse:collapse;font-size:.9rem}
/* caption-side is stated because Bootstrap sets it to BOTTOM globally: without this, "For the chef"
   printed underneath its own table and read as a stray sentence rather than as the table's name. Only a
   screenshot showed it. */
.att-sheet caption{caption-side:top;text-align:left;font-family:var(--serif);font-size:1.15rem;color:var(--heading);margin:0 0 .5rem}
.att-sheet th,.att-sheet td{text-align:left;padding:.5rem .6rem;border-bottom:1px solid var(--line-light);vertical-align:top}
.att-sheet th{font-size:.68rem;font-weight:700;letter-spacing:.09em;text-transform:uppercase;color:var(--slate-soft)}
.att-sheet th.n,.att-sheet td.n{font-variant-numeric:tabular-nums;font-weight:700;color:var(--heading);white-space:nowrap;text-align:right}
.att-sheet tr:last-child td{border-bottom:0}
/* Wide content scrolls inside its own box rather than making the page scroll sideways. The min-width is
   what makes it SCROLL instead of squeezing: at 360px the Steward's table otherwise wrapped a guest's
   name down five lines to keep every column on screen, which is a table nobody can read in a kitchen. */
.att-scroll{overflow-x:auto;background:var(--surface);border:1px solid var(--line-light);border-radius:12px;padding:14px 16px;margin:0 0 1rem}
.att-scroll .att-sheet{min-width:34rem}
@media print{ .att-sheet-page .att-scroll .att-sheet{min-width:0} }

/* =====================================================================================
   ITEM 41 — THE CATERING SHEET ON A PHONE (gate Q3(a), Q2 700px).

   TWO TABLES ON ONE PAGE AND TWO DIFFERENT REMEDIES, because they are two different
   shapes. The CHEF's table is three columns — Course, Option, Number — so it fits a 320px
   phone the moment its min-width floor is lifted, and it is built on rowspan, a course name
   spanning its options, which a block flip would tear apart. The STEWARD's table has one
   column PER COURSE plus a name and a kitchen note, so its width is not bounded by anything
   the stylesheet knows; it becomes cards.

   Measured at 544px in its own scroller — milder than the roster's 757 and on the page most
   likely to be read one-handed in a kitchen, which is why the gate answered Q3 yes.

   `screen and` is load-bearing here. This page's whole purpose is to be printed and carried
   into a kitchen, and an A4 sheet is about 794px, so print never reaches the breakpoint —
   but stating it means a future narrower paper size cannot silently turn the printed sheet
   into a column of cards.
   ===================================================================================== */
@media screen and (max-width:700px){

  .att-sheet-page .att-scroll{ overflow-x:visible; padding:12px 12px; }
  .att-sheet-page .att-scroll .att-sheet{ min-width:0; }

  /* There is no `.att-sheet--cards{display:block}` here and there was. Mutating it away left the
     whole sweep green, claim 5 included: the tr and td rules below are blocks already, so the
     table's own display never decided anything. A <table> holding block rows wraps them in one
     anonymous row and cell and they stack regardless. The modifier class still earns its place —
     it is what keeps this flip off the CHEF's rowspan table three feet up the page — but the
     declaration it used to carry was doing no work. */

  /* See the roster's thead rule for why this is display:none and not a visually-hidden box.
     This page is where the clip-path version was caught: 114px <th> cells, right edge 464. */
  .att-sheet--cards thead{ display:none; }
  .att-sheet--cards tbody{ display:block; }

  /* One block per SEAT — guests included, which is what this table is for. Not a grid: the
     course count is whatever the Steward typed, so there is no fixed set of areas to name. */
  .att-sheet--cards tr{
    display:block;
    border:1px solid var(--line-light);
    border-radius:10px;
    padding:.8rem .85rem;
    margin:0 0 .6rem;
  }
  .att-sheet--cards td{ display:block; padding:.15rem 0; border-bottom:none; }
  .att-sheet--cards td:first-child{
    font-weight:700; color:var(--heading); font-size:1rem; margin-bottom:.35rem;
  }
  .att-sheet--cards td[data-label]::before{
    content:attr(data-label);
    display:block;
    font-size:.62rem; font-weight:700; letter-spacing:.09em; text-transform:uppercase;
    color:var(--slate-soft);
  }
  /* A seat with no kitchen note would otherwise draw the label over an empty line. */
  .att-sheet--cards td.att-note--none{ display:none; }

  /* ITEM 54 HALF B. The door into the editor is item 44's business at this width and a browser said
     so: measured on the shipped page it was 23x15, on the one screen in this building that is read
     off a phone held in a kitchen. It keeps its quiet inline look and gains a target around it. */
  .att-sheet--cards .att-edit{
    display:inline-flex;
    align-items:center;
    justify-content:center;
    min-height:44px;
    min-width:44px;
    padding:0 .5rem;
    margin-left:.25rem;
  }
}
.att-diet{color:var(--warn-ink);background:var(--warn-fill);border:1px solid var(--warn-line);border-radius:5px;
  padding:0 6px;font-size:.75rem;font-weight:600;display:inline-block}
.att-none{color:var(--slate-soft);font-style:italic}

/* ITEM 54 HALF B. The door out of the sheet into the editor. It is a quiet word rather than a
   button because there is one per seat and a column of forty buttons reads as the page's subject;
   .att-noprint takes it off the paper, where it would be a blue word nobody can press. */
.att-edit{
  margin-left:.5rem;font-size:.75rem;font-weight:600;color:var(--brass-ink);
  text-decoration:underline;text-underline-offset:2px;white-space:nowrap;
}
.att-edit:hover,.att-edit:focus-visible{color:var(--heading)}
.fbt-hint-inline{font-weight:400;color:var(--slate-soft)}

/* A print stylesheet and no PDF library — the same approach Phase 7's printable member record takes,
   and the reason this page ships with no new dependency. The chrome goes, the tables stay, and the
   scrolling box becomes a plain block so nothing is cut off at the paper's edge. */
@media print{
  .att-sheet-page .att-noprint,
  body:has(.att-sheet-page) .site-head,
  body:has(.att-sheet-page) .site-foot,
  body:has(.att-sheet-page) .app-toast,
  body:has(.att-sheet-page) .admin-page-header__back{display:none !important}

  .att-sheet-page .att-scroll{overflow:visible;border:0;padding:0;background:transparent}
  .att-sheet-page .att-sheet{page-break-inside:auto}
  .att-sheet-page .att-sheet tr{page-break-inside:avoid}
  .att-sheet-page .att-sheet th,.att-sheet-page .att-sheet td{border-bottom:1px solid #999}
  .att-sheet-page .att-diet{border:1px solid #999;background:transparent;color:#000}
}

/* ============================================================================================
   MJ Phase 4 — /Members/Journey, the timeline (docs/members-journey-plan.md §12.5).

   The hero, the rank pill and the glance strip are the portal's own .mp-* shapes; this family is
   only what the timeline needs, which nothing else on the site has. Every colour is a THEME token,
   the flag line is the fixed --warn pair, and accent TEXT on the dark band is --brass-soft.
   ============================================================================================ */
.mj-next__big{font-family:var(--serif);font-size:1.5rem;line-height:1.15;margin:0;color:var(--on-dark)}
.mj-next__fine{
  margin:12px 0 0;font-size:.72rem;line-height:1.5;
  color:color-mix(in srgb, var(--on-dark) 60%, transparent);
}

.mj-notice{
  margin:22px 0 0;padding:.7rem .9rem;border-radius:10px;font-size:.88rem;
  color:var(--ok-ink);background:var(--ok-fill);border:1px solid var(--ok-line);
}

.mj-legend{
  display:flex;gap:16px;flex-wrap:wrap;align-items:center;
  margin:30px 0 4px;font-size:.78rem;color:var(--slate-soft);
}
.mj-legend__key i{
  display:inline-block;width:11px;height:11px;border-radius:50%;margin-right:6px;vertical-align:-1px;
  background:var(--parchment-2);border:2px solid var(--brass);
}

/* The rule the dots sit on. 139px = the 110px date column + half of the 60px node column, so moving
   either below has to move this with it. */
.mj-timeline{position:relative;margin:18px 0 56px;padding:4px 0}
.mj-timeline::before{
  content:"";position:absolute;left:139px;top:0;bottom:0;width:1.5px;background:var(--line-light);
}
.mj-tl{position:relative;display:grid;grid-template-columns:110px 60px 1fr;align-items:start;margin-bottom:18px}
.mj-tl__when{
  text-align:right;font-family:var(--serif);color:var(--heading);font-size:1.02rem;
  padding-top:14px;line-height:1.25;
}
.mj-tl__when span{display:block;font-family:inherit;font-size:.72rem;color:var(--slate-soft)}
.mj-tl__node{position:relative;display:flex;justify-content:center;padding-top:16px}
.mj-tl__dot{
  width:34px;height:34px;border-radius:50%;display:flex;align-items:center;justify-content:center;
  font-size:.95rem;position:relative;z-index:1;
  background:var(--parchment-2);border:2px solid var(--brass);color:var(--heading);
}
.mj-tl__card{
  background:var(--surface);border:1px solid var(--line-light);border-radius:15px;padding:16px 20px;
  box-shadow:0 20px 44px -30px rgba(0,0,0,.6);
}
.mj-tl__top{display:flex;align-items:center;gap:10px;flex-wrap:wrap}
.mj-tl__top h2{font-family:var(--serif);font-size:1.15rem;margin:0;flex:1;min-width:180px;color:var(--heading)}
.mj-tl__desc{margin:6px 0 0;font-size:.9rem;color:var(--slate-soft)}

.mj-chip{
  display:inline-flex;align-items:center;gap:6px;font-size:.62rem;font-weight:700;letter-spacing:.08em;
  text-transform:uppercase;border-radius:999px;padding:4px 10px;white-space:nowrap;
  color:var(--slate-soft);background:var(--parchment-2);border:1px solid var(--line-light);
}
/* The body chip, and only for a body that is NOT the Craft lodge — on a Craft lodge's own site,
   tagging every ordinary meeting "Craft" is noise. Brass tint under the heading colour, never a
   brass FILL: a fill needs --on-brass and this sits inside a sentence. */
.mj-chip--body{
  color:var(--heading);background:color-mix(in srgb, var(--brass) 16%, var(--surface));
  border-color:color-mix(in srgb, var(--brass) 45%, transparent);margin-left:6px;
}

.mj-detail{margin:12px 0 0;border-top:1px solid var(--line-light);padding-top:10px}
.mj-detail>summary{
  list-style:none;cursor:pointer;font-size:.82rem;font-weight:600;color:var(--slate);
  display:inline-flex;align-items:center;gap:7px;min-height:34px;
}
.mj-detail>summary::-webkit-details-marker{display:none}
.mj-detail>summary::after{content:"\25BE";font-size:.8rem;color:var(--slate-soft)}
.mj-detail[open]>summary::after{content:"\25B4"}
.mj-detail>summary:focus-visible{outline:2px solid var(--brass);outline-offset:3px;border-radius:6px}

.mj-row{
  display:flex;align-items:center;gap:12px;padding:11px 0;flex-wrap:wrap;
  border-top:1px solid var(--line-light);
}
.mj-row:first-of-type{border-top:0}
.mj-row__date{
  font-variant-numeric:tabular-nums;font-size:.84rem;color:var(--slate-soft);min-width:88px;
}
.mj-row__what{flex:1;min-width:180px;font-size:.9rem;color:var(--heading);font-weight:600}
.mj-row__what>span:last-child{display:block;font-weight:400;font-size:.8rem;color:var(--slate-soft)}

/* The flag control: a quiet outline, because "this is wrong" is not the page's primary action and a
   brass button beside every evening would read as an invitation to press one. */
.mj-flag{
  font-family:inherit;font-size:.76rem;font-weight:600;color:var(--slate-soft);background:transparent;
  border:1px solid var(--line-light);border-radius:999px;padding:8px 14px;cursor:pointer;
  white-space:nowrap;text-decoration:none;display:inline-flex;align-items:center;min-height:38px;
}
.mj-flag:hover{border-color:var(--brass);color:var(--slate)}
.mj-flag:focus-visible{outline:2px solid var(--brass);outline-offset:2px}
.mj-flag--send{background:var(--brass);color:var(--on-brass);border-color:var(--brass)}
.mj-flag--send:hover{color:var(--on-brass)}

.mj-flagged{
  display:flex;align-items:flex-start;gap:9px;flex-basis:100%;margin:2px 0 0;padding:9px 12px;
  border-radius:9px;font-size:.8rem;line-height:1.5;flex-wrap:wrap;
  color:var(--warn-ink);background:var(--warn-fill);border:1px solid var(--warn-line);
}
/* min-width:0 and a flex term, or the sentence gives its width to the button beside it: at 360px a
   45-character note wrapped down NINE lines while "Withdraw" sat comfortably. Only a screenshot
   showed it, which is the same defect Phase 3b found in the Steward's table. */
.mj-flagged>span:nth-child(2){flex:1 1 0;min-width:0}
.mj-flagged__form{margin-left:auto}
.mj-flagged .mj-flag{color:var(--warn-ink);border-color:var(--warn-line)}

.mj-flagform{
  display:flex;gap:8px;flex-wrap:wrap;align-items:center;flex-basis:100%;margin-top:2px;
  padding:11px 12px;background:var(--parchment-2);border:1px solid var(--line-light);border-radius:10px;
}
.mj-flagform input[type=text]{
  font-family:inherit;font-size:.86rem;flex:1;min-width:180px;color:var(--heading);
  background:var(--surface);border:1px solid var(--line-light);border-radius:8px;padding:9px 11px;
}
.mj-flagform input[type=text]:focus-visible{outline:2px solid var(--brass);outline-offset:1px}
.mj-flagform__hint{flex-basis:100%;margin:0;font-size:.75rem;color:var(--slate-soft);line-height:1.5}

.mj-empty{
  background:var(--surface);border:1px solid var(--line-light);border-radius:15px;
  padding:30px 26px;margin:26px 0 56px;text-align:center;
  box-shadow:0 20px 44px -30px rgba(0,0,0,.6);
}
.mj-empty h2{font-family:var(--serif);font-size:1.3rem;margin:0 0 8px;color:var(--heading)}
.mj-empty p{margin:0 auto;max-width:56ch;font-size:.92rem;color:var(--slate-soft)}

/* ============================================================================================
   MJ Phase 5 — the RECORDED half (docs/members-journey-plan.md §12.6, approved supplement
   docs/mockups/journey-phase5.html).

   Phase 4 drew every dot the same, because every entry came from the lodge's own records. A
   recorded milestone is the other thing, so it gets the legend's second key and a FILLED dot —
   which is why the glyph inside it takes --on-brass and not --heading.
   ============================================================================================ */
.mj-tl--recorded .mj-tl__dot{background:var(--brass);border-color:var(--brass);color:var(--on-brass)}

/* The legend's second key has to LOOK like the dot it explains. Without this rule both keys draw the
   same hollow dot and the legend explains a distinction the page is not making — which no assertion
   can see, because the markup is correct either way. */
.mj-legend__key--recorded i{background:var(--brass);border-color:var(--brass)}

/* The legend and the add button share a row; the button drops beneath on a narrow screen. */
.mj-toolbar{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap}
.mj-toolbar .mj-legend{margin-bottom:0;flex:1 1 auto}
.mj-add{
  display:inline-flex;align-items:center;justify-content:center;gap:8px;text-decoration:none;
  font-family:inherit;font-weight:600;font-size:.86rem;border:0;border-radius:10px;padding:10px 16px;
  background:var(--brass);color:var(--on-brass);cursor:pointer;
}
.mj-add:hover{color:var(--on-brass)}
.mj-add:focus-visible{outline:2px solid var(--brass);outline-offset:2px}

/* What can be done to an entry the member (or the lodge) recorded. A derived entry never has one. */
.mj-acts{
  display:flex;gap:8px;flex-wrap:wrap;margin-top:12px;padding-top:11px;
  border-top:1px solid var(--line-light);
}
/* gap, because THIS IS A FLEX CONTAINER and flex discards the whitespace-only node between two items.
   The four print controls are written `<span aria-hidden>&#128424;</span> Print this record` — one space
   in the markup, none on the screen, so every one of them read "🖨Print this record". 8px is what the
   sibling .mj-add already uses for its plus. Nothing else in this family holds two items, so the rule
   costs those controls nothing. MJ Phase 8; found by a geometry probe, invisible to every assertion. */
.mj-act{
  font-family:inherit;font-size:.78rem;font-weight:600;color:var(--slate);background:var(--surface);
  border:1px solid var(--line-light);border-radius:9px;padding:7px 13px;cursor:pointer;
  text-decoration:none;display:inline-flex;align-items:center;gap:8px;
}
.mj-act:hover{border-color:var(--brass);color:var(--slate)}
.mj-act:focus-visible{outline:2px solid var(--brass);outline-offset:2px}
.mj-act--quiet{border-color:transparent;color:var(--slate-soft);background:transparent}
.mj-act--quiet:hover{color:var(--slate)}

/* Removing, in two presses. The members' side has no dialogs (plan rule §12.1.1), so the
   confirmation is the card itself, in the place the member pressed. */
.mj-confirm{
  display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-top:12px;padding:11px 13px;
  border-radius:10px;font-size:.85rem;
  color:var(--warn-ink);background:var(--warn-fill);border:1px solid var(--warn-line);
}
.mj-confirm>span:first-child{flex:1 1 auto;min-width:0}
.mj-confirm b{color:var(--warn-ink)}
.mj-confirm form{display:inline-flex;gap:8px;flex-wrap:wrap}
.mj-confirm__go,.mj-confirm__keep{
  font-family:inherit;font-size:.8rem;font-weight:600;border-radius:9px;padding:7px 13px;
  cursor:pointer;text-decoration:none;display:inline-flex;align-items:center;white-space:nowrap;
}
/* --bad-ink is a FIXED status colour, so its text must be the fixed companion. Written as
   var(--surface) it measured 1.35:1 on Evergreen and about 1.5:1 on Midnight, Onyx and Garnet — the
   FIXED-BG shape the stylesheet header forbids, invisible on Heritage because there --surface is nearly
   white. Found by MJ Phase 8 only after the crawler was pointed at ?remove=, i.e. at a STATE. */
.mj-confirm__go{background:var(--bad-ink);color:var(--bad-fill);border:1px solid var(--bad-ink)}
.mj-confirm__keep{background:var(--surface);color:var(--slate);border:1px solid var(--warn-line)}
.mj-confirm__go:focus-visible,.mj-confirm__keep:focus-visible{outline:2px solid var(--brass);outline-offset:2px}

/* The photograph, beside the member's own words about it. */
.mj-shot{
  margin:12px 0 0;padding:12px 14px;background:var(--parchment-2);border:1px solid var(--line-light);
  border-radius:10px;display:flex;gap:14px;align-items:flex-start;
}
.mj-shot__img{
  width:104px;height:78px;flex:none;border-radius:8px;border:1px solid var(--line-light);
  object-fit:cover;background:var(--surface);
}
.mj-shot__body{min-width:0;font-size:.88rem;color:var(--slate);line-height:1.6}
.mj-shot__body q{font-style:italic}
.mj-shot__link{
  display:inline-block;margin-top:7px;font-size:.78rem;font-weight:600;color:var(--slate-soft);
  text-decoration:none;
}
.mj-shot__link:hover{color:var(--slate);text-decoration:underline}
.mj-shot__link:focus-visible{outline:2px solid var(--brass);outline-offset:2px;border-radius:4px}

/* The inline form. It stands where the card it belongs to stands, and it is the page with a query
   value on it — no dialog, no script. */
.mj-form{border-color:color-mix(in srgb, var(--brass) 45%, var(--line-light))}
.mj-form__head{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;margin-bottom:4px}
.mj-form__head h2{font-family:var(--serif);font-size:1.12rem;margin:0;color:var(--heading)}
.mj-form__head span{font-size:.78rem;color:var(--slate-soft)}
.mj-preface{
  margin:10px 0 16px;padding:11px 14px;border-radius:10px;font-size:.86rem;color:var(--heading);
  background:color-mix(in srgb, var(--brass) 12%, var(--surface));
  border:1px solid color-mix(in srgb, var(--brass) 40%, transparent);
}
.mj-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:10px}
.mj-grid .mj-ff--full{grid-column:1/-1}
.mj-ff{display:flex;flex-direction:column;gap:6px;min-width:0}
/* A cell holding only a control, aligned with the field beside it rather than padded by a blank label. */
.mj-ff--action{justify-content:flex-end}
.mj-ff>label{
  font-size:.66rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--slate-soft);
}
.mj-ff input[type=text],.mj-ff input[type=date],.mj-ff select,.mj-ff textarea{
  font-family:inherit;font-size:.9rem;color:var(--heading);background:var(--surface);
  border:1px solid var(--line-light);border-radius:9px;padding:9px 12px;min-width:0;
}
.mj-ff textarea{resize:vertical;min-height:74px;line-height:1.5}
.mj-ff input:focus-visible,.mj-ff select:focus-visible,.mj-ff textarea:focus-visible{
  outline:2px solid var(--brass);outline-offset:1px;
}
.mj-ff__help{font-size:.76rem;color:var(--slate-soft);margin:0}

.mj-vis{display:grid;grid-template-columns:1fr 1fr;gap:10px}
.mj-vis__opt{
  display:flex;gap:10px;align-items:flex-start;padding:11px 13px;border:1px solid var(--line-light);
  border-radius:10px;background:var(--surface);cursor:pointer;
}
.mj-vis__opt:has(input:checked){
  border-color:var(--brass);background:color-mix(in srgb, var(--brass) 8%, var(--surface));
}
.mj-vis__opt:has(input:focus-visible){outline:2px solid var(--brass);outline-offset:2px}
.mj-vis__opt input{accent-color:var(--brass);width:16px;height:16px;margin:3px 0 0;flex:none}
.mj-vis__opt b{display:block;font-size:.88rem;font-weight:600;color:var(--heading)}
.mj-vis__opt span{display:block;font-size:.76rem;color:var(--slate-soft);line-height:1.5}

.mj-file{
  display:flex;align-items:center;gap:13px;flex-wrap:wrap;padding:13px 14px;
  border:1.5px dashed var(--line-light);border-radius:11px;background:var(--parchment-2);
  font-size:.84rem;color:var(--slate-soft);
}
.mj-file input[type=file]{font-family:inherit;font-size:.82rem;color:var(--slate);max-width:100%}
.mj-file__thumb{
  width:56px;height:42px;flex:none;border-radius:6px;border:1px solid var(--line-light);object-fit:cover;
}
.mj-file__rm{display:flex;align-items:center;gap:7px;font-size:.8rem;color:var(--slate)}
.mj-file__rm input{accent-color:var(--brass);width:15px;height:15px;margin:0}

.mj-formacts{
  display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-top:16px;padding-top:14px;
  border-top:1px solid var(--line-light);
}
.mj-err{
  margin:12px 0 0;padding:.7rem .9rem;border-radius:10px;font-size:.86rem;
  color:var(--bad-ink);background:var(--bad-fill);border:1px solid var(--bad-line);
}

/* The switch off. Said once, at the top, and never as an apology. */
.mj-off{
  display:flex;align-items:flex-start;gap:11px;padding:13px 16px;border-radius:11px;font-size:.86rem;
  color:var(--slate);background:var(--parchment-2);border:1px solid var(--line-light);margin:20px 0 0;
}
.mj-off b{color:var(--heading)}

/* /Admin/Journeys' roll. Its own scroll container, so a long name never scrolls the page sideways. */
.mj-roll-wrap{
  overflow-x:auto;border:1px solid var(--line-light);border-radius:14px;background:var(--surface);
  margin:18px 0 14px;
}
.mj-roll{width:100%;border-collapse:collapse;font-size:.9rem}
.mj-roll th{
  text-align:left;font-size:.63rem;letter-spacing:.12em;text-transform:uppercase;color:var(--slate-soft);
  font-weight:700;padding:11px 16px;border-bottom:1px solid var(--line-light);white-space:nowrap;
}
.mj-roll td{padding:12px 16px;border-bottom:1px solid var(--line-light);color:var(--slate);vertical-align:middle}
.mj-roll tbody tr:last-child td{border-bottom:0}
.mj-roll tbody tr:hover{background:color-mix(in srgb, var(--brass) 6%, var(--surface))}
/* Both the header and the cells, or the caption floats left of the column it names. */
.mj-roll th.mj-roll__num,.mj-roll td.mj-roll__num{text-align:right;white-space:nowrap}
.mj-roll td.mj-roll__num{font-variant-numeric:tabular-nums}
.mj-roll__who b{color:var(--heading);font-weight:600}
.mj-roll__go{text-align:right;white-space:nowrap}
.mj-roll__empty{color:var(--slate-soft);text-align:center;padding:26px 16px}

/* ============================================================================================
   MJ Phase 7 — the year band, the season page, and the printed record
   (docs/members-journey-plan.md §12.8, approved supplement docs/mockups/journey-phase7.html).

   Three families, and one of them is unlike anything else in this file:

   .mj-band / .mj-seasons / .mj-list / .mj-honest  — theme tokens, as every members' page is.
   .rec-*                                          — INK ON PAPER, literal values on purpose.

   The sheet is the one surface on this site that ignores the palette. A Midnight theme must not print a
   black page, and a College theme must not spend a brother's toner on a navy rule; a member's printed
   record has to look the same whichever palette the lodge happens to be running. Every colour in the
   .rec-sheet block is therefore a literal, and that is a decision rather than an oversight (§12.8.1's
   fourth rule).
   ============================================================================================ */

/* ---- the year band: screen 2's own shape, drawn honestly ---- */
.mj-band{
  position:relative;overflow:hidden;border-radius:16px;margin:34px 0 40px;padding:26px 28px;
  color:var(--on-dark);
  background:radial-gradient(120% 140% at 12% -30%, color-mix(in srgb,var(--brass) 15%,transparent), transparent 55%),
             linear-gradient(180deg,var(--ink),var(--ink-deep));
  background-color:var(--ink);
  border:1px solid color-mix(in srgb,var(--brass) 28%,transparent);
}
.mj-band__inner{display:flex;align-items:center;justify-content:space-between;gap:22px;flex-wrap:wrap}
.mj-band__body{flex:1 1 auto;min-width:0}
.mj-band__k{color:var(--brass-soft);margin:0}
.mj-band__title{font-family:var(--serif);font-size:1.45rem;line-height:1.15;margin:8px 0 4px;color:var(--on-dark)}
.mj-band__lead{margin:0;font-size:.9rem;max-width:60ch;color:color-mix(in srgb,var(--on-dark) 75%,transparent)}
.mj-band__figs{display:flex;gap:26px;flex-wrap:wrap;margin-top:16px}
.mj-band__fig b{
  display:block;font-family:var(--serif);font-size:1.6rem;font-weight:600;color:var(--brass-soft);
  /* lining-nums — see the note on .mp-gcard__v. */
  font-variant-numeric:tabular-nums lining-nums;line-height:1.1;
}
.mj-band__fig span{
  font-size:.72rem;letter-spacing:.1em;text-transform:uppercase;
  color:color-mix(in srgb,var(--on-dark) 65%,transparent);
}
/* flex:none, and no basis anywhere in this family: under the 640px rule the inner row becomes a column,
   and a basis on either side would become a minimum HEIGHT — three empty boxes at 360px, which is what
   MJ Phase 6b shipped and only a screenshot caught. */
.mj-band__acts{display:flex;gap:10px;flex-wrap:wrap;flex:none}
.mj-band__act{
  display:inline-flex;align-items:center;justify-content:center;gap:7px;
  font-size:.82rem;font-weight:600;border-radius:9px;padding:9px 14px;text-decoration:none;
  color:var(--on-dark);background:transparent;
  border:1px solid color-mix(in srgb,var(--on-dark) 30%,transparent);
}
.mj-band__act:hover{border-color:var(--brass);color:var(--on-dark)}
.mj-band__act:focus-visible{outline:2px solid var(--brass-soft);outline-offset:2px}
/* The no-band state: a brother with nothing in any season still has a sheet worth printing. */
.mj-noband{
  display:flex;align-items:flex-start;gap:9px;margin:30px 0 40px;font-size:.88rem;color:var(--slate-soft);
}
.mj-noband a{color:var(--brass-ink);font-weight:600}

/* ---- the what's-next panel's one new line (screen D) ---- */
.mj-next__fact{margin:10px 0 0;font-size:.84rem;color:var(--brass-soft)}
.mj-next__fix{color:var(--brass-soft);text-decoration:underline;white-space:nowrap}

/* ---- the season's own page ---- */
.mj-seasons{display:flex;gap:8px;flex-wrap:wrap;margin:0 0 10px}
.mj-seasons__now,.mj-seasons__go{
  font-size:.79rem;border-radius:999px;padding:5px 12px;white-space:nowrap;text-decoration:none;
}
.mj-seasons__now{background:var(--brass);color:var(--on-brass);font-weight:700}
.mj-seasons__go{
  color:color-mix(in srgb,var(--on-dark) 74%,transparent);
  border:1px solid color-mix(in srgb,var(--on-dark) 22%,transparent);
}
.mj-seasons__go:hover{color:var(--on-dark);border-color:var(--brass)}
.mj-seasons__go:focus-visible{outline:2px solid var(--brass-soft);outline-offset:2px}
.mj-sect{margin:34px 0 12px;border-bottom:1px solid var(--line-light);padding-bottom:8px;
  display:flex;align-items:baseline;gap:14px;flex-wrap:wrap}
.mj-sect h2{font-family:var(--serif);font-size:1.3rem;margin:0;color:var(--heading)}
.mj-sect__hint{margin:0;font-size:.8rem;color:var(--slate-soft)}
.mj-list{background:var(--surface);border:1px solid var(--line-light);border-radius:15px;padding:4px 18px}
.mj-list__row{
  display:flex;gap:14px;align-items:flex-start;padding:13px 0;flex-wrap:wrap;
  border-bottom:1px solid var(--line-light);
}
.mj-list__row:last-child{border-bottom:0}
/* A width rather than a flex-basis, so the 640px rule can drop it to auto without leaving a tall box. */
.mj-list__date{width:7.5rem;flex:none;font-size:.82rem;color:var(--slate-soft);font-variant-numeric:tabular-nums}
.mj-list__what{flex:1 1 auto;min-width:12rem;font-size:.92rem;font-weight:600;color:var(--heading)}
.mj-list__meta{display:block;font-weight:400;font-size:.79rem;color:var(--slate-soft);margin-top:2px}
/* An evening he was not marked at is quieter than one he was: it is context for the denominator, not an
   accusation. The row is still a row, because leaving it out is what would be dishonest. */
.mj-list__row[data-present="0"] .mj-list__what{color:var(--slate-soft);font-weight:500}
.mj-honest{
  border:1.5px dashed var(--line-light);border-radius:14px;padding:18px 20px;background:var(--surface);
  margin:30px 0 0;
}
.mj-honest h2{font-family:var(--serif);font-size:1.06rem;margin:0 0 8px;color:var(--heading)}
.mj-honest p{margin:0;font-size:.87rem;color:var(--slate-soft);max-width:66ch}
.mj-honest p+p{margin-top:8px}
.mj-honest strong{color:var(--heading)}
.mj-yearfoot{display:flex;gap:12px;flex-wrap:wrap;align-items:center;margin:26px 0 10px}
.mj-act--go{background:var(--brass);color:var(--on-brass);border-color:var(--brass);font-weight:700}
.mj-act--go:hover{color:var(--on-brass);border-color:var(--brass)}

/* ---- the printed record: the desk, the controls, and the sheet ---- */
.rec-ctl{
  display:flex;gap:12px;flex-wrap:wrap;align-items:center;margin:26px 0 0;padding:16px 18px;
  background:var(--surface);border:1px solid var(--line-light);border-radius:14px;
}
.rec-ctl__lead{flex:1 1 auto;min-width:16rem;margin:0;font-size:.88rem;color:var(--slate-soft)}
.rec-ctl__lead strong{color:var(--heading)}
.rec-ctl__note{flex:1 1 100%;margin:0;font-size:.79rem;color:var(--slate-soft)}
.rec-sw{
  display:inline-flex;align-items:center;gap:8px;font-size:.84rem;font-weight:600;text-decoration:none;
  color:var(--slate);background:var(--surface);border:1px solid var(--line-light);border-radius:9px;
  padding:9px 13px;
}
.rec-sw--on{border-color:var(--brass);background:color-mix(in srgb,var(--brass) 10%,var(--surface))}
.rec-sw:hover{border-color:var(--brass)}
.rec-sw:focus-visible{outline:2px solid var(--brass);outline-offset:2px}
/* The desk the sheet lies on. Its own grey rather than a theme surface, so the paper reads as paper on
   every palette — and it is the box that scrolls at 360px rather than the page. */
.rec-desk{
  background:#E4E2DD;border:1px solid rgba(0,0,0,.14);border-radius:14px;padding:26px 22px;margin:20px 0 0;
  overflow-x:auto;
}
.rec-foot{margin:18px 0 40px;font-size:.8rem;color:var(--slate-soft);max-width:76ch}
.rec-print{border:0}

/* THE SHEET. Literal values, deliberately: see the family header above. */
.rec-sheet{
  width:660px;max-width:100%;margin:0 auto;padding:44px 46px 30px;
  background:#FFFFFF;color:#141414;
  box-shadow:0 22px 46px -26px rgba(0,0,0,.55);
  font-family:Georgia,"Times New Roman",serif;font-size:15px;line-height:1.5;
}
.rec-sheet__head{display:flex;gap:16px;align-items:flex-start;border-bottom:2px solid #141414;padding-bottom:13px}
.rec-sheet__crest{
  width:40px;height:40px;flex:none;border-radius:50%;border:1px solid #8A8A8A;
  background:radial-gradient(circle at 38% 32%, #6F2435, #141414 74%);
}
.rec-sheet__lodge{font-family:Georgia,"Times New Roman",serif;font-size:1.32rem;line-height:1.15;margin:0;color:#141414}
.rec-sheet__of{margin:5px 0 0;font-size:.86rem;color:#3A3A3A}
.rec-sheet__stamp{
  margin:4px 0 0;font-family:system-ui,-apple-system,"Segoe UI",sans-serif;font-size:.68rem;
  letter-spacing:.06em;text-transform:uppercase;color:#5C5C5C;
}
.rec-sheet__h{
  font-family:system-ui,-apple-system,"Segoe UI",sans-serif;font-size:.64rem;font-weight:700;
  letter-spacing:.17em;text-transform:uppercase;color:#141414;
  border-bottom:1px solid #B4B4B4;padding-bottom:5px;margin:26px 0 10px;
}
.rec-row{display:flex;gap:16px;padding:4px 0;font-size:.92rem}
.rec-row__d{width:9.2rem;flex:none;color:#4A4A4A;font-variant-numeric:tabular-nums}
.rec-row__t{flex:1 1 auto;min-width:0;color:#141414}
.rec-row__t em{color:#5C5C5C;font-size:.86rem;font-style:italic}
.rec-miss{color:#5C5C5C;font-style:italic;margin:4px 0 0;font-size:.9rem}
.rec-note{margin:9px 0 0;font-size:.82rem;color:#4A4A4A}
.rec-tbl{width:100%;border-collapse:collapse;font-size:.9rem;margin:0}
.rec-tbl th{
  text-align:left;font-family:system-ui,-apple-system,"Segoe UI",sans-serif;font-size:.6rem;font-weight:700;
  letter-spacing:.11em;text-transform:uppercase;color:#4A4A4A;padding:0 10px 6px 0;border-bottom:1px solid #B4B4B4;
}
.rec-tbl td{padding:6px 10px 6px 0;border-bottom:1px solid #DCDCDC;color:#141414;vertical-align:top}
.rec-tbl tr:last-child td{border-bottom:0}
.rec-tbl th.rec-n,.rec-tbl td.rec-n{text-align:right;font-variant-numeric:tabular-nums;padding-right:0}
.rec-sheet__foot{
  margin:26px 0 0;border-top:1px solid #B4B4B4;padding-top:9px;display:flex;gap:14px;
  justify-content:space-between;flex-wrap:wrap;
  font-family:system-ui,-apple-system,"Segoe UI",sans-serif;font-size:.66rem;color:#5C5C5C;
}

/* What printing actually does. The same approach as the catering sheet (Phase 3b): the chrome goes, the
   sheet loses its desk and its shadow, and a row never breaks across two pieces of paper. No PDF library. */
@media print{
  /* The paper's own margin, and a couple of millimetres inside it. Chrome's "Save as PDF" at its zero-margin
     setting otherwise puts the crest and the season table's last column hard against the edges of the sheet —
     which a print PREVIEW showed and no assertion could. Both are needed: @page for the paper, the padding for
     a printer that ignores it. */
  @page{margin:14mm}

  .rec-page .rec-noprint,
  body:has(.rec-page) .site-head,
  body:has(.rec-page) .site-foot,
  body:has(.rec-page) .app-toast,
  body:has(.rec-page) .admin-page-header__back{display:none !important}

  .rec-page .wrap{max-width:none;padding:0}
  .rec-desk{background:transparent;border:0;padding:0;margin:0;overflow:visible}
  .rec-sheet{width:auto;max-width:none;margin:0;padding:2mm;box-shadow:none}
  .rec-row,.rec-tbl tr{page-break-inside:avoid}
  .rec-sheet__h{page-break-after:avoid}
  .rec-sheet__crest{background:#141414 !important;-webkit-print-color-adjust:exact;print-color-adjust:exact}
}

@media(max-width:640px){
  .mj-band{padding:20px 18px}
  .mj-band__figs{gap:18px}
  .mj-band__acts{width:100%}
  .mj-band__act{flex:1 1 auto;min-height:44px}
  .mj-list__date{width:auto;flex:0 0 auto}
  .mj-list__what{min-width:0;flex-basis:100%}
  .rec-ctl{flex-direction:column;align-items:stretch}
  .rec-ctl__lead{flex:0 0 auto;min-width:0}
  .rec-sw,.rec-print{width:100%;justify-content:center;min-height:44px}
  .rec-desk{padding:14px 10px}
  .rec-sheet{padding:22px 18px 16px;font-size:13.5px}
  /* The season table SCROLLS inside the desk rather than squeezing five columns into 320px — the rule
     Phase 3b learned in a kitchen, and the reason .rec-desk has overflow-x:auto at all. In print there is no
     desk and no min-width, so the paper still gets the whole table. */
  .rec-sheet .rec-tbl{min-width:24rem}
  .rec-row{gap:4px}
  .rec-row__d{width:auto;flex:0 0 auto}
  .rec-row__t{flex-basis:100%}
  .mj-yearfoot .mj-act{flex:1 1 auto;justify-content:center;min-height:44px}
}

/* ---- MJ Phase 7: the Secretary's aggregate bands on /Admin/Journeys (screen E) ----
   `.lj-*` rather than more `.mj-*`: these are the LODGE's own figures, not one journey, and they sit above a
   roll that already owns the `.mj-roll` family. The tables ARE .mj-roll, deliberately — one table shape on
   one page, three times, is what makes the three bands read as one view. */
.lj-cover{
  display:flex;align-items:flex-start;gap:11px;margin:20px 0 0;padding:13px 16px;border-radius:12px;
  font-size:.88rem;color:var(--slate);
  background:color-mix(in srgb, var(--brass) 8%, var(--surface));
  border:1px solid color-mix(in srgb, var(--brass) 45%, transparent);
}
.lj-cover b{color:var(--heading)}
.lj-band{margin:28px 0 0}
.lj-band__head{display:flex;align-items:baseline;gap:14px;flex-wrap:wrap;margin:0 0 4px}
.lj-band__head h2{font-family:var(--serif);font-size:1.18rem;margin:0;color:var(--heading)}
.lj-band__head--roll{margin-top:34px}
.lj-band__hint{margin:0;font-size:.8rem;color:var(--slate-soft)}
.lj-chips{display:flex;gap:6px;flex-wrap:wrap;margin-left:auto}
.lj-chips a,.lj-chips span{
  font-size:.76rem;text-decoration:none;border-radius:999px;padding:5px 12px;
  border:1px solid var(--line-light);color:var(--slate-soft);background:var(--surface);
}
.lj-chips span{background:var(--brass);color:var(--on-brass);border-color:var(--brass);font-weight:700}
.lj-chips a:hover{border-color:var(--brass);color:var(--slate)}
.lj-chips a:focus-visible{outline:2px solid var(--brass);outline-offset:2px}
.lj-quiet{color:var(--slate-soft)}
.lj-none{
  margin:8px 0 0;padding:16px;font-size:.88rem;font-style:italic;color:var(--slate-soft);
  background:var(--surface);border:1px solid var(--line-light);border-radius:14px;
}
.lj-note{margin:10px 0 0;font-size:.84rem;color:var(--slate-soft);max-width:82ch}
.lj-note b{color:var(--heading)}
/* The flags band. The fixed --warn pair, as every other "somebody is waiting for you" box on the admin side
   is — and flex-wrap, because at 360px the button has to drop below the sentence rather than squeeze it. */
.lj-flag{
  display:flex;align-items:flex-start;gap:11px;flex-wrap:wrap;margin:10px 0 0;padding:14px 16px;
  border-radius:12px;font-size:.88rem;
  color:var(--warn-ink);background:var(--warn-fill);border:1px solid var(--warn-line);
}
.lj-flag b{color:var(--warn-ink)}
.lj-flag__body{flex:1 1 auto;min-width:16rem}
.lj-flag .mj-act{margin-left:auto;flex:none}
.lj-printlink{margin:16px 0 0}

@media(max-width:640px){
  .lj-chips{margin-left:0}
  .lj-flag .mj-act{margin-left:0;width:100%;justify-content:center;min-height:44px}
}

/* The maintainer's banner: this is somebody else's record, and it must never be mistaken for yours. */
.mj-whose{
  display:flex;align-items:flex-start;gap:13px;padding:14px 17px;border-radius:12px;margin:20px 0 6px;
  font-size:.88rem;color:var(--warn-ink);background:var(--warn-fill);border:1px solid var(--warn-line);
}
.mj-whose b{color:var(--warn-ink)}

@media(max-width:640px){
  /* Phase 5's narrow rules. Every one of these is a defect the last three phases shipped in a
     different box: a two-button row squeezing its words, a caption beside an image with no room,
     and a pair of option cards at 170px each. */
  .mj-toolbar{align-items:stretch}
  .mj-add{width:100%;min-height:44px}
  .mj-acts .mj-act{flex:1 1 auto;justify-content:center;min-height:44px}
  .mj-confirm{align-items:stretch}
  .mj-confirm form{flex-basis:100%}
  .mj-confirm__go,.mj-confirm__keep{flex:1 1 auto;justify-content:center;min-height:44px}
  .mj-shot{flex-direction:column}
  .mj-shot__img{width:100%;height:150px}
  .mj-grid,.mj-vis{grid-template-columns:1fr}
  .mj-file{align-items:flex-start;flex-direction:column}
  .mj-formacts .mj-add,.mj-formacts .mj-act{flex:1 1 100%;justify-content:center;min-height:44px}
}

@media(max-width:640px){
  /* The date moves above the card and the rule follows the dots, exactly as the .mp-* pages do. */
  .mj-timeline::before{left:29px}
  .mj-tl{grid-template-columns:60px 1fr}
  .mj-tl__when{grid-column:2;text-align:left;padding:0 0 6px;order:-1}
  .mj-tl__node{grid-row:span 2;grid-column:1;padding-top:4px}
  .mj-tl__card{grid-column:2}
  .mj-row{align-items:flex-start;gap:6px}
  .mj-row__date,.mj-row__what{min-width:0;flex-basis:100%}
  .mj-flag{width:100%;justify-content:center;min-height:44px}
  /* The sentence keeps the flag glyph's row — pushed to its own line it leaves the glyph orphaned
     above it — and only the button drops beneath. */
  .mj-flagged{gap:6px}
  .mj-flagged__form{margin-left:0;flex-basis:100%}
}

/* ============================================================================================
   MJ Phase 6a — /Members/Ritual, the ritual room (docs/members-journey-plan.md §12.7, approved
   supplement docs/mockups/journey-phase6.html screen D).

   Its OWN family, though the journey's .mj-* forms look almost the same. Borrowing a family for its
   look has cost this project twice already (an admin class that turned out to be display:none, and a
   shared kind list that silently changed a members' page), and these two pages will not stay
   identical: 6b adds a mentor panel here and nothing like it there.

   Every colour is a THEME token. The marker and the rollup use the three FIXED status pairs, never a
   brass fill — and no box on this page is brass-tinted AND brass-bordered, which is the combination
   that rendered as a pink box with a red border on the Grammarians palette earlier in this phase.
   ============================================================================================ */
.rr-notice{
  margin:22px 0 0;padding:.7rem .9rem;border-radius:10px;font-size:.88rem;
  color:var(--ok-ink);background:var(--ok-fill);border:1px solid var(--ok-line);
}
.rr-err{
  margin:0 0 12px;padding:.7rem .9rem;border-radius:10px;font-size:.86rem;
  color:var(--bad-ink);background:var(--bad-fill);border:1px solid var(--bad-line);
}

/* The honest empty room. Dashed, because it is a space waiting to be filled rather than a card
   reporting something. */
.rr-empty{
  border:1.5px dashed var(--line-light);border-radius:13px;padding:22px 24px;background:var(--surface);
}
.rr-empty h3{font-family:var(--serif);font-size:1.15rem;margin:0 0 8px;color:var(--heading)}
.rr-empty p{margin:0;font-size:.9rem;color:var(--slate-soft);max-width:62ch;line-height:1.65}
.rr-empty p+p{margin-top:9px}

/* ---- the content half: one card per section ---- */
.rr-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:16px;align-items:start}
.rr-sec{
  background:var(--surface);border:1px solid var(--line-light);border-radius:15px;padding:16px 20px;
  box-shadow:0 20px 44px -30px rgba(0,0,0,.6);
}
/* A locked section is quieter, not alarming: it is a section that exists and is not this brother's
   yet, which is a fact about his progress rather than a refusal. */
.rr-sec--locked{background:var(--parchment-2);box-shadow:none}
.rr-sec__head{display:flex;align-items:center;gap:11px;flex-wrap:wrap;margin-bottom:4px}
.rr-sec__head h3{font-family:var(--serif);font-size:1.12rem;margin:0;flex:1;min-width:120px;color:var(--heading)}
.rr-sec__sym{
  width:34px;height:34px;border-radius:50%;flex:none;display:flex;align-items:center;justify-content:center;
  font-family:var(--serif);font-size:.9rem;font-weight:700;
  background:var(--parchment-2);border:1px solid color-mix(in srgb, var(--brass) 45%, transparent);
  color:var(--heading);
}
.rr-sec__locked{margin:6px 0 0;font-size:.85rem;color:var(--slate-soft);line-height:1.6}

.rr-item{
  display:flex;align-items:center;gap:12px;padding:11px 0;flex-wrap:wrap;
  border-top:1px solid var(--line-light);
}
.rr-item__body{flex:1;min-width:150px;font-size:.9rem}
.rr-item__body a{font-weight:600;color:var(--heading);text-decoration:none}
.rr-item__body a:hover{text-decoration:underline}
.rr-item__body a:focus-visible{outline:2px solid var(--brass);outline-offset:2px;border-radius:4px}
.rr-item__body>span{display:block;font-weight:400;font-size:.76rem;color:var(--slate-soft)}
.rr-markform{margin:0;display:inline-flex}

/* The marker: ONE button that cycles. Three fixed pairs, and a 32px minimum because it is a target. */
.rr-mark{
  font-family:inherit;font-size:.62rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
  border-radius:999px;padding:6px 12px;min-height:32px;cursor:pointer;white-space:nowrap;
}
.rr-mark--none{color:var(--slate-soft);background:var(--parchment-2);border:1px solid var(--line-light)}
.rr-mark--part{color:var(--warn-ink);background:var(--warn-fill);border:1px solid var(--warn-line)}
.rr-mark--done{color:var(--ok-ink);background:var(--ok-fill);border:1px solid var(--ok-line)}
.rr-mark:focus-visible{outline:2px solid var(--brass);outline-offset:2px}

/* The read-only rollup in a section's heading. Same three pairs, no cursor: it is not a control. */
.rr-chip{
  display:inline-flex;align-items:center;gap:6px;font-size:.62rem;font-weight:700;letter-spacing:.08em;
  text-transform:uppercase;border-radius:999px;padding:4px 10px;white-space:nowrap;
}
.rr-chip--neutral{color:var(--slate-soft);background:var(--parchment-2);border:1px solid var(--line-light)}
.rr-chip--warn{color:var(--warn-ink);background:var(--warn-fill);border:1px solid var(--warn-line)}
.rr-chip--ok{color:var(--ok-ink);background:var(--ok-fill);border:1px solid var(--ok-line)}

/* ---- the learning half ---- */
.rr-two{display:grid;grid-template-columns:1fr 1fr;gap:18px;align-items:start}
.rr-pane{
  background:var(--surface);border:1px solid var(--line-light);border-radius:15px;padding:18px 20px;
  box-shadow:0 20px 44px -30px rgba(0,0,0,.6);
}
.rr-pane h3{font-family:var(--serif);font-size:1.14rem;margin:0 0 4px;color:var(--heading)}
.rr-pane__second{margin-top:22px !important;padding-top:18px;border-top:1px solid var(--line-light)}
.rr-pane__sub{margin:0 0 14px;font-size:.85rem;color:var(--slate-soft);line-height:1.6}
.rr-paneacts{display:flex;gap:9px;flex-wrap:wrap;margin-top:13px}

/* An empty list inside a working feature. Deliberately NOT .rr-empty: "you have written no cards" and
   "the lodge has published nothing" are different messages and must not look like the same one. */
.rr-none{
  margin:0;padding:13px 15px;border-radius:11px;font-size:.86rem;line-height:1.6;
  color:var(--slate-soft);background:var(--parchment-2);border:1px solid var(--line-light);
}

.rr-cue{
  border:1px solid var(--line-light);border-radius:11px;padding:12px 14px;background:var(--parchment-2);
  margin-bottom:10px;
}
.rr-cue__k{margin:0;font-size:.6rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--slate-soft)}
.rr-cue__q{margin:6px 0 0;font-family:var(--serif);font-size:1.06rem;color:var(--heading)}
.rr-cue__a{margin:7px 0 0;font-size:.84rem;color:var(--slate-soft);line-height:1.6;white-space:pre-line}
/* The covered prompt. The dashed box is where the words are NOT: they are absent from the HTML, and
   this is the space they will appear in. */
.rr-cue--hidden .rr-cue__a{
  border:1.5px dashed var(--line-light);border-radius:8px;padding:9px 11px;text-align:center;
  background:var(--surface);
}
.rr-cue__a--held{font-style:italic;color:var(--slate-soft);opacity:.85}
.rr-cue__acts{display:flex;gap:8px;flex-wrap:wrap;margin-top:11px;align-items:center}
.rr-cue__acts form{margin:0;display:inline-flex}

.rr-act{
  display:inline-flex;align-items:center;justify-content:center;gap:8px;text-decoration:none;
  font-family:inherit;font-weight:600;font-size:.86rem;border-radius:10px;padding:10px 16px;cursor:pointer;
  color:var(--slate);background:var(--surface);border:1px solid var(--line-light);
}
.rr-act:hover{border-color:var(--brass);color:var(--slate)}
.rr-act:focus-visible{outline:2px solid var(--brass);outline-offset:2px}
.rr-act--brass{background:var(--brass);color:var(--on-brass);border-color:var(--brass)}
.rr-act--brass:hover{color:var(--on-brass)}
.rr-act--small{font-size:.79rem;padding:7px 13px}
.rr-act--quiet{border-color:transparent;background:transparent;color:var(--slate-soft)}
.rr-act--quiet:hover{color:var(--slate)}

/* The inline form. NEUTRAL, not brass-tinted: a tint plus a brass border is a gold wash on Heritage
   and a pink box with a red border on Grammarians, which reads as validation failure — the defect a
   screenshot caught on /Admin/Documents earlier in this same phase. */
.rr-form{
  border:1px solid var(--line-light);border-radius:12px;padding:15px 16px;background:var(--parchment-2);
  margin-bottom:12px;
}
.rr-form__head{display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;margin-bottom:10px}
.rr-form__head h4{font-family:var(--serif);font-size:1.02rem;margin:0;color:var(--heading)}
.rr-fgrid{display:flex;flex-direction:column;gap:12px}
.rr-ff{display:flex;flex-direction:column;gap:6px;min-width:0}
.rr-ff>label{
  font-size:.66rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--slate-soft);
}
.rr-ff input[type=text],.rr-ff textarea{
  font-family:inherit;font-size:.9rem;color:var(--heading);background:var(--surface);
  border:1px solid var(--line-light);border-radius:9px;padding:9px 12px;min-width:0;
}
.rr-ff textarea{resize:vertical;min-height:74px;line-height:1.5}
.rr-ff input:focus-visible,.rr-ff textarea:focus-visible{outline:2px solid var(--brass);outline-offset:1px}
.rr-ff__help{margin:0;font-size:.76rem;color:var(--slate-soft)}
.rr-formacts{
  display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-top:14px;padding-top:13px;
  border-top:1px solid var(--line-light);
}

/* Removing a card, in two presses, in the card itself. */
.rr-confirm{
  display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-top:11px;padding:11px 13px;
  border-radius:10px;font-size:.85rem;
  color:var(--warn-ink);background:var(--warn-fill);border:1px solid var(--warn-line);
}
.rr-confirm>span:first-child{flex:1 1 auto;min-width:0}
.rr-confirm b{color:var(--warn-ink)}
.rr-confirm form{margin:0;display:inline-flex}
.rr-confirm__go,.rr-confirm__keep{
  font-family:inherit;font-size:.8rem;font-weight:600;border-radius:9px;padding:7px 13px;cursor:pointer;
  text-decoration:none;display:inline-flex;align-items:center;white-space:nowrap;
}
/* Same defect as .mj-confirm__go, copied with the idiom — and this one the crawler reached only at
   ?drop=. A confirm button is never on a page at rest, which is why both survived Phases 5 and 6. */
.rr-confirm__go{background:var(--bad-ink);color:var(--bad-fill);border:1px solid var(--bad-ink)}
.rr-confirm__keep{background:var(--surface);color:var(--slate);border:1px solid var(--warn-line)}
.rr-confirm__go:focus-visible,.rr-confirm__keep:focus-visible{outline:2px solid var(--brass);outline-offset:2px}

/* The diary's add row: dashed, because it is where something new goes. */
.rr-addrow{
  display:flex;gap:9px;flex-wrap:wrap;align-items:center;padding:12px 13px;
  border:1.5px dashed var(--line-light);border-radius:11px;background:var(--parchment-2);margin-bottom:12px;
}
.rr-addrow input{
  font-family:inherit;font-size:.86rem;color:var(--heading);background:var(--surface);
  border:1px solid var(--line-light);border-radius:9px;padding:8px 11px;min-width:0;
}
.rr-addrow input:focus-visible{outline:2px solid var(--brass);outline-offset:1px}
.rr-addrow .rr-grow{flex:1;min-width:110px}
.rr-addrow .rr-act{padding:8px 14px;font-size:.83rem}

/* Its own scroll container, so a long note never scrolls the page sideways. */
.rr-logwrap{overflow-x:auto}
.rr-log{width:100%;border-collapse:collapse;font-size:.87rem}
.rr-log th{
  text-align:left;font-size:.62rem;letter-spacing:.12em;text-transform:uppercase;color:var(--slate-soft);
  font-weight:700;padding:9px 10px;border-bottom:1px solid var(--line-light);white-space:nowrap;
}
.rr-log td{padding:10px;border-bottom:1px solid var(--line-light);color:var(--slate);vertical-align:top}
.rr-log tbody tr:last-child td{border-bottom:0}
.rr-log__dt{font-variant-numeric:tabular-nums;white-space:nowrap;color:var(--slate-soft)}
.rr-log__go{text-align:right;white-space:nowrap}
.rr-log__go form{margin:0}

.rr-lockline{display:flex;align-items:center;gap:10px;color:var(--slate-soft);font-size:.8rem;margin:24px 0 40px}

@media(max-width:960px){
  .rr-grid,.rr-two{grid-template-columns:1fr}
}

@media(max-width:640px){
  /* Screen E's three narrow rules. The marker beside a long title squeezed the title to four lines,
     so it takes a row of its own; every action is a 44px target; and the add row stacks. */
  .rr-item{align-items:flex-start}
  .rr-item__body{flex-basis:100%}
  .rr-markform{width:100%}
  .rr-mark{width:100%;min-height:44px}
  .rr-act{min-height:44px}
  .rr-cue__acts .rr-act,.rr-cue__acts form{flex:1 1 auto;justify-content:center}
  .rr-cue__acts form .rr-act{width:100%}
  .rr-paneacts .rr-act{flex:1 1 auto}
  .rr-addrow{flex-direction:column;align-items:stretch}
  .rr-addrow input,.rr-addrow .rr-act{width:100%}
  .rr-confirm{align-items:stretch}
  .rr-confirm form{flex-basis:100%}
  .rr-confirm__go,.rr-confirm__keep{flex:1 1 auto;justify-content:center;min-height:44px}
  .rr-formacts .rr-act{flex:1 1 100%}

  /* The diary stops being a table. Three text columns in a 290px pane wrapped the piece over four lines
     and the note over five, with Remove squeezed between them — the row technically fitting and not
     actually working, which is the defect Phase 3b found in the Steward's table and Phase 5 found in the
     flag line. Each line becomes a block: date, piece, note, and the action beside them. */
  .rr-log thead{display:none}
  .rr-log tbody tr{display:block;padding:11px 0;border-bottom:1px solid var(--line-light)}
  .rr-log tbody tr:last-child{border-bottom:0}
  .rr-log td{display:block;padding:0;border:0}
  .rr-log td:nth-child(2){font-weight:600;color:var(--heading);margin-top:2px}
  .rr-log td:nth-child(3){font-size:.85rem;color:var(--slate-soft);margin-top:2px}
  /* Blocks in DOM order — date, piece, note, then the action. A grid with the action spanning the three
     rows beside them put "Remove" above the date it belonged to, which is what the 360px shot showed. */
  .rr-log__go{margin-top:6px;text-align:right}
}

/* ============================================================================================
   MJ Phase 6b — the mentor panel, in the ritual room's hero band (approved supplement
   docs/mockups/journey-phase6.html screen A, docs/members-journey-plan.md §12.7).

   IT LIVES ON THE DARK BAND, which decides every colour here: text is --on-dark, accent text is
   --brass-soft and NEVER --brass (1.75:1 on the College palette, the correction the theme sweep
   made), and the buttons are the band's own pair — .mp-meeting__act--primary is a brass FILL under
   --on-brass, --ghost is an --on-dark outline. The panel shell itself is .mp-meeting, the same box
   the portal, the archive and the journey use, so a member meets one kind of panel on this site.

   THE STATUS CHIPS ARE THE EXCEPTION and are deliberately the FIXED pairs (--ok-*, --warn-*,
   --bad-*): those are light fill plus dark ink by definition, so they read on a dark band without a
   theme mapping — which is exactly why the site has them.
   ============================================================================================ */

/* The second column is a STACK now, not one panel: a brother can have a mentor, a request waiting on
   him, mentees of his own and his offer switch, and all four are drawn as panels by screen A. */
.rr-panels{display:flex;flex-direction:column;gap:14px;min-width:0}

/* .mp-meeting__act HAS ONLY EVER BEEN AN <a>. Every action in this panel is a form post, so the shared
   class now has to look right on a <button> too — a button carries its own border, its own font and no
   pointer, and without these three lines the brass "Accept" rendered as a grey system button with a
   1px inset ridge on every palette. The forms flex like the links beside them for the same reason: a
   <form> wrapper is not itself an .mp-meeting__act, so its button would not have stretched. */
.mp-meeting__acts form{margin:0;flex:1;display:flex;min-width:0}
.mp-meeting__acts form .mp-meeting__act{flex:1}
button.mp-meeting__act{font-family:inherit;font-size:.82rem;cursor:pointer;border:0}
/* BACKGROUND:TRANSPARENT IS THE WHOLE OF THIS RULE'S REASON, and only a screenshot found it. The ghost
   variant sets a border and a colour and no background, which is right for the <a> it has always been —
   an anchor has no background — and wrong for a <button>, which carries the UA's `buttonface` grey. On
   the dark band that rendered as a near-white box with --on-dark text inside it: "End the arrangement"
   and "Decline" were invisible, and every count assertion in the net was perfectly happy.
   Element-qualified so it beats the class rule whatever the source order; the PRIMARY variant is left
   alone, because its brass fill is exactly what a button should have. */
button.mp-meeting__act--ghost{
  background:transparent;
  border:1px solid color-mix(in srgb, var(--on-dark) 28%, transparent);
}

/* A request is the one panel that wants the eye. A brass BORDER and no fill: a tint plus a brass edge
   is a gold wash on Heritage and a pink box with a red border on Grammarians, which reads as
   validation failure — the defect a screenshot caught on /Admin/Documents in Phase 6a. */
.rr-req{border-color:color-mix(in srgb, var(--brass) 44%, transparent)}

.rr-mentor{display:flex;gap:12px;align-items:flex-start;min-width:0}
.rr-mentor__av{
  width:34px;height:34px;border-radius:50%;flex:none;display:flex;align-items:center;
  justify-content:center;font-weight:700;font-size:.76rem;color:var(--on-dark);
  background:color-mix(in srgb, var(--on-dark) 14%, transparent);
  border:1px solid var(--line-dark);
}
.rr-mentor__av--none{color:var(--brass-soft);font-size:.95rem}
.rr-mentor__nm{
  margin:0;font-family:var(--serif);font-size:1.02rem;line-height:1.3;color:var(--on-dark);
  overflow-wrap:anywhere;
}
/* The rank-and-progress line under a name, inside .rr-mentor__nm on the picker and mentees rows. */
.rr-mentor__nm>span{
  display:block;font-family:var(--sans);font-size:.74rem;font-weight:400;margin-top:2px;
  color:color-mix(in srgb, var(--on-dark) 68%, transparent);
}
.rr-mentor__sub{
  margin:4px 0 0;font-size:.84rem;line-height:1.55;
  color:color-mix(in srgb, var(--on-dark) 74%, transparent);
}

/* "Waiting for him to accept". A fixed pair, so it reads on the band whatever the palette. */
.rr-state{
  display:inline-flex;align-items:center;gap:6px;font-size:.66rem;font-weight:700;letter-spacing:.08em;
  text-transform:uppercase;border-radius:999px;padding:4px 10px;white-space:nowrap;
  color:var(--warn-ink);background:var(--warn-fill);border:1px solid var(--warn-line);
}

/* The consent strip, and the one honest line after a decline. */
.rr-consent{
  margin:14px 0 0;padding:11px 13px;border-radius:11px;font-size:.83rem;line-height:1.55;
  background:color-mix(in srgb, var(--on-dark) 7%, transparent);
  border:1px solid var(--line-dark);
  color:color-mix(in srgb, var(--on-dark) 82%, transparent);
}
.rr-consent b{color:var(--on-dark)}
.rr-consent form{margin:11px 0 0}
.rr-consent .mp-meeting__act{display:inline-flex;justify-content:center;flex:0 1 auto}

/* A refused write, inside the panel where the press was. The fixed bad pair, for the band. */
.rr-perr{
  margin:13px 0 0;padding:.6rem .8rem;border-radius:10px;font-size:.83rem;line-height:1.5;
  color:var(--bad-ink);background:var(--bad-fill);border:1px solid var(--bad-line);
}

/* ---- the picker (A1) ---- */
.rr-search{display:flex;gap:8px;margin:12px 0 0;align-items:center;flex-wrap:wrap}
.rr-search input[type=text]{
  flex:1 1 130px;min-width:0;font-family:inherit;font-size:.86rem;color:var(--on-dark);
  background:color-mix(in srgb, var(--on-dark) 8%, transparent);
  border:1px solid var(--line-dark);border-radius:9px;padding:8px 11px;
}
.rr-search input[type=text]::placeholder{color:color-mix(in srgb, var(--on-dark) 52%, transparent)}
.rr-search input[type=text]:focus-visible{outline:2px solid var(--brass-soft);outline-offset:1px}
.rr-search .mp-meeting__act{flex:0 0 auto}

.rr-pick,.rr-mentees{display:flex;flex-direction:column;gap:8px;margin-top:12px}
.rr-pick__row,.rr-mentees__row{
  display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:9px 11px;border-radius:11px;
  background:color-mix(in srgb, var(--on-dark) 6%, transparent);
  border:1px solid var(--line-dark);
}
.rr-pick__row .rr-mentor__nm,.rr-mentees__row .rr-mentor__nm{flex:1 1 110px;min-width:0;font-size:.92rem}
.rr-pick__row form,.rr-mentees__row form{margin:0}
.rr-pick__row .mp-meeting__act,.rr-mentees__row .mp-meeting__act{flex:0 0 auto;padding:8px 13px}

/* The empty picker. NOT a validation tone: nobody has done anything wrong, the lodge simply has no
   volunteers yet — and on this site that is the day-one state rather than an edge case. */
.rr-dempty{
  margin-top:12px;padding:12px 13px;border-radius:11px;font-size:.83rem;line-height:1.6;
  background:color-mix(in srgb, var(--on-dark) 7%, transparent);
  border:1px solid var(--line-dark);
  color:color-mix(in srgb, var(--on-dark) 78%, transparent);
}
.rr-dempty b{display:block;color:var(--on-dark);margin-bottom:4px}

/* "2 confident" beside a mentee. A chip on the band, so it borrows the band's own weight rather than
   one of the three status pairs: it is a count, not a state. */
.rr-dchip{
  display:inline-flex;align-items:center;font-size:.66rem;font-weight:700;letter-spacing:.08em;
  text-transform:uppercase;border-radius:999px;padding:4px 10px;white-space:nowrap;flex:0 0 auto;
  color:var(--brass-soft);background:color-mix(in srgb, var(--brass) 15%, transparent);
  border:1px solid color-mix(in srgb, var(--brass) 34%, transparent);
}

/* ---- the message box (A4) ---- */
.rr-msg{margin:14px 0 0}
.rr-msg textarea{
  width:100%;box-sizing:border-box;font-family:inherit;font-size:.87rem;line-height:1.5;
  color:var(--on-dark);background:color-mix(in srgb, var(--on-dark) 8%, transparent);
  border:1px solid var(--line-dark);border-radius:10px;padding:10px 12px;resize:vertical;min-height:76px;
}
.rr-msg textarea::placeholder{color:color-mix(in srgb, var(--on-dark) 52%, transparent)}
.rr-msg textarea:focus-visible{outline:2px solid var(--brass-soft);outline-offset:1px}

/* ---- his own offer (A6) ---- */
.rr-offer{margin:14px 0 0}
.rr-offer__row{display:flex;gap:11px;align-items:flex-start;cursor:pointer}
.rr-offer__row input[type=checkbox]{
  accent-color:var(--brass);width:16px;height:16px;margin:3px 0 0;flex:none;cursor:pointer;
}
.rr-offer__row>span{min-width:0}
.rr-offer__row b{display:block;font-size:.9rem;color:var(--on-dark)}
.rr-offer__row>span>span{
  display:block;font-size:.82rem;line-height:1.55;margin-top:3px;
  color:color-mix(in srgb, var(--on-dark) 74%, transparent);
}
.rr-offer__row input[type=checkbox]:focus-visible{outline:2px solid var(--brass-soft);outline-offset:2px}

/* The fine print under every panel, and the plain link the picker's Cancel is. */
.rr-fine{
  margin:13px 0 0;font-size:.76rem;line-height:1.55;
  color:color-mix(in srgb, var(--on-dark) 62%, transparent);
}
.rr-plain{color:var(--brass-soft);text-decoration:underline;text-underline-offset:2px}
.rr-plain:hover{color:var(--brass-soft)}
.rr-plain:focus-visible{outline:2px solid var(--brass-soft);outline-offset:2px;border-radius:4px}

@media(max-width:640px){
  /* Every action a 44px target, and each on its own line: the picker's row is a name, a rank and a
     button in a 300px panel, which put "Nominate" beside a name broken over three lines. */
  .rr-pick__row,.rr-mentees__row{align-items:stretch;flex-direction:column}
  .rr-pick__row .mp-meeting__act,.rr-mentees__row .mp-meeting__act{
    width:100%;justify-content:center;min-height:44px;
  }
  .rr-dchip{align-self:flex-start}
  .rr-consent .mp-meeting__act{width:100%;min-height:44px}
  .rr-search{flex-direction:column;align-items:stretch}
  .rr-search input[type=text],.rr-search .mp-meeting__act{width:100%;min-height:44px}
  .rr-search .mp-meeting__act{display:flex;justify-content:center;align-items:center}
  /* THE SAME TRAP, TWICE MORE, and both were visible only in the 360px shot: a flex-BASIS is a height once
     the row becomes a column, so the search box grew to 130px of empty and each candidate's name block to
     110px. Nothing overflowed and every selector still matched — see the same note on .mn-ff. */
  .rr-search input[type=text]{flex:0 0 auto}
  .rr-pick__row .rr-mentor__nm,.rr-mentees__row .rr-mentor__nm{flex:0 0 auto}
}

/* ============================================================================================
   MJ Phase 6b — /Admin/Mentoring (approved supplement docs/mockups/journey-phase6.html screen C).

   Its OWN family, though it is one table and one form away from /Admin/Journeys' .mj-roll. That
   likeness is exactly the reason: an admin class borrowed for its look was display:none the last time
   this project tried it, and a shared kind list silently changed a members' page the time before.

   The state chips are the three FIXED pairs (--ok-*, --warn-*, --neutral surface), which is the site's
   one vocabulary for saying where something stands. Nothing here is brass-tinted AND brass-bordered:
   that combination is a gold wash on Heritage and a pink box with a red border on Grammarians.
   ============================================================================================ */
.mn-section{padding:1.25rem 0 3rem}
.mn-notice{
  margin:0 0 18px;padding:.7rem .9rem;border-radius:10px;font-size:.88rem;
  color:var(--ok-ink);background:var(--ok-fill);border:1px solid var(--ok-line);
}

/* Its own scroll container, so five columns never scroll the page sideways. */
.mn-wrap{
  overflow-x:auto;border:1px solid var(--line-light);border-radius:14px;background:var(--surface);
  margin:16px 0 0;
}
.mn-tbl{width:100%;border-collapse:collapse;font-size:.9rem}
.mn-tbl th{
  text-align:left;font-size:.63rem;letter-spacing:.12em;text-transform:uppercase;color:var(--slate-soft);
  font-weight:700;padding:11px 16px;border-bottom:1px solid var(--line-light);white-space:nowrap;
}
.mn-tbl td{
  padding:12px 16px;border-bottom:1px solid var(--line-light);color:var(--slate);vertical-align:middle;
}
.mn-tbl tbody tr:last-child td{border-bottom:0}
.mn-tbl tbody tr:hover{background:color-mix(in srgb, var(--brass) 6%, var(--surface))}
.mn-tbl__who b{display:block;color:var(--heading);font-weight:600}
.mn-tbl__who span{display:block;font-size:.76rem;color:var(--slate-soft);margin-top:1px}
.mn-tbl__since{white-space:nowrap;color:var(--slate-soft);font-size:.83rem}
.mn-tbl__go{text-align:right;white-space:nowrap}
.mn-tbl__go form{margin:0}
.mn-tbl__empty{color:var(--slate-soft);text-align:center;padding:26px 16px}

.mn-chip{
  display:inline-flex;align-items:center;gap:6px;font-size:.62rem;font-weight:700;letter-spacing:.08em;
  text-transform:uppercase;border-radius:999px;padding:4px 10px;white-space:nowrap;
}
.mn-chip--neutral{color:var(--slate-soft);background:var(--parchment-2);border:1px solid var(--line-light)}
.mn-chip--warn{color:var(--warn-ink);background:var(--warn-fill);border:1px solid var(--warn-line)}
.mn-chip--ok{color:var(--ok-ink);background:var(--ok-fill);border:1px solid var(--ok-line)}

/* Pairing. Two selects and a button, dashed because it is where something new goes — the same signal
   the ritual room's diary add-row uses. */
.mn-pair{
  display:flex;gap:14px;align-items:flex-end;flex-wrap:wrap;padding:15px 16px;margin-top:4px;
  border:1.5px dashed var(--line-light);border-radius:12px;background:var(--parchment-2);
}
.mn-ff{display:flex;flex-direction:column;gap:6px;flex:1 1 220px;min-width:0}
.mn-ff>label{
  font-size:.66rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase;color:var(--slate-soft);
}
.mn-ff select{
  font-family:inherit;font-size:.9rem;color:var(--heading);background:var(--surface);
  border:1px solid var(--line-light);border-radius:9px;padding:9px 12px;min-width:0;
}
.mn-ff select:focus-visible{outline:2px solid var(--brass);outline-offset:1px}
.mn-ff__help{font-size:.76rem;color:var(--slate-soft);line-height:1.55}
/* The count of brethren who have not offered. Emphasised because it is the sentence that stops a
   Secretary hunting for a name the page is never going to show him. */
.mn-ff__help b{display:block;color:var(--heading);margin-top:3px}

.mn-act{
  font-family:inherit;font-size:.82rem;font-weight:600;text-decoration:none;cursor:pointer;
  display:inline-flex;align-items:center;justify-content:center;gap:7px;white-space:nowrap;
  border-radius:9px;padding:9px 15px;
  color:var(--slate);background:var(--surface);border:1px solid var(--line-light);
}
.mn-act:hover{border-color:var(--brass);color:var(--slate)}
.mn-act:focus-visible{outline:2px solid var(--brass);outline-offset:2px}
.mn-act--brass{background:var(--brass);color:var(--on-brass);border-color:var(--brass)}
.mn-act--brass:hover{color:var(--on-brass)}

.mn-note{margin:1.4rem 0 0;font-size:.78rem;color:var(--slate-soft);line-height:1.7;max-width:74ch}
.mn-note b{color:var(--heading)}

@media(max-width:640px){
  /* The table stops being a table. Five columns in a 330px panel wrapped both names over three lines
     each with "Cancel the request" squeezed between them — the row technically fitting and not
     actually working, which is the defect the last three phases each shipped in a different box. */
  .mn-tbl thead{display:none}
  .mn-tbl tbody tr{display:block;padding:12px 14px;border-bottom:1px solid var(--line-light)}
  .mn-tbl tbody tr:last-child{border-bottom:0}
  .mn-tbl td{display:block;padding:0;border:0}
  .mn-tbl td+td{margin-top:7px}
  /* WITH THE HEADER ROW GONE, two names in a column say nothing about which is which. Each cell
     carries its own label from data-label, which is why the markup sets one on every td — a stacked
     row reading "Bro. Bell / W.Bro. Harding" is the phone version of an unlabelled field. */
  .mn-tbl td[data-label]::before{
    content:attr(data-label);display:block;font-size:.6rem;font-weight:700;letter-spacing:.12em;
    text-transform:uppercase;color:var(--slate-soft);margin-bottom:2px;
  }
  .mn-tbl__go{text-align:left}
  .mn-tbl__go .mn-act{width:100%;min-height:44px}
  .mn-pair{flex-direction:column;align-items:stretch}
  /* flex-basis IS A HEIGHT once the row becomes a column, so `flex:1 1 220px` gave each select a 220px-tall
     box and left two ragged gaps down the form. Only the 360px shot showed it: nothing overflowed, nothing
     wrapped, and every selector in the net still found its select. */
  .mn-pair .mn-ff{flex:0 0 auto}
  .mn-pair .mn-act{width:100%;min-height:44px}
}

/* ===================================================================================================
   HW, the wiki chrome (.hlp-*). docs/members-help-plan.md, approved mockup
   docs/mockups/members-help.html.

   ONE new family. It restyles no existing surface: the contents page and every article borrow the
   members' area's own `.mp-hero`, `.mp-sect` and `.mp-cta` and add only what a guide needs — a card
   for a topic, a control-and-outcome list, the "?" and the portal band.

   DG (backlog item 8) RENAMED THIS BLOCK FROM "the members' help wiki" AND CHANGED NOTHING ELSE IN IT.
   The designer's guide under /Admin/Guide is a second wiki, and it renders `.hlp-art`, `.hlp-prose`,
   `.hlp-does`, `.hlp-set`, `.hlp-ctrl`, `.hlp-side`, `.hlp-index`, `.hlp-card`, `.hlp-chip`,
   `.hlp-count` and `.hlp-nav` unchanged. Copying eleven rules under a `.gd-` prefix would have been a
   second copy free to drift from this one, and drift between two rules is invisible to any test that
   reads only one of them, the same argument `--shell-lift` and `--shell-board` are composed for,
   fifteen thousand lines above. What the guide adds of its own is the SPECIMEN, and that is the whole
   of the `.gd-*` block further down. The two families are `.hlp-` = a wiki article's chrome,
   `.gd-` = a live component in one, and `.wf-` = a drawn diagram in either.

   THE ONE THING TO BE CAREFUL WITH is the "?" (`.hlp-q`). It lands on THREE header families and two
   different grounds: `.mp-hero` and `.page-hero--admin` are var(--ink), `.page-hero--light` is
   var(--parchment-2). An accent legible on one is not legible on the other — --brass-soft on ink,
   --brass-ink on parchment — which is why there are two modifiers rather than one class. --brass as
   TEXT on a dark band measures 1.75:1 on College, the defect the portal's contrast guard already
   caught once (see the accent note in this file's header).
   =================================================================================================== */

.hlp-body{padding-bottom:56px}

/* ---- the "?" ---------------------------------------------------------------------------------- */
.hlp-q{
  display:inline-flex;align-items:center;justify-content:center;gap:.45rem;
  min-height:44px;                    /* a thumb target on a phone (WCAG 2.5.5), as the admin back link */
  padding:.28rem .85rem;border-radius:999px;text-decoration:none;
  font-size:.85rem;font-weight:600;letter-spacing:.02em;white-space:nowrap;
  border:1px solid transparent;
}
.hlp-q__mark{
  display:inline-flex;align-items:center;justify-content:center;flex:none;
  width:20px;height:20px;border-radius:50%;font-size:.74rem;font-weight:700;line-height:1;
}
.hlp-q--dark{
  color:var(--brass-soft);
  border-color:color-mix(in srgb, var(--brass-soft) 42%, transparent);
  background:color-mix(in srgb, var(--on-dark) 7%, transparent);
}
/* A CHIP BEHIND ACCENT TEXT MUST BE TINTED FROM THE GROUND, NEVER FROM THE ACCENT ITSELF — HW Phase 4,
   and it is a rule this file's header did not state. --brass-ink carries a DERIVED 4.5:1 floor
   (ThemeStyleComposer.ComputeReadableAccent, against --surface, --parchment and --parchment-2), and a
   derived floor is computed against those grounds UNTINTED. This family shipped washing its own ground
   with 7% of that token for the pill and 16% for the chip; every point of wash moves the ground toward the
   text, so a token guaranteed at exactly 4.5:1 has NO headroom for any wash at all. Measured on eleven
   palettes: pill 4.14, glyph 3.43. Bare --parchment-2 measures 4.53 — the floor itself, showing through.

   Same mechanism, larger tint, on the dark band: the chip's 22% of --brass-soft took a token that is
   >= 8.18:1 on a dark ground down to 4.13 (Grammarians), 4.24 (College), 4.37 (Regalia), 4.48 (Laurel).

   The grounds below are the three the composer computes against, and any mix of them, which is why they
   are safe by construction rather than by measurement luck. Worst of eleven palettes, after:
     dark  pill 5.82  glyph 6.75  (hover 8.76 / 6.22)
     light pill 4.77  glyph 4.53  (hover 4.53 / 4.53)
   AND THE HOVER RULES ARE PART OF THIS, which is MJ Phase 8's lesson arriving in a second family: a page
   list is a list of STATES, and no crawl measures :hover. The light hover was the WORST of the six pairs
   at 3.88 on Alabaster and 3.93 on Heritage, and nothing on this site could have reported it.
   StylesheetPairingTests holds the rule now, in every state, with no page and no browser. */
.hlp-q--dark .hlp-q__mark{background:color-mix(in srgb, var(--ink-deep) 45%, transparent);color:var(--brass-soft)}
.hlp-q--dark:hover,.hlp-q--dark:focus-visible{
  color:var(--on-dark);border-color:var(--brass-soft);
  background:color-mix(in srgb, var(--on-dark) 14%, transparent);
}
.hlp-q--light{
  color:var(--brass-ink);
  border-color:color-mix(in srgb, var(--brass-ink) 34%, transparent);
  background:color-mix(in srgb, var(--surface) 70%, transparent);
}
.hlp-q--light .hlp-q__mark{background:var(--surface);color:var(--brass-ink)}
.hlp-q--light:hover,.hlp-q--light:focus-visible{
  border-color:var(--brass-ink);background:var(--surface);
}
.hlp-q:focus-visible{outline:2px solid var(--brass);outline-offset:2px}
/* WHERE it sits, and it is the one thing about the "?" the partial cannot own. The members' area has no
   shared header (§4's finding, and the reason this is fourteen call sites rather than one), so each band
   places it — and the place has to be the SAME on all three, or the guide's own door moves as a brother
   walks the room.

   It is the last thing in a page's own introduction: under the lead, level with .mp-rank. Mockup screen D
   drew it top-right beside the title, on a band with NOTHING in its second column. Four of the six real
   .mp-hero bands put a panel exactly there — the next meeting, the lodge's papers, the journey band — so
   top-right is drawn rather than available, and an absolutely-positioned pill over a responsive panel is a
   collision waiting for a narrow viewport. HW Phase 3 §7.5. */
.mp-hero__intro > .hlp-q,
.page-hero .wrap > .hlp-q{margin-top:18px}

/* ---- the contents page -------------------------------------------------------------------------
   TWO columns, not three. A topic card is a title, one sentence and the page it documents; at a third
   of the 1180px wrapper every summary breaks to four lines and the group stops being scannable. Ten
   cards in two columns is five clean rows and the duty group is two, so neither leaves a hole — which
   is the same arithmetic rule the portal's grids are held to (§12.1.7). */
.hlp-count{
  margin:0;font-size:.72rem;font-weight:700;letter-spacing:.12em;text-transform:uppercase;
  color:var(--slate-soft);font-variant-numeric:tabular-nums lining-nums;
}
.hlp-index{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;padding-bottom:6px}
/* ONE card gets ONE column. Two columns holding a single card is a half-width card beside an empty half,
   which reads as a grid that failed to load — the same arithmetic rule the portal's grids are held to
   (§12.1.7), and the one this page was a screenshot away from breaking while Phase 2 is still writing
   articles. Nothing but a screenshot notices: the markup, the build and every selector are happy. */
.hlp-index--one{grid-template-columns:minmax(0,1fr)}
/* minmax(0,1fr), never a bare 1fr: a bare `1fr` is minmax(auto,1fr), so a track cannot shrink below its
   content and one long word pushes the whole grid past its container. Four instances of that trap have
   been found on this site; this is the family not repeating it. */
.hlp-card{
  display:flex;flex-direction:column;gap:7px;
  background:var(--surface);border:1px solid var(--line-light);border-radius:14px;padding:17px 19px;
  text-decoration:none;color:inherit;
  transition:transform .15s, border-color .15s, box-shadow .15s;
}
.hlp-card:hover{
  transform:translateY(-2px);border-color:var(--brass);
  box-shadow:0 18px 38px -30px rgba(0,0,0,.7);
}
.hlp-card:focus-visible{outline:2px solid var(--brass);outline-offset:3px}
.hlp-card__t{display:flex;align-items:baseline;gap:10px;flex-wrap:wrap}
.hlp-card__t h3{font-family:var(--serif);font-weight:600;font-size:1.1rem;margin:0;color:var(--heading)}
.hlp-card__s{font-size:.85rem;color:var(--slate-soft)}
/* The route the article describes. Monospace because it IS an address, and dashed above rather than a
   solid rule so it reads as a footnote to the card and not as a second section of it. */
/* overflow-wrap, as .hlp-side__where has: the register topic's card prints BOTH of its doors on this line
   ("/Admin/Attendance · /Admin/FestiveBoard/Tonight"), which is one unbreakable token per path in a
   monospace face inside a 360px column. */
.hlp-card__p{
  font-size:.72rem;color:var(--slate-soft);font-family:ui-monospace,Menlo,monospace;
  padding-top:6px;border-top:1px dashed var(--line-light);overflow-wrap:anywhere;
}
/* A duty card is marked by a brass edge AND a worded chip. The edge alone would be colour carrying
   meaning on its own (WCAG 1.4.1); the chip is what a listener and a colour-blind reader get. */
.hlp-card--duty{border-left:3px solid color-mix(in srgb, var(--brass) 60%, transparent)}
.hlp-chip{
  display:inline-flex;align-items:center;font-size:.62rem;font-weight:700;letter-spacing:.08em;
  text-transform:uppercase;border-radius:999px;padding:4px 10px;white-space:nowrap;
  color:var(--heading);background:color-mix(in srgb, var(--brass) 16%, var(--surface));
  border:1px solid color-mix(in srgb, var(--brass) 45%, transparent);
}

/* ---- an article -------------------------------------------------------------------------------- */
.hlp-art{display:grid;grid-template-columns:minmax(0,1fr) 268px;gap:34px;align-items:start;padding-top:34px}
.hlp-prose{max-width:66ch}
/* DIRECT CHILDREN, AND A SCREENSHOT IS WHY (DG, backlog item 8).

   These two were descendant selectors until the designer's guide put a LIVE COMPONENT inside an article.
   `.hlp-prose h2` weighs 0,1,1, the same as `.home-widget--heritage h2` fifteen thousand lines above it,
   and a tie is settled by source order, so the prose column won, and every specimen heading on the page
   came back `var(--heading)` whatever shell it was sitting on. Measured on the first render: `#111` on the
   Feature shell's `#111` ground. **1.00:1. The heading was not faint, it was absent.**

   The unit suite could not see it and neither could the roster tests: the markup is correct, the classes
   are correct, the product's own rule exists and is correct, and the cascade quietly picked the other one.
   Only the screenshot did.

   IT IS FIXED HERE RATHER THAN IN THE GUIDE, and the narrowing costs the members' help nothing: measured
   over all fourteen articles, there is not one h2 or h3 nested inside anything, and every nested `p` is
   inside `.hlp-set`, whose own `p` rule already won this same tie. `> h2:first-child` on the line below was
   already written this way, which is the family's author having had the right instinct once.

   The general form is worth keeping: a rule about A PROSE COLUMN should select the column's own children,
   or it is a rule about everything anybody ever puts in one. */
.hlp-prose > h2{font-family:var(--serif);font-weight:600;font-size:1.3rem;margin:30px 0 10px;color:var(--heading)}
.hlp-prose > h2:first-child{margin-top:0}
.hlp-prose > p{margin:0 0 12px}

/* The article's spine: a control, and what the software does when you press it. A grid rather than a
   floated <dl>, so a long control name wraps inside its own column instead of pushing its answer down. */
.hlp-does{display:grid;grid-template-columns:13.5rem minmax(0,1fr);margin:14px 0 4px;border-top:1px solid var(--line-light)}
.hlp-does dt{
  padding:13px 16px 13px 0;border-bottom:1px solid var(--line-light);
  font-weight:600;color:var(--heading);font-size:.92rem;
}
.hlp-does dd{padding:13px 0;border-bottom:1px solid var(--line-light);margin:0;font-size:.92rem}
.hlp-does dd b{color:var(--heading)}
/* The words as they appear on the control. A chip rather than <code>, because the reader is looking for
   these words on a button and not at a code sample. */
.hlp-ctrl{
  display:inline-block;background:var(--parchment-2);border:1px solid var(--line-light);
  border-radius:7px;padding:.1rem .45rem;font-size:.86rem;font-weight:600;color:var(--heading);
}
/* Where behaviour depends on a lodge setting — a distinct KIND of sentence (§2's third rule): not
   "what the software does" but "what your lodge has chosen". Also the duty note at the top of an
   article somebody opened without holding the duty. */
.hlp-set{
  border-left:3px solid color-mix(in srgb, var(--brass) 55%, transparent);
  background:var(--parchment-2);border-radius:0 11px 11px 0;padding:13px 16px;margin:16px 0;font-size:.88rem;
}
.hlp-set b{color:var(--heading)}
.hlp-set p{margin:0 0 8px}
.hlp-set p:last-child{margin-bottom:0}

.hlp-side{display:flex;flex-direction:column;gap:14px;position:sticky;top:24px}
.hlp-side__box{background:var(--surface);border:1px solid var(--line-light);border-radius:13px;padding:15px 17px}
.hlp-side__k{
  margin:0 0 9px;font-size:.62rem;font-weight:700;letter-spacing:.14em;text-transform:uppercase;
  color:var(--slate-soft);
}
.hlp-side__go{width:100%;justify-content:center}
.hlp-side__where{
  margin:9px 0 0;font-size:.74rem;color:var(--slate-soft);font-family:ui-monospace,Menlo,monospace;
  overflow-wrap:anywhere;
}
.hlp-side__list{margin:0;padding-left:18px;font-size:.84rem}
.hlp-side__list li{margin-bottom:5px}
.hlp-side__list a{color:var(--brass-ink);text-decoration:none}
.hlp-side__list a:hover{text-decoration:underline}
.hlp-side__list li[aria-current="page"]{font-weight:700;color:var(--heading)}

.hlp-nav{display:flex;gap:12px;justify-content:space-between;flex-wrap:wrap;margin:34px 0 0;padding-top:18px;border-top:1px solid var(--line-light)}
.hlp-nav:empty{display:none}
.hlp-nav a{
  display:flex;flex-direction:column;gap:2px;text-decoration:none;
  border:1px solid var(--line-light);border-radius:11px;padding:11px 15px;
  background:var(--surface);min-width:220px;
}
.hlp-nav a:hover{border-color:var(--brass)}
.hlp-nav a:focus-visible{outline:2px solid var(--brass);outline-offset:2px}
.hlp-nav span{font-size:.66rem;font-weight:700;letter-spacing:.13em;text-transform:uppercase;color:var(--slate-soft)}
.hlp-nav b{color:var(--heading);font-weight:600;font-size:.86rem}
.hlp-nav a.next{text-align:right;margin-left:auto}

/* ---- the portal band ----------------------------------------------------------------------------
   The approved entry point (mockup screen E2), and the reason it is a BAND rather than an eighth tile:
   it is not a grid item at all, so `.mp-grid--live`'s locked six-at-repeat(3) plus the conditional
   full-width year tile is untouched, and no arrangement leaves a hole. It also renders for a brother
   the year tile is absent for, which is exactly the brother most likely to want a guide. */
.hlp-portal{
  display:flex;align-items:center;gap:20px;flex-wrap:wrap;
  background:var(--parchment-2);border:1px solid var(--line-light);
  border-left:3px solid var(--brass);border-radius:14px;padding:17px 22px;margin:8px 0 0;
}
.hlp-portal__icon{font-size:1.5rem;line-height:1;flex:none}
.hlp-portal__body{flex:1;min-width:240px}
.hlp-portal__body b{display:block;font-family:var(--serif);font-weight:600;font-size:1.15rem;color:var(--heading)}
.hlp-portal__body span{font-size:.86rem;color:var(--slate-soft)}
.hlp-portal .mp-cta{flex:none}

/* ITEM 42 SLICE 3 -- THE MESSAGES BAND ON THE PORTAL.

   A BAND AND NOT A SEVENTH EQUAL TILE, which is exactly the choice HW Phase 3
   made for the guide band above: six equal tiles at repeat(3) plus the
   conditional full-width year tile is the arithmetic plan rule §12.1.7 locks,
   and a seventh EQUAL tile is 3+3+1 with a two-cell hole. A band is not a grid
   item, so the locked arithmetic is untouched.

   ITS OWN CLASS AND NOT .hlp-portal, EVEN THOUGH IT IS THE SAME SHAPE.
   Borrowing a family for its look is a mistake this project has paid for
   twice -- see _MentorPanel's note about .mj-*. The declarations that ARE the
   shape are shared through this selector list; the accent is the only thing
   that differs, and it differs on purpose: the guide is the site explaining
   itself and this is somebody waiting for an answer. */
/* ITEM 46 SLICE 1, GATE Q1(a) -- THE BAND IS NOW THE FIRST THING UNDER THE
   HERO, AND THAT CHANGED WHAT IT HAS TO DO RATHER THAN JUST WHERE IT SITS.

   `margin:8px 0 0` was written for a band following a grid of tiles. As the
   first child of .wrap it meets the dark hero instead, and 8px of parchment
   between the two reads as a gap somebody forgot to close. The page already has
   a vocabulary for the thing that meets the hero: .mp-glance rides its bottom
   edge on a NEGATIVE margin with position:relative and z-index:2, so the cards
   straddle the dark band. The band takes that idiom over, with the same two
   numbers -- -26px and -18px below 600 -- and the same lift .mp-gcard uses, so
   it reads as sitting on the hero rather than beside it.

   AND THE STRIP'S OWN PULL HAD TO BE CANCELLED, WHICH A SCREENSHOT FOUND AND NO
   ASSERTION IN THIS REPOSITORY WOULD HAVE. The -26px is written for a strip
   whose previous sibling is the hero. Put a band in front of it and the pull
   still happens -- against the BAND -- so at 390 the glance cards climbed 18px
   into the band's bottom-left corner and painted over it, z-index:2 and all.
   Measured before the fix: band 751..963, strip starting at 945 on a phone;
   417..510 against 483 at 1280; 849..1105 against 1086 at 320. Nothing failed.

   THE CANCELLATION IS ON THE ADJACENT SIBLING AND NOT ON .mp-glance ITSELF,
   because the pull is still right for the case it was written for -- a portal
   with the band absent is a page where the strip meets the hero again. The
   selector says the condition out loud, and it is one specificity point
   stronger than the rule it corrects, which is what makes it win in the phone
   media query at line 13321 as well as at the desk. */
.cnv-portal{
  display:flex;align-items:center;gap:20px;flex-wrap:wrap;
  background:var(--parchment-2);border:1px solid var(--line-light);
  border-left:3px solid var(--brass-ink);border-radius:14px;padding:17px 22px;
  margin:-26px 0 0;position:relative;z-index:2;
  box-shadow:0 20px 44px -30px rgba(0,0,0,.6);
}
.cnv-portal + .mp-glance{margin-top:24px}
.cnv-portal__icon{font-size:1.5rem;line-height:1;flex:none}
.cnv-portal__body{flex:1;min-width:240px}
.cnv-portal__body b{display:block;font-family:var(--serif);font-weight:600;font-size:1.15rem;color:var(--heading)}
.cnv-portal__body span{font-size:.86rem;color:var(--slate-soft)}
.cnv-portal .mp-cta{flex:none}

/* The hero is taller on a phone and the strip's own pull is -18px there (line
   13321). THIS BLOCK HAS TO LIVE HERE, BELOW THE RULES IT OVERRIDES, rather
   than beside that one: the two selectors would tie on specificity and source
   order would decide, and that block comes 1,900 lines earlier in this file.
   Splitting a stylesheet does not change its cascade, and neither does a media
   query -- the winner is still the last equal-weight rule that matched. */
@media (max-width:600px){
  .cnv-portal{margin-top:-18px}
  .cnv-portal + .mp-glance{margin-top:18px}
}

/* ITEM 42 SLICE 6 -- THE COUNT ON THE PORTAL BAND, filled by the poll and
   hidden until it answers (see /Members/Index.cshtml, where slice 3's reason
   for having no count is what slice 6 answers rather than reverses).

   INLINE-BLOCK AND NOT A FLEX ITEM, because it sits INSIDE the band's <b> and
   has to sit on the same line as the word Messages -- a badge that wrapped
   would push the band's body two lines taller on a phone the moment anything
   was waiting, which is exactly when a reader is looking at it.

   AND `display` HERE IS SAFE ONLY BECAUSE SOMETHING ELSE SAYS SO, which was
   measured rather than assumed and is worth the sentence. An AUTHOR `display`
   beats the UA stylesheet's [hidden]{display:none} outright -- specificity does
   not come into it, the ORIGIN does -- and THIS file has no [hidden] reset, so
   the reasoning that got as far as writing one here was sound. It is Bootstrap's
   reboot that saves it: `[hidden]{display:none!important}`, loaded on every page
   in layer(vendor), and an important declaration beats a normal one whatever the
   layer order. The guard written here was deleted after a mutant proved it inert
   -- see the note at the top of this file, which already records this exact pair
   for the media picker and the photo editor. */
/* ITEM 46 SLICE 2 -- THE MASTHEAD JOINS THE SELECTOR LIST, AND THE INK WAS
   WRONG BEFORE IT DID.

   ONE LIST BECAUSE THEY ARE ONE CONTROL IN TWO PLACES, which is the test this
   project applies and which .cnv-portal / .hlp-portal FAILED -- those two look
   alike and are a nudge and a conversation, so they stayed apart. These two are
   both [data-cnv-total]: the same number, composed by the same
   ConversationPollRules.UnreadAloud, written by the same paintTotal, hidden by
   the same rule at zero. A second block would be one place for the pill to stop
   being a pill.

   AND color:#fff WAS 2.42:1 ON --brass, WHICH IS THE ONE THING THIS FAMILY MUST
   NOT BE. #C7A24A has a relative luminance of 0.384, so white over it measures
   2.42:1 -- under the 4.5:1 floor for a 0.72rem numeral by a wide margin, on the
   DEFAULT palette, and #fff is not a token so no theme could rescue it. The note
   at the top of this file already says it: brass "as a FILL is fine, but only
   with var(--on-brass), which is derived". --on-brass is one of the four tokens
   ThemeStyleComposer computes and lifts to a 4.5:1 floor, so it is right on all
   eleven palettes rather than on none. It measures 7.81:1 here, and 6.83:1 in
   the masthead, where .nav-links a's opacity:.88 composites the whole pill
   against --ink-deep and a white numeral would have come out at 2.33:1.

   Found while adding the second badge rather than by a sweep, because the sweep
   cannot see an element that is `hidden` on a portal with nothing waiting --
   which is every portal the crawler has ever loaded. */
.cnv-portal__badge,
.cnv-nav__badge{
  display:inline-block;
  margin-left:.45rem;
  padding:.05rem .45rem;
  border-radius:999px;
  background:var(--brass);
  color:var(--on-brass);
  font-family:var(--sans);
  font-weight:700;
  font-size:.72rem;
  line-height:1.5;
  vertical-align:.1em;
}

/* WHAT THE MASTHEAD'S COPY NEEDS THAT THE BAND'S DOES NOT. `.nav-links a` is
   display:inline-flex, so the badge is a flex ITEM there rather than an inline
   box: vertical-align does nothing, and without flex:none a long count would be
   allowed to shrink below its own padding inside a nav that wraps. And the 1px
   pull-up is against the link's own 2px transparent bottom border, which is what
   the hover rule paints: align-items:center centres the pill in the CONTENT box,
   and a border below it puts the box's visual middle 1px higher than that. */
.cnv-nav__badge{
  flex:none;
  margin-left:.4rem;
  transform:translateY(-1px);
}

@media (max-width:1080px){
  .hlp-art{grid-template-columns:1fr;gap:24px}
  /* position:sticky on a box that is now BELOW the prose pins it to the viewport as the reader scrolls
     past the end of the article, which reads as a panel that has come loose. */
  .hlp-side{position:static}
}
@media (max-width:700px){
  /* minmax(0,1fr) here too. The rule is stated 130 lines above and these two lines were breaking it: a
     bare `1fr` is minmax(auto,1fr), so a single long token in a card or a control name cannot shrink the
     track and pushes the grid past the phone. Found by HW Phase 2 rather than by a screenshot, because
     the fourteen-article contents page is the first thing to put a long address on one of these cards. */
  .hlp-index{grid-template-columns:minmax(0,1fr)}
  /* The control and its answer stack. Two columns at 360px hyphenates "Print my Masonic record" down
     the middle of a 5rem track. The dt keeps its rule off so the pair reads as one row, not two. */
  .hlp-does{grid-template-columns:minmax(0,1fr)}
  .hlp-does dt{padding:13px 0 2px;border-bottom:0}
  .hlp-does dd{padding-top:4px}
  /* Both boxes full width and BOTH aligned left. A right-aligned "next" under a left-aligned
     "previous" in a single column reads as two unrelated cards. */
  .hlp-nav a{min-width:0;width:100%}
  .hlp-nav a.next{text-align:left;margin-left:0}
  .hlp-portal .mp-cta{width:100%;justify-content:center}
  /* ITEM 46 SLICE 1. The Messages band gets what the help band has had since HW Phase 3, and it needs it
     more: it is the page's lead now, and a 161px button left-aligned under three lines of copy at 390 is
     the weakest thing on the screen it is supposed to be the way out of. Measured at 390 before the
     change: .cnv-portal .mp-cta was 161x45 in a 358px band. */
  .cnv-portal .mp-cta{width:100%;justify-content:center}
}

/* =========================================================================================================
   DG, THE WIREFRAME VOCABULARY (.wf-*). Backlog item 8, docs/designer-guide-plan.md §2.1, gate Q1(a),
   approved mockup docs/mockups/designer-guide-gate.html plate A.

   ONE FAMILY, TWO GUIDES, AND THAT IS THE WHOLE REASON ITEM 8 IS ONE ITEM. The members' help illustrates
   SCREENS, the booking form, the register, the journey, which cannot be rendered for real without
   inventing members and evenings for them. The designer's guide illustrates a card's PLACE, the layout
   modes, the four stage measures, the four arrangements, the column span, and plan §1 M4 measured why
   those cannot be rendered either: sixteen of the layout rules are descendants of `.home-widget-grid` and
   full-bleed needs a container-query ancestor, so a "live" stage measure inside an article would paint
   something that is not what the board does. Both halves need the same drawing kit, which is what stopped
   this being two mechanisms.

   WHAT IT IS NOT. It is not pixel-real and it must never become so. A figure here says WHERE a thing sits
   and WHICH control the sentence beside it is talking about; the moment it starts claiming to be a
   screenshot it acquires the one property this project has a standing rule against, which is rotting
   silently ([[widget-parity-pixels-are-not-bytes]]). Values are bars, not text, for exactly that reason, 
   a `.wf-line` cannot go out of date because it never said anything.

   IT LIVES IN site.css RATHER THAN A GUIDE-ONLY STYLESHEET (Q1). The source-scanning colour guards, 
   StylesheetPairingTests and the block-part ink scans, only read this file. A family they cannot see is a
   family that is fine on Heritage and fails on College six months from now, which is the shape of every
   contrast defect this project has fixed.

   THE ONE THING TO BE CAREFUL WITH IS THE TARGET RING. `.wf-tgt` is --brass-ink and never --brass. The
   figures land on --surface and --parchment-2, both light, and --brass is a DARK hue on College, Regalia
   and Ashlar, 1.75:1 there, perfect on Heritage. That is the defect `HelpLink.OnLightSurface` was written
   for, and the reason this family has no dark variant is that it is never drawn on an ink band: an article
   is a prose column, and both guides' prose columns are light on all eleven palettes.
   ========================================================================================================= */

/* The figure. A schematic panel, not a card, it sits INSIDE prose and must read as an aside rather than
   competing with the `.hlp-set` boxes around it. --parchment-2 is the one ground that is a step off the
   article's own on every palette without being a second surface colour. */
.wf{
  display:flex;flex-direction:column;gap:9px;
  background:var(--parchment-2);border:1px solid var(--line-light);border-radius:12px;
  padding:14px;margin:18px 0 6px;
}
/* The caption, and it carries meaning rather than a title: what the figure is OF, in the article's own
   words, so a reader who skips the picture has not skipped a sentence. */
.wf__cap{margin:2px 0 0;font-size:.79rem;font-style:italic;color:var(--slate-soft);text-align:center}

/* A screen's own chrome strip, its heading and whatever sits at the right-hand end of it. */
.wf-bar{
  display:flex;align-items:center;gap:9px;flex-wrap:wrap;
  background:var(--surface);border:1px solid var(--line-light);border-radius:8px;padding:8px 12px;
}
.wf-bar__t{font-size:.76rem;font-weight:700;letter-spacing:.02em;color:var(--heading)}
.wf-bar__sp{flex:1 1 auto;min-width:0}

/* A row of the screen, and a split inside one. `minmax(0,…)` for the reason the help index carries it:
   a bare 1fr is minmax(auto,1fr), so one long control name pushes the figure past a phone. */
.wf-row{display:flex;align-items:stretch;gap:9px;flex-wrap:wrap}
.wf-cols{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:9px}

/* A labelled control. The LABEL is real text, it is the string the prose names, and the value is a bar,
   because the value is never what the sentence is about. */
.wf-field{
  flex:1 1 130px;min-width:0;display:flex;flex-direction:column;gap:6px;
  background:var(--surface);border:1px solid var(--line-light);border-radius:7px;padding:8px 10px;
}
.wf-field__l{
  font-size:.63rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--slate-soft);
}
.wf-field__v{height:7px;width:72%;border-radius:4px;background:var(--line-light)}

/* A button, in the two weights the product actually has: the one that does the thing, and the rest.
   --on-brass on --brass is the pair the theme composer already derives and floors, so the primary control
   is legible on all eleven without this family choosing a text colour of its own. */
.wf-ctl{
  display:inline-flex;align-items:center;gap:6px;
  font-size:.72rem;font-weight:600;white-space:nowrap;
  background:var(--surface);border:1px solid var(--line-light);border-radius:999px;
  padding:5px 12px;color:var(--slate);
}
.wf-ctl--go{background:var(--brass);border-color:var(--brass);color:var(--on-brass)}

/* A block of the screen, a column beside it, and a run of text. Between them these three draw every
   screen either guide describes. The rail is DASHED so a reader can tell a region of the page from a
   thing on it without a second colour doing the work. */
.wf-tile{
  flex:1 1 130px;min-width:0;display:flex;flex-direction:column;gap:7px;
  background:var(--surface);border:1px solid var(--line-light);border-radius:8px;padding:10px 11px;
}
.wf-tile__t{
  font-size:.64rem;font-weight:700;letter-spacing:.09em;text-transform:uppercase;color:var(--slate);
}
.wf-rail{
  flex:1 1 130px;min-width:0;display:flex;flex-direction:column;gap:8px;
  border:1px dashed var(--line-light);border-radius:8px;padding:10px 11px;
}
.wf-line{height:7px;border-radius:4px;background:var(--line-light);width:100%}
.wf-line--40{width:40%}
.wf-line--60{width:60%}
.wf-line--80{width:80%}
.wf-line--95{width:95%}
.wf-note{font-size:.68rem;letter-spacing:.03em;color:var(--slate-soft)}

/* A sequence, and it is drawn ONLY where the content is genuinely one, the booking's two steps, the
   order the stage resolves in. A numbered marker over a list that is not a sequence encodes nothing and
   is the commonest way a diagram starts lying. */
.wf-steps{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.wf-step{
  display:flex;align-items:center;gap:7px;
  background:var(--surface);border:1px solid var(--line-light);border-radius:8px;
  padding:7px 11px;font-size:.76rem;color:var(--slate);
}
.wf-step__n{
  flex:none;width:18px;height:18px;border-radius:50%;
  background:var(--brass-ink);color:var(--surface);
  font-size:.62rem;font-weight:700;display:grid;place-items:center;
}
.wf-steps__arw{color:var(--slate-soft);font-size:.85rem;line-height:1}

/* THE TARGET, what the sentence beside the figure is pointing at, and the one class that makes a picture
   an argument rather than decoration. --brass-ink, NEVER --brass: see the family note above.

   `outline` rather than `border` on purpose, so marking a control does not change its size and shift
   everything beside it; `position:relative` so the numbered mark can hang off the corner without the
   figure needing a containing block of its own. */
.wf-tgt{
  position:relative;
  outline:2px solid var(--brass-ink);outline-offset:2px;border-radius:8px;
}
.wf-tgt::after{
  content:attr(data-mark);
  position:absolute;top:-9px;right:-9px;width:19px;height:19px;border-radius:50%;
  background:var(--brass-ink);color:var(--surface);
  font-size:.62rem;font-weight:700;display:grid;place-items:center;
}

/* The legend. The numbers are the ARTICLE'S OWN sentences, condensed, which is what stops the figure
   being a picture beside prose and makes it a picture the prose is written against. */
.wf-key{
  display:flex;flex-wrap:wrap;gap:10px 20px;
  margin-top:4px;padding-top:11px;border-top:1px solid var(--line-light);
}
.wf-key__i{display:flex;align-items:flex-start;gap:8px;flex:1 1 240px;min-width:0;font-size:.79rem;color:var(--slate)}
.wf-key__n{
  flex:none;width:18px;height:18px;border-radius:50%;margin-top:2px;
  background:var(--brass-ink);color:var(--surface);
  font-size:.62rem;font-weight:700;display:grid;place-items:center;
}

@media (max-width:700px){
  /* The figure is the one thing in an article that can carry a long control name AND a fixed minimum
     track, so it gets the same minmax(0,1fr) treatment the contents grid needed. One column below the
     phone, because two 120px tracks plus a gap is wider than a 320px screen once the article's own
     padding is taken off. */
  .wf-cols{grid-template-columns:minmax(0,1fr)}
  .wf-field,.wf-tile,.wf-rail{flex-basis:100%}
  .wf-key__i{flex-basis:100%}
}

/* =========================================================================================================
   DG, THE SPECIMEN (.gd-*). Backlog item 8, docs/designer-guide-plan.md §2.4, gate Q3(a), approved mockup
   plate D1.

   THIS IS THE WHOLE OF THE DESIGNER'S GUIDE'S OWN CSS, and the smallness is the decision. The guide's page
   shell, the prose column, the sticky contents rail, the contents cards, prev/next, reuses the `.hlp-*`
   family unchanged. That family is not "the members' help": it is A WIKI ARTICLE'S CHROME, and copying it
   under a second prefix would have been a second copy of eleven rules free to drift from the first, which
   is the defect this project has now paid for in three separate places. The `.hlp-` block above says so.

   WHAT IS GENUINELY NEW IS THE SPECIMEN. A specimen is a REAL component, an actual
   `.home-widget--shell-heritage`, painted by the product's own rules, sitting in an article beside the
   prose that describes it, which is the one property that makes a designer's guide trustworthy: it cannot
   drift from the product, because it IS the product. Same argument that made the widget designer be the
   board it designs ([[designer-must-be-the-board-it-designs]]).

   THE FRAME AROUND IT EXISTS TO SAY "THIS IS LIVE". Without it a reader cannot tell a rendered shell from a
   drawn one, and the distinction is the whole of §1 M4: a card's LOOK is real here and a card's PLACE is a
   `.wf-` diagram. The `live` flag and the class name in the header are that distinction, on the page.
   ========================================================================================================= */

.gd-spec{
  margin:18px 0 6px;
  border:1px solid var(--line-light);border-radius:12px;overflow:hidden;
  background:var(--parchment-2);
}
.gd-spec__hd{
  display:flex;align-items:center;gap:10px;flex-wrap:wrap;
  padding:8px 14px;border-bottom:1px solid var(--line-light);background:var(--surface);
}
/* --brass-ink and not --brass, for the reason the whole of the .wf- family carries: this sits on
   --surface, and --brass is a dark hue on College, Regalia and Ashlar. */
.gd-spec__k{
  font-size:.62rem;font-weight:700;letter-spacing:.13em;text-transform:uppercase;color:var(--brass-ink);
}
.gd-spec__n{
  margin-left:auto;font-size:.7rem;color:var(--slate-soft);
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  overflow-wrap:anywhere;
}
.gd-spec__bd{padding:16px}

/* THE SPECIMEN'S OWN BOARD, AND IT IS §1 M5 AS A RULE RATHER THAN A NOTE.

   `HomeBoardStyleComposer` writes --board-columns, --board-gap, --board-space-above and
   --board-card-min-height onto `:root`, SITE-WIDE, on every page of this application. A
   `.home-widget-grid` rendered inside a guide article therefore inherits THE LODGE'S OWN board: a
   one-card specimen would draw in a three-column grid on one lodge and a two-column grid on another, and
   would look perfectly correct to whoever built it on theirs.

   So every specimen restates the four, locally. Redeclaring a custom property on an ancestor is the only
   way to beat an inherited one, there is no specificity contest to win, because :root and this element
   are not competing for the same box. */
.gd-spec__board{
  --board-columns:1;
  --board-gap:0px;
  --board-space-above:0px;
  --board-card-min-height:0px;
}

/* A specimen that is only meaningful at one size says so rather than lying at the others. Used by the
   frame article, where an 8px hairline and a 24px chequer are the point. */
.gd-spec--pair .gd-spec__bd{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:14px;
}

/* The contents card's ordinal. The guide's articles ARE a sequence, an author works through them in
   order, which is §8.4's finding and the reason the list is not alphabetical, so a number here encodes
   something true rather than decorating a list. */
.gd-ord{
  flex:none;width:22px;height:22px;border-radius:50%;
  background:var(--brass-ink);color:var(--surface);
  font-size:.68rem;font-weight:700;display:grid;place-items:center;
}

/* =========================================================================================================
   SCC PHASE 7c — THE BLOCK LAYOUT PANEL, THE HIDE EYE, THE REGROUPED SETTINGS COLUMN AND ITS RAIL

   Every colour here is a theme token on a theme ground, so the contrast crawler can measure it: --slate and
   --slate-soft on --surface, --brass for a selection, and never --brass as TEXT on a light ground (1.75:1 on
   College). The two segmented controls in the panel are NOT declared here — they join the existing
   .btn-border-side / .btn-separator-choice family further up this file, because they are the same affordance
   and a second look would be a second thing to keep in step.
   ========================================================================================================= */

.widget-layout-control{
  display:grid;
  gap:.4rem;
}

.widget-layout-control__row{
  display:flex;
  align-items:center;
  gap:.5rem;
  flex-wrap:wrap;
  padding:.3rem .4rem;
  border:1px solid rgba(148,163,184,.24);
  border-radius:8px;
  background:var(--surface);
}

.widget-layout-control__label{
  font-size:.68rem;
  font-weight:700;
  color:var(--slate);
  letter-spacing:.06em;
  text-transform:uppercase;
  min-width:5.4rem;
}

.widget-layout-seg{
  display:flex;
  flex-wrap:wrap;
  gap:.22rem;
}

.widget-layout-control__stepper{
  display:inline-flex;
  align-items:center;
  gap:.3rem;
}

/* The number boxes are the rich block's line-spacing box, restated as its own class rather than borrowed:
   that one names a spacing between LINES and these name three different things. Same width, on purpose. */
.widget-layout-control__num{
  width:4rem;
  padding:.2rem .35rem;
  border:1px solid rgba(100,116,139,.35);
  border-radius:5px;
  background:var(--surface);
  color:var(--slate);
  font-size:.75rem;
}

.widget-layout-control__unit{
  font-size:.68rem;
  color:var(--slate-soft);
}

.widget-layout-control__colors{
  display:flex;
  flex-wrap:wrap;
  gap:.25rem;
  align-items:center;
}

.widget-layout-fill-swatch{
  width:1.15rem;
  height:1.15rem;
  padding:0;
  border:1px solid rgba(100,116,139,.4);
  border-radius:5px;
  background:var(--surface);
  color:var(--slate-soft);
  font-size:.6rem;
  line-height:1;
  cursor:pointer;
}

.widget-layout-fill-swatch.is-active{
  border-color:var(--brass);
  box-shadow:0 0 0 2px color-mix(in srgb, var(--brass) 30%, transparent);
}

/* "No fill" is a swatch with no colour to show, so it says so with a glyph instead of painting one. */
.widget-layout-fill-swatch--none{
  background:var(--parchment-2);
}

.widget-layout-control__hint{
  margin:0;
  font-size:.68rem;
  line-height:1.4;
  color:var(--slate-soft);
}

/* ---------------------------------------------------------------------------------------------------------
   SF PHASE 2, GATE Q8 — THE SIZE ROW SAYS WHAT WIDTH IT IS TRUE AT, AND THE READING SAYS WHAT IT BECOMES
   (docs/sample-front-end-plan.md §10.2 Q8, §10.5, build record §12, mockup Plate G).

   NOTHING HERE IS A NEW COMPONENT. The reading is a `.widget-layout-control__hint` with two spans in it, the
   same way BT Phase 4's colour reading is two hints — so it inherits the panel's size, colour and line
   height, and these four rules only say where the parts sit.

   EVERY SELECTOR IS A CLASS AND NONE OF THEM IS A DESCENDANT SELECTOR, which is not fussiness: drawing this
   exact control's mockup produced the defect first hand (plan §10.4). `.reading span{display:block}` also
   matched an unrelated chip inside the same box — 0,1,1 against an element that stated no `display` of its
   own — and a pill rendered as a full-width bordered box on its own line. The three `<b>` figures are left
   bold by the user agent rather than given a rule, so there is no element selector in this block at all.
   --------------------------------------------------------------------------------------------------------- */

/* "at 1440px", under the word Size. The label above it is uppercase and tracked-out; this is neither,
   because it is a fact about the control rather than a second heading. */
.widget-layout-control__label-note{
  display:block;
  margin-top:.1rem;
  font-size:.58rem;
  font-weight:600;
  letter-spacing:.04em;
  text-transform:none;
  color:var(--slate-soft);
}

.widget-layout-control__reading{
  display:flex;
  flex-wrap:wrap;
  align-items:baseline;
  gap:.1rem .5rem;
  padding:0 .4rem;
}

.widget-layout-control__reading-figures{
  color:var(--slate);
}

/* Its own line under the figures, and `flex-basis` is the honest way to say that inside a wrapping row: the
   caption names the three numbers above it in order, so it must never sit beside them. */
.widget-layout-control__reading-note{
  flex-basis:100%;
  font-size:.58rem;
  letter-spacing:.06em;
  text-transform:uppercase;
  color:var(--slate-soft);
}

/* ---------------------------------------------------------------------------------------------------------
   BT PHASE 4 — THE COLOUR READING ON THE BLOCK TEXT PANEL (docs/block-typography-plan.md §14, Plate D)

   THE THREE COLOURS HERE ARE THE STATUS PAIRS AND NOT THE PALETTE'S, on purpose. A verdict about contrast
   printed in a themed accent would be a sentence whose own legibility follows the palette — the exact defect
   the status pairs were added for (TC, 2026-08-03: a "Paid" chip measured 2.04:1 in dark green on a dark
   card). --ok-ink and --bad-ink are fixed, opaque and measured at 7.37:1 and 9.08:1 on light, and every row
   in this panel paints --surface.
   --------------------------------------------------------------------------------------------------------- */

/* A swatch that would put this part below the bar on the ground this card paints. HATCHED, NOT DISABLED:
   the panel cannot see a background picture, an unseen palette or a shell the author is about to change, so
   it marks and states rather than refuses (Plate D's third bullet). */
.widget-layout-fill-swatch--fails{
  position:relative;
}

/* THE RING IS PAINTED OUTSIDE THE SWATCH AND MUST NOT BE CLICKABLE. The swatches sit .25rem apart and this
   reaches 3px into that gap from both sides, so without pointer-events:none a hatched swatch would swallow
   presses aimed at its neighbour — a control made unreachable by the mark that was only meant to warn about
   it, which is this backlog's own §10 and §11 in one line. */
.widget-layout-fill-swatch--fails::after{
  content:"";
  position:absolute;
  inset:-3px;
  border-radius:7px;
  border:1px solid var(--bad-line);
  background:repeating-linear-gradient(135deg,
    transparent 0 3px,
    color-mix(in srgb, var(--bad-ink) 34%, transparent) 3px 4px);
  pointer-events:none;
}

.widget-layout-control__hint--pass{
  color:var(--ok-ink);
  font-weight:600;
}

.widget-layout-control__hint--fails{
  color:var(--bad-ink);
  font-weight:600;
}

/* The hide eye. Same 1.7rem circle as the collapse and remove buttons beside it — one affordance family in
   that header, so this joins their size and border rather than inventing a third. */
.widget-control-block__hide{
  width:1.7rem;
  height:1.7rem;
  border:1px solid rgba(148,163,184,.45);
  border-radius:999px;
  background:var(--surface);
  color:var(--slate-soft);
  cursor:pointer;
  line-height:1;
  font-size:.82rem;
  padding:0;
  transition:border-color .15s ease, background .15s ease;
}

.widget-control-block__hide:hover{
  border-color:var(--brass);
  background:color-mix(in srgb, var(--brass) 10%, var(--surface));
}

/* The pressed state paints --claret text, so its ground is tinted from a GROUND token and never from claret
   itself. Written that way the first time and caught by NoAccentTintsTheGroundBeneathItself the first time
   too: --claret is lifted to a 4.5:1 floor and then stopped, so where the floor binds it has no headroom, and
   washing the ground with 12% of the same token spends headroom that is not there. --parchment-2 is a ground
   the floor already covers on all eleven palettes. The border keeps --claret, which is not text. */
.widget-control-block__hide[aria-pressed="true"]{
  border-color:var(--claret);
  background:var(--parchment-2);
  color:var(--claret);
}

/* A hidden block is still edited, still dragged and still saved — it just does not render. So it is DIMMED
   and its title struck through rather than removed from the stack: the author has to be able to find the
   thing they hid, which is the whole difference between hiding and deleting. */
.widget-control-block.is-hidden .widget-control-block__title{
  opacity:.55;
  text-decoration:line-through;
}

.widget-control-block.is-hidden .widget-control-block__body{
  opacity:.6;
}

/* ---------------------------------------------------------------------------------------------------------
   The card's alignment default — four segments where the vocabulary has three, the first being "no default".
   Native radios wrapped in their labels, the same shape as 7a's section-style chips and 7b's board segments:
   arrow keys, focus and posting are the browser's job, and `:checked +` paints the selection so no class
   drifts.
   --------------------------------------------------------------------------------------------------------- */
.widget-alignseg{
  display:flex;
  border:1px solid var(--line-light);
  border-radius:8px;
  overflow:hidden;
  width:100%;
}

/* BACKLOG §1: the segment contains its own hidden radio. See the note on `.widget-shellchip` — without this
   the absolute input below escapes to `.widget-edit-dialog__inner` and the dialog scrolls its head away. */
.widget-alignseg__opt{
  position:relative;
  margin:0;
  flex:1;
}

/* 1px rather than 0, and never display:none, because a zero-sized or hidden radio is one some browsers
   refuse to focus. */
.widget-alignseg__opt input{
  position:absolute;
  width:1px;
  height:1px;
  opacity:0;
  pointer-events:none;
}

.widget-alignseg__opt span{
  display:block;
  padding:.32rem .2rem;
  text-align:center;
  background:var(--surface);
  color:var(--heading);
  font-size:.8rem;
  cursor:pointer;
  border-left:1px solid var(--line-light);
}

.widget-alignseg__opt:first-child span{
  border-left:0;
}

.widget-alignseg__opt input:checked + span{
  background:var(--brass);
  color:var(--on-brass);
  font-weight:600;
}

.widget-alignseg__opt input:focus-visible + span{
  outline:2px solid var(--brass-ink);
  outline-offset:-2px;
}

/* SCC Phase 7d: the chequer's own settings, which appear under the Frame segments only when the chequer is
   the chosen one. Not a panel card of its own and not three more settings rows: they qualify ONE choice and
   are meaningless without it, so they are indented under it and go away with it. */
.widget-framechequer{
  display:flex;
  flex-direction:column;
  gap:.5rem;
  margin-top:.55rem;
  padding-left:.6rem;
  border-left:2px solid var(--line-light);
}

.widget-framechequer__row{
  display:flex;
  flex-direction:column;
  gap:.3rem;
}

.widget-framechequer__label{
  font-size:.72rem;
  font-weight:600;
  letter-spacing:.04em;
  color:var(--slate-soft);
}

/* The absence chip carries no background of its own, so it has to READ as the absence rather than as a
   white swatch somebody might mistake for parchment. A dashed ring is what the custom-swatch chip already
   uses to mean "not one of the theme's colours". */
.widget-framechequer .widget-rich__swatch[data-widget-frame-color=""]{
  background:transparent;
  border-style:dashed;
}

/* ---------------------------------------------------------------------------------------------------------
   IB PHASE 3: THE BACKGROUND CARD (mockup docs/mockups/image-background.html, Plate E).

   Five rows: the chosen picture, the fit, the focal point, the treatment chips and the scrim. Every colour
   here is a token, and the two that carry text on a tinted ground take --heading and --slate-soft, which the
   contrast crawler already covers on all eleven palettes.
   --------------------------------------------------------------------------------------------------------- */

/* The chosen picture: thumbnail, name, shape, and the two buttons that change or clear it.

   IT IS A GRID WITH A `minmax(0, 1fr)` IN THE MIDDLE, AND THAT TRACK IS THE WHOLE POINT.

   `.widget-settings-panel__rows` is a grid whose implicit track takes its widest row's min-content as an
   automatic minimum — BACKLOG §1b's mechanism — so ONE row that cannot fit sets the width of every row in
   its card, and the card is `overflow:hidden`, so the excess is CLIPPED rather than scrolled. This box is
   that row: three things across (a 64px thumbnail, a file name, two stacked buttons) inside a 243px card.
   Measured: the Background card's five rows all came out 272 while every other card's sat at 243, and the
   29px took "Change", "Remove", the fit's "Tile" segment and the scrim scale's "100%" with it.

   TWO CHEAPER-LOOKING FIXES WERE TRIED AND MEASURED FIRST, and both are recorded because they look right.
   `flex-wrap:wrap` (§1b's own answer) fixes the SHORT-name case and fails the long one: the buttons wrap to
   a second line, the name then has the whole line to itself, and a 66-character file name put the row at
   447px. `min-width:0` on the name does not help either — the name is a block with `white-space:nowrap`, so
   its min-content is the whole string, and a min-width only sets a floor.

   A grid states the answer instead of hoping for it: the thumbnail and the buttons take exactly what they
   need, the name takes the rest, and the row's min-content becomes the sum of the two fixed ends (~146px) —
   so no file name can widen this card again. THE GRID IS THE LOAD-BEARING HALF, measured: swapping it back
   for `display:flex` fails the long-name test immediately.

   `minmax(0, 1fr)` rather than `auto` for the middle track is a DEFENSIVE choice and not a load-bearing one,
   and a surviving mutation is how that is known — `auto auto auto` passes today, because the name clips
   itself and a clipped box contributes nothing to its track's minimum. It is kept because that makes the
   row's shape depend on the name's own overflow, and `minmax(0, 1fr)` says "this track may shrink to
   nothing" in the row's own terms instead. Same fact [[settings-column-scrolls-sideways]] records from the
   other side: a track's automatic minimum is what overflows a column, and zero is the only one that cannot. */
.widget-bgpick{
  display:grid;
  grid-template-columns:auto minmax(0, 1fr);
  align-items:center;
  gap:.6rem;
  padding:.45rem;
  border:1px solid var(--line-light);
  border-radius:7px;
  background:var(--parchment-2);
}

.widget-bgpick[hidden]{
  display:none;
}

.widget-bgpick__thumb{
  flex:none;
  width:64px;
  height:44px;
  border-radius:4px;
  overflow:hidden;
  border:1px solid var(--line-light);
  background:var(--surface);
}

.widget-bgpick__thumb img{
  display:block;
  width:100%;
  height:100%;
  object-fit:cover;
}

/* `min-width:0` says out loud that this box may be squeezed to nothing, which the `minmax(0, 1fr)` track
   above already guarantees. There was an `overflow:hidden` here too for one build, and it was REMOVED after a
   mutation showed that it and the clip on the name below each hid the other's absence — two guards behind one
   assertion, and the one that has to stay is the one on the element the ellipsis is on. */
.widget-bgpick__meta{
  min-width:0;
  line-height:1.35;
}

.widget-bgpick__meta b{
  display:block;
  font-size:.78rem;
  font-weight:600;
  color:var(--heading);
  overflow:hidden;
  text-overflow:ellipsis;
  white-space:nowrap;
}

.widget-bgpick__meta span{
  font-size:.68rem;
  color:var(--slate-soft);
}

/* THE TWO BUTTONS TAKE A ROW OF THEIR OWN, and that is a legibility decision made from a screenshot. Beside
   the name they are 62px of a 243px card, which left the file name about 85px and rendered
   `lodge-seal.png` as `lodge-s…` — a name that tells an author nothing is not worth the row it saves. On
   their own line the name gets the whole remaining width and the box grows by one button's height.

   `grid-column:1 / -1` and `justify-content:flex-end` rather than an auto margin, which is what
   [[auto-margins-defeat-align-items-stretch]] is about. */
.widget-bgpick__actions{
  grid-column:1 / -1;
  display:flex;
  justify-content:flex-end;
  gap:.3rem;
}

.widget-bgpick__btn{
  padding:.2rem .5rem;
  font-size:.7rem;
}

.widget-bgpick__choose{
  align-self:start;
  font-size:.78rem;
  padding:.34rem .7rem;
}

/* THE FOCAL PAD. The frame is shrink-to-fit so its box is the picture's box — see the note in the view for
   why that is load-bearing rather than tidy. */
.widget-bgfocus{
  display:flex;
  justify-content:center;
}

.widget-bgfocus__frame{
  position:relative;
  display:inline-block;
  line-height:0;
  border:1px solid var(--line-light);
  border-radius:6px;
  overflow:hidden;
  cursor:crosshair;
  max-width:100%;
}

.widget-bgfocus__frame:focus-visible{
  outline:2px solid var(--brass-ink);
  outline-offset:2px;
}

.widget-bgfocus__img{
  display:block;
  width:auto;
  height:auto;
  max-width:100%;
  max-height:170px;
}

/* White at 42% over the picture, with a dark shadow under it, because a crosshair on a photograph has no
   ground to be measured against and has to read on a white sky and a black doorway alike. */
.widget-bgfocus__cross{
  position:absolute;
  background:rgba(255,255,255,.42);
  box-shadow:0 0 1px rgba(0,0,0,.6);
  pointer-events:none;
}

.widget-bgfocus__cross--h{
  left:0;
  right:0;
  height:1px;
}

.widget-bgfocus__cross--v{
  top:0;
  bottom:0;
  width:1px;
}

.widget-bgfocus__pin{
  position:absolute;
  width:18px;
  height:18px;
  margin:-9px 0 0 -9px;
  border:2px solid #FFFFFF;
  border-radius:50%;
  box-shadow:0 0 0 1px rgba(0,0,0,.55), inset 0 0 0 1px rgba(0,0,0,.4);
  pointer-events:none;
}

.widget-bgfocus__readout{
  font-size:.68rem;
  color:var(--slate-soft);
}

/* THE TREATMENT CHIPS. Each swatch wears its own look through data-image-filter, which is the same five
   rules the card's layer and the photo viewer use. */
.widget-bgchips{
  display:flex;
  flex-wrap:wrap;
  gap:.3rem;
}

/* BACKLOG §1 AGAIN: the chip contains its own hidden radio, so it MUST establish a containing block. Without
   this the absolutely-positioned input escapes to .widget-edit-dialog__inner and scrolls the dialog's head
   away — the defect the section-style chips and the alignment segments each carry this line for. */
.widget-bgchip{
  position:relative;
  display:flex;
  align-items:center;
  gap:.3rem;
  margin:0;
  padding:.2rem .45rem .2rem .25rem;
  border:1px solid var(--line-light);
  border-radius:999px;
  background:var(--surface);
  cursor:pointer;
}

.widget-bgchip input{
  position:absolute;
  width:1px;
  height:1px;
  opacity:0;
  pointer-events:none;
}

.widget-bgchip__swatch{
  width:20px;
  height:20px;
  border-radius:50%;
  border:1px solid var(--line-light);
  background-color:var(--parchment-2);
  background-size:cover;
  background-position:center;
}

.widget-bgchip__name{
  font-size:.7rem;
  color:var(--slate-soft);
}

.widget-bgchip:has(input:checked){
  border-color:var(--brass);
  background:var(--parchment-2);
}

.widget-bgchip:has(input:checked) .widget-bgchip__name{
  color:var(--heading);
  font-weight:600;
}

.widget-bgchip:has(input:focus-visible){
  outline:2px solid var(--brass-ink);
  outline-offset:1px;
}

/* SF PHASE 6 STEP 4 - THE DRAWING CHIPS (plan section 23, mockup Plate E). Wider than the treatment chips
   and stacked rather than inline, because what they are showing is a SHAPE and not a tint: a 20px round
   swatch can show "sepia" and cannot show an arch.

   THE SWATCH SHOWS THE REAL DRAWING AT A SCALE, WHICH IS WHY THERE ARE TWO ELEMENTS. Every drawing in
   `CardArt` is sized for a card - a 220px circle, 96px chequer squares, a 168px arch - and a chip is 68px
   wide, so the inner box is drawn at card size and TRANSFORMED down. Reusing `.home-widget__bg--art*`
   verbatim is the point: a chip that painted its own small approximation of a pavement would be a second
   statement of the drawing, free to stop resembling the card.

   AND THE GROUND IS `--widget-edit-shell-bg`, THE SAME TABLE THE BLOCK EDITORS AND THE SECTION-STYLE CHIPS
   READ. A drawing's entire cost is its colour against the ground it is painted ON (section 19.4), so a
   swatch on a neutral ground would be showing an author the one thing that decides nothing. */
.widget-artchips{
  display:flex;
  flex-wrap:wrap;
  gap:.35rem;
}

/* BACKLOG section 1: the chip contains its own hidden radio, so it MUST establish a containing block, or the
   absolutely positioned input escapes to .widget-edit-dialog__inner and scrolls the dialog's head away. */
.widget-artchip{
  position:relative;
  display:block;
  width:74px;
  margin:0;
  border:1px solid var(--line-light);
  border-radius:6px;
  overflow:hidden;
  background:var(--surface);
  cursor:pointer;
}

.widget-artchip input{
  position:absolute;
  width:1px;
  height:1px;
  opacity:0;
  pointer-events:none;
}

.widget-artchip__swatch{
  position:relative;
  display:block;
  height:34px;
  overflow:hidden;
  background:var(--widget-edit-shell-bg, var(--ink));
}

.widget-artchip__art{
  position:absolute;
  top:0;
  left:0;
  width:360px;
  height:170px;
  transform:scale(.2);
  transform-origin:0 0;

  /* THE SWATCH ANSWERS "WHICH SHAPE", NOT "HOW STRONG", AND MEASURING IT IS WHAT DECIDED THIS. At the
     vocabulary's own default of 12% the wash, the arch, the banner and "None" are four indistinguishable
     claret rectangles at 68px - so a row whose whole job is choosing a shape could not be used to choose one.
     The STRENGTH is answered one row below, by a slider with a number and a floor line beside it, which is
     where a number belongs.

     It is still the same five rules the card paints, at a strength picked so all five read - not five
     hand-tuned opacities, which would be five design values with no principle behind them. */
  --card-art-strength:.55;
}

.widget-artchip__name{
  display:block;
  padding:.2rem .15rem;
  font-size:.63rem;
  text-align:center;
  color:var(--slate-soft);
}

.widget-artchip:has(input:checked){
  border-color:var(--brass);
  box-shadow:0 0 0 2px color-mix(in srgb, var(--brass) 26%, transparent);
}

.widget-artchip:has(input:checked) .widget-artchip__name{
  color:var(--heading);
  font-weight:600;
}

.widget-artchip:has(input:focus-visible){
  outline:2px solid var(--brass-ink);
  outline-offset:1px;
}

/* THE SCRIM SLIDER. The range's own track is made transparent so the hatched region below the floor shows
   through it: the bar is not decoration, it is the part of the track the author cannot reach. */
.widget-bgslider{
  position:relative;
  display:flex;
  align-items:center;
  height:26px;
}

.widget-bgslider::before{
  content:"";
  position:absolute;
  left:0;
  right:0;
  top:calc(50% - 4px);
  height:6px;
  border-radius:3px;
  background:var(--parchment-2);
  border:1px solid var(--line-light);
}

.widget-bgslider__barred{
  position:absolute;
  left:0;
  width:0;
  top:calc(50% - 4px);
  height:6px;
  border:1px solid var(--line-light);
  border-right:0;
  border-radius:3px 0 0 3px;
  background:repeating-linear-gradient(135deg, var(--line-light) 0 3px, transparent 3px 6px);
}

.widget-bgslider__range{
  position:relative;
  flex:1 1 auto;
  min-width:0;
  height:26px;
  margin:0;
  background:transparent;
  -webkit-appearance:none;
  appearance:none;
}

.widget-bgslider__range::-webkit-slider-runnable-track{
  height:6px;
  background:transparent;
}

.widget-bgslider__range::-moz-range-track{
  height:6px;
  background:transparent;
}

.widget-bgslider__range::-webkit-slider-thumb{
  -webkit-appearance:none;
  width:16px;
  height:16px;
  margin-top:-5px;
  border-radius:50%;
  background:var(--surface);
  border:2px solid var(--brass);
  box-shadow:0 1px 3px rgba(0,0,0,.25);
}

.widget-bgslider__range::-moz-range-thumb{
  width:16px;
  height:16px;
  border-radius:50%;
  background:var(--surface);
  border:2px solid var(--brass);
  box-shadow:0 1px 3px rgba(0,0,0,.25);
}

.widget-bgslider__range:focus-visible{
  outline:2px solid var(--brass-ink);
  outline-offset:2px;
}

.widget-bgslider__scale{
  display:flex;
  justify-content:space-between;
  font-size:.66rem;
  color:var(--slate-soft);
}

/* The floor SAYS WHY, in the same left-ruled shape the mockup gives it: a number an author cannot go below
   needs a sentence, and a sentence after the save would be too late. */
.widget-bgslider__floorline{
  border-left:2px solid var(--brass);
  padding-left:.5rem;
}

/* The treatment strength keeps the browser's own slider, and the ONE thing it must not keep is the
   browser's own accent: a stock blue thumb in a brass-and-claret dialog is the only control on the page that
   belongs to somebody else's design. `accent-color` recolours the native widget without rebuilding it, which
   is what makes keeping the native one worth doing. */
.widget-bgrange{
  flex:1 1 8rem;
  min-width:0;
  accent-color:var(--brass);
}

.widget-bgslider__readout{
  font-size:.7rem;
  color:var(--slate-soft);
  font-weight:600;
}

/* ---------------------------------------------------------------------------------------------------------
   THE SETTINGS COLUMN COLLAPSES TO A RAIL.

   The dialog body is a grid, so the collapse is a COLUMN WIDTH and nothing moves in the DOM — no element is
   reparented, so nothing loses focus, scroll position or a half-typed field. The inner column is hidden with
   `display:none` rather than a zero width, so a collapsed panel takes nothing out of the tab order either.
   --------------------------------------------------------------------------------------------------------- */
.widget-edit-dialog__settings-head{
  display:flex;
  align-items:center;
  gap:.5rem;
  margin-bottom:.6rem;
}

.widget-edit-dialog__settings-head-title{
  font-size:.72rem;
  font-weight:700;
  letter-spacing:.1em;
  text-transform:uppercase;
  color:var(--slate-soft);
}

.widget-edit-dialog__settings-collapse{
  margin-left:auto;
  width:1.6rem;
  height:1.6rem;
  padding:0;
  border:1px solid rgba(148,163,184,.45);
  border-radius:999px;
  background:var(--surface);
  color:var(--slate-soft);
  font-size:.85rem;
  line-height:1;
  cursor:pointer;
}

.widget-edit-dialog__settings-collapse:hover{
  border-color:var(--brass);
  background:color-mix(in srgb, var(--brass) 10%, var(--surface));
}

.widget-edit-dialog__settings-inner{
  display:flex;
  flex-direction:column;
  gap:.6rem;
  min-width:0;
}

/* The rail is what is left when the column is shut: one vertical word and a chevron. It is absent (not
   merely invisible) while the column is open, so it is never a second tab stop for the same control. */
.widget-edit-dialog__settings-rail{
  display:none;
  width:100%;
  height:100%;
  padding:.7rem .2rem;
  border:1px solid rgba(148,163,184,.35);
  border-radius:12px;
  background:var(--surface);
  color:var(--slate-soft);
  cursor:pointer;
  align-items:center;
  justify-content:flex-start;
  flex-direction:column;
  gap:.6rem;
}

.widget-edit-dialog__settings-rail:hover{
  border-color:var(--brass);
  color:var(--brass-ink);
}

.widget-edit-dialog__settings-rail-icon{
  font-size:.95rem;
  line-height:1;
}

.widget-edit-dialog__settings-rail-label{
  writing-mode:vertical-rl;
  font-size:.7rem;
  font-weight:700;
  letter-spacing:.14em;
  text-transform:uppercase;
}

/* THE COLUMN IS WHAT COLLAPSES, and this rule is the whole of it. Hiding the inner alone left the 296px
   TRACK behind — the blocks column measured 884px open and 884px collapsed, so the rail hid a panel and paid
   nothing for it, which the E2E caught by asserting the blocks column had grown rather than by asserting the
   rail was visible. The track goes to the rail's own width and the space is the editor's. */
.widget-edit-dialog--settings-collapsed .widget-edit-dialog__body{
  grid-template-columns:44px minmax(0,1fr);
}

.widget-edit-dialog--settings-collapsed .widget-edit-dialog__settings{
  padding:.5rem .25rem;
  overflow:visible;
}

.widget-edit-dialog--settings-collapsed .widget-edit-dialog__settings-inner{
  display:none;
}

.widget-edit-dialog--settings-collapsed .widget-edit-dialog__settings-rail{
  display:flex;
}

/* ---------------------------------------------------------------------------------------------------------
   THE BOARD LAYOUT PANEL COLLAPSES, AND STARTS COLLAPSED.

   The head is a <button> now, so it has to be un-styled back to a heading: a button brings its own border,
   background, font and centring, and every one of those would fight the .widgets-zone__head rules above.
   --------------------------------------------------------------------------------------------------------- */
.widgets-zone__head--toggle{
  display:flex;
  align-items:center;
  gap:.75rem;
  width:100%;
  border:0;
  background:transparent;
  padding:0;
  margin:0 0 .75rem;
  text-align:left;
  font:inherit;
  color:inherit;
  cursor:pointer;
  flex-wrap:wrap;
}

.widgets-zone--board.is-collapsed .widgets-zone__head--toggle{
  margin-bottom:0;
}

.widgets-zone__head-text{
  min-width:0;
}

.widgets-zone--board.is-collapsed .board-panel__body{
  display:none;
}

.widgets-zone--board.is-collapsed .widget-panelcard__chevron{
  transform:rotate(-90deg);
}

/* The three numbers on the closed summary, so "is this board still three across?" needs no click. They read
   the same model the rows do, so they cannot drift from them. */
.board-panel__summary{
  display:flex;
  gap:.7rem;
  flex-wrap:wrap;
  justify-content:flex-end;
  margin-left:auto;
  font-size:.78rem;
  color:var(--slate-soft);
  font-variant-numeric:tabular-nums;
}

/* This panel has its own save button and its own form, so an edit left behind a chevron never reaches the
   database. The mark is what makes that impossible to do by accident; site.js also refuses the collapse. */
.board-panel__dirty{
  display:none;
  font-size:.68rem;
  font-weight:700;
  letter-spacing:.08em;
  text-transform:uppercase;
  color:var(--on-brass);
  background:var(--brass);
  border-radius:100px;
  padding:.14rem .55rem;
}

.widgets-zone--board.is-dirty .board-panel__dirty{
  display:inline-block;
}

.widgets-zone--board.is-dirty .board-panel__summary{
  display:none;
}

/* ============================================================================================
   IE — /Admin/ImportExport, THE SCREEN AS A LEDGER (.iex-*).
   docs/import-export-plan.md §15 and §16; approved gate docs/mockups/import-export-redesign.html.
   Q22 (a) the export panel is a strip, Q23 (a) a 232px rail from 1000px up, Q24 (a) claret marks
   consequence on this page and nothing else.

   Every colour here is a theme token. The receipt, the tags and the fence's header are the site's
   ok, warn and bad triple — the one family that has a fill AND a line AND an ink for each state,
   so a fill always takes the ink from its own family. Anything hand-mixed would be a fixed
   surface meeting a theme text token on the palettes whose ground is dark, which is the defect
   StylesheetPairingTests caught three times in Phase 3 before this page had ever loaded.

   THE ONE RULE THIS REDRAW ADDS: claret is spent on consequence only. The back end's seal, the
   danger fence, a figure that is about to be deleted and a refused run. It is deliberately absent
   from the routine half of the page, so that seeing it means something.
   ============================================================================================ */
.iex{
  padding-bottom:4rem;
}

/* The work and the standing facts beside it. minmax(0,1fr) rather than a bare 1fr: a bare track
   cannot shrink below its content, and the ledger inside this column holds nowrap figures. */
.iex-work{
  display:grid;
  gap:1.4rem;
  align-items:start;
}

/* A SIZE CONTAINER, and that is the instrument rather than a nicety. The ledger and the history are
   laid out against the width of the COLUMN they are in, which stopped being the viewport's the moment
   Q23 put a 232px rail beside them: at 1280 this column is 866px and at 1000 it is 588, and a media
   query cannot tell those apart. The history's eight columns shipped behind `min-width:761px` and, with
   the rail taking 264 of them, overflowed the column and painted OVER the rail — found in a screenshot,
   because .iex-main's own box stayed exactly the width of its track while its child hung out of it. */
.iex-main{
  display:grid;
  gap:1.2rem;
  min-width:0;
  container-type:inline-size;
  container-name:iex-col;
}

@media (min-width:1000px){
  .iex-work{
    grid-template-columns:minmax(0,1fr) 232px;
    gap:1.4rem 2rem;
  }
}

/* THE RAIL. Three standing facts, off the main column instead of eating a full-width band each.
   Below 1000px they fall to the end of the page, which is where the markup already puts them. */
.iex-rail{
  display:grid;
  gap:1rem;
  align-content:start;
  min-width:0;
}

.iex-note{
  border-top:2px solid var(--line-light);
  padding-top:.6rem;
}

.iex-note__t{
  margin:0 0 .3rem;
  font-size:.68rem;
  font-weight:700;
  letter-spacing:.11em;
  text-transform:uppercase;
  color:var(--slate-soft);
}

.iex-note__p{
  margin:0;
  font-size:.82rem;
  line-height:1.5;
  color:var(--slate-soft);
}

.iex-note__p b{
  color:var(--ink);
  font-variant-numeric:tabular-nums;
}

.iex-note__p code{
  font-size:.92em;
}

/* ------------------------------------------------------------------ THE STRIP: the routine act

   Q22 (a). Taking a copy destroys nothing and asks nothing, so it is one line, one button, and
   the parts go behind a disclosure. The button sits ON the strip rather than floating below three
   cards, which was the loudest thing wrong with the shipped page. */
.iex-take{
  display:grid;
  gap:.8rem;
  padding:1.1rem 1.2rem;
  background:var(--surface);
  border:1px solid var(--line-light);
  border-left:3px solid var(--brass);
  border-radius:0 var(--r) var(--r) 0;
}

.iex-strip{
  display:flex;
  flex-wrap:wrap;
  gap:.8rem 1.4rem;
  align-items:center;
  justify-content:space-between;
}

/* flex:1 1 16rem with min-width:0, because a flex item's automatic minimum is its MIN-CONTENT and
   this one holds a serif heading and a sentence — the shape that put the page 29px over at 360. */
.iex-strip__lead{
  display:grid;
  gap:.15rem;
  flex:1 1 16rem;
  min-width:0;
}

.iex-strip__t{
  margin:0;
  font-family:var(--serif);
  font-size:1.2rem;
  line-height:1.2;
  color:var(--heading);
}

.iex-strip__d{
  margin:0;
  font-size:.86rem;
  color:var(--slate-soft);
}

.iex-strip__d b{
  color:var(--ink);
  font-variant-numeric:tabular-nums;
}

.iex-more{
  display:grid;
  gap:.8rem;
}

/* fit-content, NOT max-content, and the difference is 29 pixels at 360. Both shrink the hit area
   to the text so that opening it is not a full-width click. But a width:max-content box reports
   its MAX-content as its minimum contribution to the grid above it, and a percentage max-width
   does not cap an intrinsic contribution. fit-content is the same shrink-wrap with a ceiling. */
.iex-open,
.iex-more > summary,
.iex-inside > summary{
  cursor:pointer;
  width:fit-content;
  max-width:100%;
  font-size:.82rem;
  font-weight:700;
  color:var(--brass-ink);
}

.iex-open:focus-visible,
.iex-more > summary:focus-visible,
.iex-inside > summary:focus-visible{
  outline:2px solid var(--brass);
  outline-offset:3px;
  border-radius:4px;
}

/* ---------------------------------------------------------------------------------- THE PANELS

   A header band and a body, so the eye can tell the container from the things in it. The ledger
   is a direct child rather than a body child, which is how it runs edge to edge. */
/* padding:0 is LOAD-BEARING. These panels are <section> elements and the page-wide
   `section{padding:74px 0}` at the top of this file would otherwise open each one with 74 pixels of
   blank white above its header band. The .admin-panel class this replaced carried a padding of its own
   and hid the rule; two other families in this stylesheet already state it for the same reason. */
.iex-panel{
  display:grid;
  padding:0;
  background:var(--surface);
  border:1px solid var(--line-light);
  border-radius:var(--r);
}

.iex-panel__h{
  display:flex;
  flex-wrap:wrap;
  gap:.3rem 1rem;
  align-items:baseline;
  justify-content:space-between;
  padding:.85rem 1.1rem;
  background:var(--parchment);
  border-bottom:1px solid var(--line-light);
  border-radius:var(--r) var(--r) 0 0;
}

.iex-panel__t{
  margin:0;
  font-family:var(--serif);
  font-size:1.15rem;
  line-height:1.2;
  color:var(--heading);
}

.iex-panel__n{
  font-size:.72rem;
  font-weight:700;
  letter-spacing:.08em;
  text-transform:uppercase;
  color:var(--slate-soft);
}

.iex-panel__b{
  display:grid;
  gap:.9rem;
  padding:1.1rem;
}

/* ---------------------------------------------------------------------------------- THE LEDGER

   One grammar, shared by both halves of the page: a row per part, figures right-aligned and
   tabular so they read DOWN a column, and consequence carried by the rule down the left edge
   rather than by another beige box. */
.iex-led{
  display:grid;
}

.iex-led__head,
.iex-led__row{
  display:grid;
  grid-template-columns:minmax(0,1fr) 5.5rem 7rem minmax(9rem,15rem);
  gap:0 1rem;
  align-items:baseline;
  padding:.75rem 1.1rem .75rem .85rem;
  border-top:1px solid var(--line-light);
  border-left:3px solid transparent;
}

.iex-led__head{
  padding-top:.5rem;
  padding-bottom:.5rem;
  font-size:.66rem;
  font-weight:700;
  letter-spacing:.1em;
  text-transform:uppercase;
  color:var(--slate-soft);
  background:var(--parchment);
}

/* The head's own cells wrap: "In the archive" is wider than the column it labels. */
.iex-led__head .iex-num{
  white-space:normal;
}

.iex-led__row--add{
  border-left-color:var(--ok-line);
}

.iex-led__row--replace{
  border-left-color:var(--warn-line);
}

.iex-led__row--people{
  border-left-color:var(--claret);
}

/* An unticked row is quiet and still readable: the ground moves, the figures go grey, and nothing
   is hidden. A row nobody has ticked is not deleting anything, so its figure is never claret. */
.iex-led__row--off{
  background:var(--parchment);
}

.iex-led__row--off .iex-num{
  color:var(--slate-soft);
}

.iex-led__name{
  display:flex;
  flex-wrap:wrap;
  gap:.35rem .6rem;
  align-items:baseline;
  min-width:0;
}

/* Only the box and the name are the label, so that opening "what's inside" cannot toggle the tick
   underneath the pointer. It wraps, because three nowrap children on one row is how this page
   went 29px over at 360 once already. */
.iex-pick{
  display:flex;
  align-items:baseline;
  gap:.55rem;
  margin:0;
  cursor:pointer;
  flex-wrap:wrap;
  min-width:0;
}

.iex-pick input{
  width:1.05rem;
  height:1.05rem;
  accent-color:var(--brass);
  flex:0 0 auto;
  position:relative;
  top:.15rem;
}

.iex-led__nm{
  font-family:var(--serif);
  font-size:1.05rem;
  line-height:1.25;
  font-weight:600;
}

/* Q24: the one claret object on the routine half of the page. A border and an ink, never a fill —
   a fixed status fill under a themed ink is the pairing the source scan refuses. */
.iex-seal{
  font-size:.62rem;
  font-weight:700;
  letter-spacing:.08em;
  text-transform:uppercase;
  padding:.1em .5em;
  border-radius:4px;
  white-space:nowrap;
  border:1px solid var(--claret);
  color:var(--claret);
}

/* THE FIGURES. Tabular on purpose: a column of counts that shifts when one digit changes is a
   column nobody can compare down. */
.iex-num{
  text-align:right;
  font-variant-numeric:tabular-nums;
  font-size:.86rem;
  white-space:nowrap;
  min-width:0;
}

.iex-num--gone{
  color:var(--claret);
}

.iex-num--dim{
  color:var(--slate-soft);
}

/* The label a figure needs only when the head above it has gone. Shown by the phone rules below. */
.iex-num__lab{
  display:none;
}

.iex-does{
  font-size:.82rem;
  color:var(--slate-soft);
  min-width:0;
}

.iex-does b{
  color:var(--ink);
}

/* The sentence that runs under a row rather than inside a column, because it is about the row and
   not about one figure in it. */
.iex-led__sub{
  grid-column:1 / -1;
  margin-top:.4rem;
  font-size:.8rem;
  line-height:1.5;
  color:var(--slate-soft);
  max-width:74ch;
}

.iex-led__sub b{
  color:var(--ink);
}

/* Q8's strong line, and the reason it is a class of its own: it must not quietly become the same
   grey as the two quiet ones. Full ink against their slate, on a row whose rule is already claret. */
.iex-part__carries--people,
.iex-led__sub--strong{
  color:var(--ink);
}

/* §7.6: closed by default, and it spans the row so the counts inside it line up under the ones
   above. A table with no rows is shown at zero rather than hidden — an empty line is how a person
   learns the archive covers galleries at all. */
.iex-inside{
  grid-column:1 / -1;
  margin-top:.45rem;
}

.iex-inside__list{
  list-style:none;
  margin:.55rem 0 0;
  padding:.7rem .85rem;
  display:grid;
  gap:.4rem;
  border:1px solid var(--line-light);
  border-radius:8px;
  background:var(--parchment);
}

/* A flex item's automatic minimum is its MIN-CONTENT, so a row holding "Branding: the masthead,
   the name, the motto" and a count could not shrink and put the page 29px over at 360. The label
   wraps; the count never does. */
.iex-inside__list li{
  display:flex;
  justify-content:space-between;
  align-items:baseline;
  gap:1rem;
  font-size:.86rem;
  flex-wrap:wrap;
}

.iex-inside__list li > span{
  min-width:0;
}

.iex-inside__list em{
  font-style:normal;
  color:var(--slate-soft);
  font-variant-numeric:tabular-nums;
  white-space:nowrap;
  margin-left:auto;
}

.iex-actions{
  display:flex;
  flex-wrap:wrap;
  gap:.8rem;
  align-items:center;
}

.iex-actions__note{
  font-size:.86rem;
  color:var(--slate-soft);
}

.iex-error{
  margin:0;
  padding:.7rem .9rem;
  border-radius:8px;
  background:var(--bad-fill);
  border:1px solid var(--bad-line);
  color:var(--bad-ink);
  font-weight:700;
}

.iex-warning{
  margin:0;
  padding:.7rem .9rem;
  border-radius:8px;
  background:var(--warn-fill);
  border:1px solid var(--warn-line);
  color:var(--warn-ink);
}

/* --------------------------------------------------------------------------------- THE RECEIPT

   Green when the archive travelled whole, amber when it travelled with something to say, and red
   only when no archive was made at all. */
.iex-receipt{
  display:grid;
  gap:.7rem;
  padding:1rem 1.15rem;
  border-radius:var(--r);
  background:var(--ok-fill);
  border:1px solid var(--ok-line);
  color:var(--ok-ink);
}

.iex-receipt--told{
  background:var(--warn-fill);
  border-color:var(--warn-line);
  color:var(--warn-ink);
}

.iex-receipt--failed{
  background:var(--bad-fill);
  border-color:var(--bad-line);
  color:var(--bad-ink);
}

.iex-receipt__head{
  margin:0;
  display:flex;
  flex-wrap:wrap;
  gap:.35rem .8rem;
  align-items:baseline;
}

.iex-receipt__title{
  font-weight:700;
}

.iex-receipt__file{
  font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
  font-size:.85rem;
  color:inherit;
  opacity:.75;
}

/* The figures as a figure ROW — a label above each number — rather than a sentence of bolded
   fragments. Every colour in here is inherited from the receipt, so the fill and the ink stay in
   one family whatever the state is. */
.iex-figs{
  margin:0;
  display:flex;
  flex-wrap:wrap;
  gap:.5rem 1.7rem;
}

.iex-figs > div{
  display:grid;
  gap:.05rem;
}

.iex-figs dt{
  font-size:.62rem;
  font-weight:700;
  letter-spacing:.1em;
  text-transform:uppercase;
  opacity:.78;
}

.iex-figs dd{
  margin:0;
  font-size:.98rem;
  font-weight:700;
  font-variant-numeric:tabular-nums;
}

.iex-receipt__keep,
.iex-receipt__reason{
  margin:0;
  font-size:.9rem;
  color:inherit;
  opacity:.82;
}

.iex-told{
  list-style:none;
  margin:0;
  padding:0;
  display:grid;
  gap:.55rem;
}

.iex-told li{
  display:grid;
  grid-template-columns:auto 1fr;
  gap:.65rem;
  align-items:baseline;
  font-size:.89rem;
}

/* No background at all. A var(--surface) chip under this panel's fixed ink is the MIRROR defect,
   and it is the one TC's evidence never counted: perfect on Heritage, dark ink on near-black
   everywhere else. */
.iex-told code{
  font-size:.85em;
  font-weight:600;
  padding:0 .1em;
}

.iex-told__kind{
  font-size:.64rem;
  font-weight:700;
  letter-spacing:.08em;
  text-transform:uppercase;
  padding:.18em .5em;
  border-radius:4px;
  white-space:nowrap;
  background:var(--warn-fill);
  border:1px solid var(--warn-line);
  color:var(--warn-ink);
}

.iex-told__kind--missing{
  background:var(--bad-fill);
  border-color:var(--bad-line);
  color:var(--bad-ink);
}

/* A refusal. Every reason, so it is a list even when it holds one line. */
.iex-refuse{
  display:grid;
  gap:.6rem;
  padding:1rem 1.15rem;
  border-radius:var(--r);
  background:var(--bad-fill);
  border:1px solid var(--bad-line);
  color:var(--bad-ink);
}

.iex-refuse__title{
  margin:0;
  font-weight:700;
}

.iex-refuse__list{
  margin:0;
  padding-left:1.15rem;
  display:grid;
  gap:.45rem;
  font-size:.9rem;
}

.iex-refuse__kind{
  font-size:.68rem;
  font-weight:700;
  letter-spacing:.07em;
  text-transform:uppercase;
  margin-right:.35rem;
}

/* ----------------------------------------------------------------------------- THE IMPORT HALF

   Same ledger, two more columns, and one fenced place where something is destroyed. The three
   states a rung can be in are a SHAPE as well as a colour — the rule down the left edge, the
   numeral's ring and the dimming all move together — because a ladder read only by hue is a
   ladder nobody can read on the palettes whose accent is close to their ground. */
.iex-import{
  scroll-margin-top:1.5rem;
}

.iex-import__lead{
  margin:0;
  color:var(--slate);
}

/* What the panel is about to do to you, in three words, before a file is chosen. A band under the
   panel's own header rather than a box inside its body: a box inside a box is the thing this redraw
   exists to stop, and the body's padding around it cost 35 pixels twice. */
.iex-steps{
  list-style:none;
  margin:0;
  padding:0;
  display:flex;
  flex-wrap:wrap;
  border-bottom:1px solid var(--line-light);
}

.iex-step{
  flex:1 1 9rem;
  min-width:0;
  display:flex;
  gap:.45rem;
  align-items:baseline;
  padding:.5rem 1.1rem;
  font-size:.8rem;
  color:var(--slate-soft);
  background:var(--parchment);
  border-right:1px solid var(--line-light);
}

.iex-step:last-child{
  border-right:0;
}

.iex-step--now{
  background:var(--surface);
  color:var(--ink);
  font-weight:700;
  box-shadow:inset 0 -2px 0 var(--brass);
}

.iex-step--done{
  background:var(--ok-fill);
  color:var(--ok-ink);
}

.iex-step__n{
  font-variant-numeric:tabular-nums;
  font-size:.75rem;
}

/* The chooser. The browser's own control is DRESSED rather than replaced: a label wrapping a
   hidden input would make the chosen file's name a JavaScript-only fact, and this page's only
   script is an enhancement nothing depends on. */
.iex-choose{
  display:flex;
  flex-wrap:wrap;
  align-items:center;
  gap:.8rem;
  padding:.95rem 1.05rem;
  border:1px dashed var(--line-light);
  border-radius:var(--r);
  background:var(--parchment);
}

/* A file input's intrinsic width is the browser's, not the author's: at 360px Chromium's is 73
   pixels wider than the page, and min-width:0 does not touch it because the width is not a flex
   minimum. It gets an explicit width and a line of its own. */
.iex-choose input[type="file"]{
  flex:1 1 100%;
  width:100%;
  max-width:100%;
  min-width:0;
  box-sizing:border-box;
  font-size:.86rem;
  color:var(--slate);
}

.iex-choose input[type="file"]::file-selector-button{
  font-family:var(--sans);
  font-size:.8rem;
  font-weight:700;
  margin-right:.75rem;
  padding:.5em 1.05em;
  border-radius:100px;
  border:1px solid var(--brass-ink);
  background:transparent;
  color:var(--brass-ink);
  cursor:pointer;
}

/* What is being looked at. Solid rather than dashed, because a file is held now. */
.iex-staged{
  display:flex;
  flex-wrap:wrap;
  align-items:baseline;
  gap:.4rem .9rem;
  padding:.85rem 1rem;
  border:1px solid var(--brass);
  border-radius:var(--r);
  background:var(--parchment);
}

.iex-staged--bad{
  border-color:var(--bad-line);
}

.iex-staged__name{
  font-family:ui-monospace,Menlo,Consolas,monospace;
  font-weight:700;
  font-size:.9rem;
  word-break:break-all;
}

.iex-staged__meta,
.iex-staged__state{
  font-size:.84rem;
  color:var(--slate-soft);
}

.iex-staged__swap{
  font-size:.84rem;
  margin-left:auto;
}

/* ------------------------------------------------------------------------------- THE ONE FENCE

   Q24. The warning and the ladder are one object rather than a beige paragraph followed by two
   more panels, and this is the only claret border on the page. The mechanism is untouched: the
   copy is still a run this site recorded, still accepted for an hour, still found by asking the
   log; the name is still checked with the keyboard's apostrophes forgiven. */
.iex-fence{
  border:1px solid var(--claret);
  border-radius:var(--r);
  background:var(--surface);
}

.iex-fence__h{
  margin:0;
  padding:.75rem 1rem;
  font-size:.88rem;
  background:var(--bad-fill);
  color:var(--bad-ink);
  border-bottom:1px solid var(--bad-line);
  border-radius:calc(var(--r) - 1px) calc(var(--r) - 1px) 0 0;
}

.iex-fence__b{
  padding:.95rem 1rem;
}

.iex-ladder{
  list-style:none;
  margin:0;
  display:grid;
  gap:.7rem;
}

.iex-rung{
  display:grid;
  grid-template-columns:auto 1fr;
  gap:.85rem;
  align-items:start;
  padding:.85rem 1rem;
  border:1px solid var(--line-light);
  border-left:3px solid var(--line-light);
  border-radius:0 var(--r) var(--r) 0;
  background:var(--parchment);
}

.iex-rung--now{
  border-color:var(--line-light);
  border-left-color:var(--claret);
  background:var(--surface);
}

.iex-rung--done{
  border-color:var(--ok-line);
  border-left-color:var(--ok-line);
  background:var(--ok-fill);
  color:var(--ok-ink);
}

/* Dimmed AND still ruled: the step is visible so a person knows what is coming, and unreachable
   so they do not try. */
.iex-rung--locked{
  opacity:.6;
}

.iex-rung__n{
  width:1.6rem;
  height:1.6rem;
  display:grid;
  place-items:center;
  border-radius:50%;
  border:1px solid var(--line-light);
  background:var(--surface);
  font-size:.8rem;
  font-weight:700;
  color:var(--slate-soft);
}

.iex-rung--now .iex-rung__n{
  border-color:var(--claret);
  color:var(--claret);
}

.iex-rung--done .iex-rung__n{
  border-color:var(--ok-line);
  color:var(--ok-ink);
}

.iex-rung__body{
  display:grid;
  gap:.5rem;
  justify-items:start;
  min-width:0;
}

.iex-rung__title{
  margin:0;
  font-weight:700;
  font-size:.95rem;
}

.iex-rung__detail{
  margin:0;
  font-size:.87rem;
  color:inherit;
  max-width:62ch;
}

.iex-rung--now .iex-rung__detail,
.iex-rung--locked .iex-rung__detail{
  color:var(--slate-soft);
}

.iex-rung__file{
  font-family:ui-monospace,Menlo,Consolas,monospace;
  font-size:.88em;
}

/* The typed name. Full width of its rung: this lodge's is 36 characters, and a field you cannot
   read back is a field you cannot check before pressing the button underneath it. */
.iex-name{
  width:100%;
  max-width:34rem;
  font-family:ui-monospace,Menlo,Consolas,monospace;
  font-size:.88rem;
  padding:.5rem .65rem;
  border:1px solid var(--line-light);
  border-radius:8px;
  background:var(--surface);
  color:var(--slate);
}

.iex-name:disabled{
  background:var(--parchment);
  color:var(--slate-soft);
  border-style:dashed;
}

/* The button whose words change with its consequence. It takes BOTH colours from the bad triple,
   which is the one family with a fill, a line and an ink that are provably readable together on
   every palette — a claret ground under a themed ink is the exact defect the pairing guard caught
   three times in Phase 3. */
.iex-danger{
  background:var(--bad-ink);
  border-color:var(--bad-ink);
  color:var(--bad-fill);
}

/* The hover moves the LIGHTNESS and not the palette. Reaching for --claret here paired a themed
   ground with a fixed status ink, and StylesheetPairingTests said so before the button had ever
   been hovered. */
.iex-danger:hover{
  filter:brightness(1.15);
}

/* --------------------------------------------------------------------------------- THE HISTORY

   Ruled rather than boxed, the counts right-aligned and tabular so they read DOWN, an import
   carrying a brass rule and a refusal a claret one, and the reason under the row it belongs to
   instead of cut off at the edge. One row markup, laid out twice: a grid of columns on a desktop
   and a card on a phone, because eight columns at 360px is a sideways scroller whose result is
   off the right-hand edge — the one column somebody came to read. */
.iex-runs{
  list-style:none;
  margin:0;
  padding:0;
  display:grid;
}

.iex-run{
  display:grid;
  grid-template-columns:1fr;
  gap:.25rem .9rem;
  padding:.7rem 1.1rem .7rem .85rem;
  border-top:1px solid var(--line-light);
  border-left:3px solid transparent;
  font-size:.85rem;
}

.iex-run:first-child{
  border-top:0;
}

.iex-run:nth-child(even){
  background:var(--parchment);
}

.iex-run--in{
  border-left-color:var(--brass);
}

.iex-run--failed{
  border-left-color:var(--claret);
}

.iex-run__when{
  font-weight:700;
  font-variant-numeric:tabular-nums;
}

.iex-run__what{
  font-size:.66rem;
  font-weight:700;
  letter-spacing:.09em;
  text-transform:uppercase;
  color:var(--slate-soft);
}

.iex-run--in .iex-run__what{
  color:var(--brass-ink);
}

.iex-run__parts,
.iex-run__by{
  color:var(--slate-soft);
}

.iex-run__why{
  color:var(--claret);
  font-size:.82rem;
}

.iex-run__why--quiet{
  color:var(--slate-soft);
}

.iex-tag{
  display:inline-block;
  font-size:.64rem;
  font-weight:700;
  letter-spacing:.08em;
  text-transform:uppercase;
  padding:.18em .5em;
  border-radius:4px;
  border:1px solid var(--line-light);
  color:var(--slate-soft);
  background:var(--surface);
}

.iex-tag--ok{
  background:var(--ok-fill);
  border-color:var(--ok-line);
  color:var(--ok-ink);
}

.iex-tag--failed{
  background:var(--bad-fill);
  border-color:var(--bad-line);
  color:var(--bad-ink);
}

/* THE COLUMN, not the window. 48rem is the width the eight fields below actually need — 30.5rem of
   fixed tracks, 11rem of minimums for the two that flex, 5.25rem of gaps and 2rem of padding — and
   asking the column for it is what stops the row from overflowing into the rail. */
@container iex-col (min-width:48rem){
  /* Above the phone the control and its button share a line. The full-width base below is the
     measured one and stays the base: a file input's intrinsic width is the browser's, and at 360px
     Chromium's is wider than the page. */
  .iex-choose input[type="file"]{
    flex:1 1 18rem;
  }

  .iex-run{
    grid-template-columns:9.5rem 4rem minmax(6rem,1fr) 4rem 4rem 4.5rem minmax(5rem,1fr) 4.5rem;
    gap:.25rem .75rem;
    align-items:baseline;
  }

  .iex-run__result{
    text-align:right;
  }

  /* The reason and the disclosures run under the whole row rather than squeezing a ninth column
     into the eight above. */
  .iex-run__why{
    grid-column:1 / -1;
  }
}

/* -------------------------------------------------------------- A NARROW COLUMN, 360px AND UP

   The ledger stops being a table and becomes a stack: the head goes, each figure takes the label
   the head was carrying for it, and the numbers read from the left like the words above them. It
   asks the COLUMN rather than the window for the same reason the history above does. */
@container iex-col (max-width:47.999rem){
  .iex-led__head{
    display:none;
  }

  .iex-led__row{
    grid-template-columns:1fr;
    gap:.2rem;
    padding:.85rem 1rem .85rem .8rem;
  }

  .iex-num,
  .iex-does{
    text-align:left;
  }

  .iex-num{
    white-space:normal;
  }

  .iex-num__lab{
    display:inline;
    margin-right:.4rem;
    font-size:.7rem;
    font-weight:700;
    letter-spacing:.08em;
    text-transform:uppercase;
    color:var(--slate-soft);
  }

  .iex-led__sub{
    margin-top:.3rem;
  }

  .iex-strip .btn{
    width:100%;
    text-align:center;
  }
}

/* =========================================================================================================
   IB PHASE 5 - /Admin/Banners, THE LEDGER (.bnr-*) (migration 070, docs/image-background-plan.md
   section 16.3, gate Q11, mockup docs/mockups/image-background-banner.html Plate D).

   Drawn as the import/export ledger is drawn: one row per route, the settings opening in place. Every
   colour here is a theme token, so the screen follows a twelve-palette switch like every other admin page.
   ========================================================================================================= */
.bnr-notice{
  margin:0 0 1.25rem;
  padding:.75rem 1rem;
  border-left:3px solid var(--brass);
  background:var(--parchment-2);
  color:var(--slate);
  font-size:.92rem;
}

.bnr-notice--warn{
  border-left-color:var(--claret);
}

.bnr-ledger{
  border:1px solid var(--line-light);
  border-radius:10px;
  background:var(--surface);
  overflow:hidden;
}

/* THE HEAD IS THE LEDGER'S ONE FIXED GRID, and every row's own line repeats it rather than inheriting it -
   a subgrid would have been tidier and is not what makes this readable: the open panel below a row is a
   full-width child, so the row is a BLOCK containing a grid line rather than a grid row itself. */
.bnr-ledger__head,
.bnr-row__line{
  display:grid;
  grid-template-columns:minmax(9rem,14rem) minmax(0,1fr) auto;
  gap:1rem;
  align-items:center;
  padding:.85rem 1.1rem;
}

.bnr-ledger__head{
  border-bottom:1px solid var(--line-light);
  background:var(--parchment-2);
  font-size:.72rem;
  font-weight:600;
  letter-spacing:.14em;
  text-transform:uppercase;
  color:var(--slate-soft);
}

.bnr-row + .bnr-row{
  border-top:1px solid var(--line-light);
}

.bnr-row--open{
  background:var(--parchment);
}

.bnr-row__page{
  display:flex;
  flex-direction:column;
  gap:.15rem;
}

.bnr-row__page b{
  font-family:var(--serif);
  font-size:1.05rem;
  color:var(--heading);
}

.bnr-row__route{
  font-size:.8rem;
  color:var(--brass-ink);
  text-decoration:none;
}

.bnr-row__route:hover,
.bnr-row__route:focus-visible{
  text-decoration:underline;
}

.bnr-row__state{
  display:flex;
  align-items:center;
  gap:.7rem;
  min-width:0;                 /* a grid item's min-width is auto, and these hold long file names */
}

.bnr-row__thumb,
.bnr-pick__thumb{
  flex:0 0 auto;
  width:64px;
  height:44px;
  border-radius:4px;
  overflow:hidden;
  background:var(--parchment-2);
  border:1px solid var(--line-light);
}

.bnr-row__thumb img,
.bnr-pick__thumb img{
  width:100%;
  height:100%;
  object-fit:cover;
  display:block;
}

.bnr-row__meta,
.bnr-pick__meta{
  display:flex;
  flex-direction:column;
  gap:.1rem;
  min-width:0;
}

.bnr-row__meta b,
.bnr-pick__meta b{
  font-size:.9rem;
  color:var(--slate);
  overflow:hidden;
  text-overflow:ellipsis;
  white-space:nowrap;
}

.bnr-row__meta span,
.bnr-pick__meta span{
  font-size:.78rem;
  color:var(--slate-soft);
}

.bnr-row__none{
  font-size:.88rem;
  color:var(--slate-soft);
  font-style:italic;
}

/* A REFERENCE THAT IS NOT BEING PAINTED IS NOT THE SAME AS NO PICTURE, and the two must not look alike:
   one is a decision the author made and the other is a decision the site made for them. */
.bnr-row__trouble{
  font-size:.88rem;
  color:var(--claret);
}

.bnr-row__act{
  justify-self:end;
}

.bnr-panel{
  padding:0 1.1rem 1.25rem;
  display:flex;
  flex-direction:column;
  gap:1.15rem;
}

.bnr-field{
  display:grid;
  grid-template-columns:minmax(6rem,9rem) minmax(0,1fr);
  gap:1rem;
  align-items:start;
  padding-top:1.15rem;
  border-top:1px dashed var(--line-light);
}

.bnr-field__label{
  font-size:.78rem;
  font-weight:600;
  letter-spacing:.08em;
  text-transform:uppercase;
  color:var(--slate-soft);
  padding-top:.2rem;
}

.bnr-field__body{
  display:flex;
  flex-direction:column;
  gap:.55rem;
  min-width:0;
}

.bnr-field__note{
  margin:0;
  font-size:.8rem;
  color:var(--slate-soft);
  max-width:62ch;
}

.bnr-pick{
  display:flex;
  align-items:center;
  gap:.7rem;
  min-width:0;
}

.bnr-pick__actions{
  display:flex;
  flex-wrap:wrap;
  gap:.5rem;
}

/* THE THREE SEGMENTS. The radio is the control and the label paints it, which is this column's arrangement
   since SCC 7a - arrow keys, focus and posting are the browser's job. */
.bnr-seg{
  display:inline-flex;
  border:1px solid var(--line-light);
  border-radius:999px;
  overflow:hidden;
  background:var(--surface);
  width:max-content;
  max-width:100%;
}

/* `position:relative` IS LOAD-BEARING AND NOT DECORATION. The radio below is a 1px absolutely positioned
   box under a painted label, so without a positioned wrapper it positions itself against whatever box
   happens to be positioned above it. Backlog §1 was exactly this: a hidden radio escaped to a dialog's
   scroll container and landed 1098px out, scrolling the head away every time the row took focus. A guard
   in WidgetBackgroundPanelTests states the rule over every declaration block in this file, and it caught
   both of this screen's chip families the first time they were written. */
.bnr-seg__opt{
  margin:0;
  position:relative;
}

.bnr-seg__opt input{
  position:absolute;
  width:1px;
  height:1px;
  opacity:0;
  pointer-events:none;
}

.bnr-seg__opt span{
  display:inline-block;
  padding:.4rem 1rem;
  font-size:.85rem;
  color:var(--slate-soft);
  cursor:pointer;
  min-height:36px;
  line-height:1.7;
}

.bnr-seg__opt input:checked + span{
  background:var(--brass);
  color:var(--on-brass);
  font-weight:600;
}

.bnr-seg__opt input:focus-visible + span{
  outline:2px solid var(--brass-ink);
  outline-offset:-2px;
}

.bnr-aim{
  display:flex;
  flex-direction:column;
  gap:.4rem;
}

.bnr-aim__row{
  display:grid;
  grid-template-columns:4.5rem minmax(0,1fr) 3.5rem;
  gap:.6rem;
  align-items:center;
  font-size:.85rem;
  color:var(--slate-soft);
}

.bnr-aim__row--strength{
  margin-top:.35rem;
}

.bnr-aim__row output{
  font-variant-numeric:tabular-nums;
  color:var(--slate);
}

/* THE SIX LOOKS. Each swatch wears the look it names through data-image-filter, which is the SAME five CSS
   rules the card's layer, the gallery and the theme chips read - a chip painting its own approximation
   would be a seventh statement of a filter. */
.bnr-chips{
  display:flex;
  flex-wrap:wrap;
  gap:.5rem;
}

.bnr-chip{
  margin:0;
  position:relative;              /* see .bnr-seg__opt — the same 1px radio, the same reason */
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:.25rem;
  cursor:pointer;
}

.bnr-chip input{
  position:absolute;
  width:1px;
  height:1px;
  opacity:0;
  pointer-events:none;
}

.bnr-chip__swatch{
  display:block;
  width:44px;
  height:32px;
  border-radius:4px;
  border:2px solid transparent;
  background:linear-gradient(135deg,#8a6a2f,#c9c9c9 55%,#1f1c17);
}

.bnr-chip input:checked + .bnr-chip__swatch{
  border-color:var(--brass);
}

.bnr-chip input:focus-visible + .bnr-chip__swatch{
  outline:2px solid var(--brass-ink);
  outline-offset:2px;
}

.bnr-chip__name{
  font-size:.72rem;
  color:var(--slate-soft);
}

/* THE SCRIM TRACK, AND THE HATCHED STRETCH IS THE FLOOR. The width is written by the SERVER as an inline
   percentage - there is no arithmetic on that page - and the hatch is what the picture's own brightness has
   taken off the table. */
.bnr-slider{
  position:relative;
  padding:.35rem 0;
}

.bnr-slider__barred{
  position:absolute;
  left:0;
  top:50%;
  transform:translateY(-50%);
  height:6px;
  border-radius:3px;
  pointer-events:none;      /* a mark that warns about a control must never intercept it */
  background:repeating-linear-gradient(
    45deg,
    color-mix(in srgb, var(--claret) 45%, transparent) 0 4px,
    transparent 4px 8px);
}

.bnr-slider__range{
  position:relative;
  width:100%;
  margin:0;
}

.bnr-slider__scale{
  display:flex;
  justify-content:space-between;
  font-size:.75rem;
  color:var(--slate-soft);
  font-variant-numeric:tabular-nums;
}

.bnr-panel__act{
  display:flex;
  gap:.6rem;
  padding-top:1.15rem;
  border-top:1px dashed var(--line-light);
}

.bnr-foot{
  margin:1.25rem 0 0;
  font-size:.85rem;
  color:var(--slate-soft);
  max-width:76ch;
}

@media (max-width:820px){
  .bnr-ledger__head{display:none}
  .bnr-row__line{grid-template-columns:minmax(0,1fr)}
  .bnr-row__act{justify-self:start}
  .bnr-field{grid-template-columns:minmax(0,1fr)}
}

/* =====================================================================================================
   LH PHASE 2 — /Admin/History, THE DESIGNER THAT IS THE TIMELINE (backlog item 21,
   docs/lodge-history-plan.md section 12, mockup docs/mockups/lodge-history-admin.html plates B, C and D;
   Phase 2's gate docs/mockups/lodge-history-phase-2-gate.html, Q9 to Q12 all on the recommendation).

   NOT ONE OF THE 38 `.hist-*` RULES ABOVE IS TOUCHED. Everything here is prefixed `hist-adm` and the
   preview column renders the same partial /History renders, so the rules that draw the lodge's history
   draw the admin screen's copy of it too. That is the whole reason the screen is worth building this way
   and the whole reason this block must never reach inside `.hist-entry` to adjust something.
   ===================================================================================================== */

.hist-adm__refusal{
  margin:0 0 1.25rem;
  padding:.85rem 1rem;
  border-left:3px solid var(--claret);
  background:var(--parchment-2);
  color:var(--slate);
  font-size:.92rem;
}

.hist-adm__refusal ul{
  margin:.4rem 0 0;
  padding-left:1.1rem;
}

/* The rail sits BESIDE the preview at any width, which is what gate Q10(a) bought: the preview is honest
   at every column width now, so there is no 620px floor to keep it above. Below 900px they stack, for the
   ordinary reason that a 330px rail and a preview cannot share 700px. */
.hist-adm__body{
  display:grid;
  gap:1.25rem;
  align-items:start;
}

@media (min-width:900px){
  .hist-adm__body{ grid-template-columns:minmax(0,1fr) 340px; }
}

.hist-adm__zone{
  min-width:0;
}

.hist-adm__zonehead{
  display:flex;
  flex-wrap:wrap;
  align-items:baseline;
  justify-content:space-between;
  gap:.5rem;
  margin-bottom:.6rem;
}

.hist-adm__zonehead h2{
  margin:0;
  font-family:var(--serif);
  font-size:1.05rem;
  color:var(--heading);
}

.hist-adm__width{
  font-size:.74rem;
  color:var(--slate-soft);
}

/* THE FRAME CLIPS AND THE BOX SCALES — the board designer's mechanism, and its three moves are stated on
   `.widgets-board--home` above. `overflow:hidden` is what makes the PRE-SCRIPT state safe: before site.js
   measures anything the box is laid out at its full 1132px, which is wider than this column, and the frame
   clips it instead of taking the admin page sideways.

   THE PADDING IS ON THE FRAME AND NOT ON THE BOX, which is the one difference from the board. /History's
   timeline is exactly HomeBoardLayout.BoardContentWidth of CONTENT — 1132px is already `--maxw` minus
   `.wrap`'s padding — so the box has to be 1132px with nothing added, or the rail at `left:84px` would sit
   somewhere the visitor never sees it. The board needed `box-sizing:content-box` because its padding was
   inside the measured surface; here there is no padding inside to correct for. */
.hist-adm__frame{
  overflow:hidden;
  padding:1rem 1rem .25rem;
  border:1px solid var(--line-light);
  border-radius:10px;

  /* THE PREVIEW'S GROUND IS /History's GROUND. `.hist-entry__body p` paints `var(--slate)` and takes its
     contrast from whatever is behind it; the History page's is `--parchment` (body), so anything else here
     would be a preview measured against a ground the visitor never gets. The three self-grounded shapes
     (`.hist-quote` on `--surface`, `.hist-verse` on `--parchment-2`, `.hist-event--dark` on `--ink`) carry
     their own and are unaffected either way. */
  background:var(--parchment);
}

.hist-adm__site{
  width:var(--hist-adm-board, 1132px);
  transform-origin:top left;

  /* Set by site.js, which is the one measurement CSS cannot take. Defaults to 1, so the frame clips.
     DO NOT switch this transform off in a media query without teaching site.js about it: the script also
     writes the frame's HEIGHT from the scaled box, and a `transform:none` it does not know about would
     leave the frame the wrong height rather than merely unscaled. */
  transform:scale(var(--hist-adm-scale, 1));
}

/* ONE PLAIN BLOCK BOX PER ENTRY, WITH THE ARTICLE'S OWN DIMENSIONS. `.hist-timeline` is a flex column, so
   this wrapper stretches to the full width and its height is the article's; the tools strip is positioned
   against it and therefore lands exactly where it would land inside `.hist-entry`, which is what lets the
   entry partial stay the one the public page renders with no admin parameter in it.

   It is deliberately not `display:contents`: a `display:contents` box has no `position` and no `margin` —
   this project has paid to find both — and the whole job here is to be a positioning context. */
.hist-adm__entry{
  position:relative;
  cursor:move;
}

.hist-adm__entry.is-open{
  outline:2px solid var(--brass);
  outline-offset:6px;
  border-radius:4px;
}

.hist-adm__entry.is-dragging{
  opacity:.45;
}

/* THE CHROME'S TYPE IS DIVIDED BY THE SCALE, SO IT PAINTS AT A CONSTANT SIZE INSIDE A SCALED SURFACE.
   Without this the tools and the seams shrink with the timeline: in a 500px column the scale is about .44,
   and a .68rem label paints at five pixels. "A control can be faithful and unusable" is a defect this
   project has already found once, at 12% zoom, with every assertion passing.

   It is a division in `font-size` rather than a counter-`transform` on purpose. A transform does not change
   layout, so a counter-scaled label would paint outside the box the seam row reserved for it and overlap
   the entry above; dividing the font-size grows the LAYOUT box too, so the row keeps its proportions and
   the painted height stays put. The padding is in `em` so it follows the same division. */
.hist-adm__tools{
  position:absolute;
  top:-2px;
  right:0;
  z-index:2;
  display:flex;
  gap:.35em;
  align-items:center;
  font-size:calc(.68rem / var(--hist-adm-scale, 1));
}

.hist-adm__grip{
  color:var(--slate-soft);
  cursor:move;
  line-height:1;
}

.hist-adm__tools .hist-adm__grip{
  background:var(--surface);
  border:1px solid var(--line-light);
  border-radius:5px;
  padding:.16em .4em;
}

.hist-adm__edit,
.hist-adm__editing{
  font:inherit;
  border-radius:5px;
  padding:.16em .5em;
  border:1px solid var(--line-light);
  background:var(--surface);
  color:var(--slate-soft);
  white-space:nowrap;
}

.hist-adm__edit{ cursor:pointer; }

.hist-adm__edit:hover{
  border-color:var(--brass);
  color:var(--heading);
}

.hist-adm__editing{
  border-color:var(--brass);
  background:var(--brass);
  color:var(--on-brass);
}

/* A seam is a flex item of `.hist-timeline`, between two entries, and it is a submit button: pressing it
   posts the whole screen and adds an entry above the one it names. */
.hist-adm__seam{
  display:flex;
  align-items:center;
  gap:.7em;
  width:100%;
  margin:0 0 1em;
  padding:0;
  border:0;
  background:none;
  color:var(--slate-soft);
  font-family:var(--sans);
  font-size:calc(.72rem / var(--hist-adm-scale, 1));
  cursor:pointer;
}

.hist-adm__seam::before,
.hist-adm__seam::after{
  content:"";
  flex:1;
  height:1px;
  background:var(--line-light);
}

.hist-adm__seam:hover{
  color:var(--heading);
}

.hist-adm__seam:hover::before,
.hist-adm__seam:hover::after{
  background:var(--brass);
}

/* PLATE D. The state a different lodge starts in, and the one place gate Q5(a) is driven rather than
   reasoned — the E2E net cannot empty HistoryEntries on a shared instance, but an author making their
   first entry starts here. */
.hist-adm__empty{
  border:1px dashed color-mix(in srgb, var(--slate-soft) 55%, transparent);
  border-radius:10px;
  padding:1.6rem 1.25rem;
  text-align:center;
  color:var(--slate-soft);
  background:var(--parchment);
}

.hist-adm__empty b{
  display:block;
  font-family:var(--serif);
  font-size:1.2rem;
  color:var(--heading);
  margin-bottom:.35rem;
}

.hist-adm__empty p{
  margin:0 0 .9rem;
}

.hist-adm__rail{
  display:grid;
  gap:1rem;
  min-width:0;
}

.hist-adm__card{
  background:var(--surface);
  border:1px solid var(--line-light);
  border-radius:10px;
  padding:.95rem 1rem 1rem;
}

.hist-adm__card h3{
  margin:0 0 .7rem;
  font-family:var(--serif);
  font-size:.98rem;
  color:var(--heading);
}

.hist-adm__card--panel{
  border-color:var(--brass);
}

.hist-adm__fld{
  margin-bottom:.85rem;
}

.hist-adm__fld:last-child{
  margin-bottom:0;
}

.hist-adm__fld label{
  display:flex;
  flex-wrap:wrap;
  align-items:baseline;
  gap:.4rem;
  margin-bottom:.3rem;
  font-size:.78rem;
  font-weight:600;
  color:var(--heading);
}

.hist-adm__opt{
  font-weight:400;
  color:var(--slate-soft);
  font-size:.72rem;
}

/* THE COUNTER IS THE WHOLE OF GATE Q12(a)'s FIRST HALF, AND THERE IS NO `maxlength` ANYWHERE ON THIS
   SCREEN. `maxlength` would drop the tail of a paste with no message, which is the same silent truncation
   the read-side clamp exists as a backstop for; the save refuses instead, with the field named. So the
   counter has to be able to read PAST the cap, which is what `is-over` is for. */
.hist-adm__count{
  margin-left:auto;
  font-weight:400;
  font-variant-numeric:tabular-nums;
  font-size:.72rem;
  color:var(--slate-soft);
}

.hist-adm__count.is-over{
  color:var(--claret);
  font-weight:600;
}

.hist-adm__inp{
  width:100%;
  font-family:var(--sans);
  font-size:.88rem;
  color:var(--slate);
  background:var(--surface);
  border:1px solid var(--line-light);
  border-radius:6px;
  padding:.42rem .55rem;
}

.hist-adm__inp:focus{
  outline:2px solid var(--brass);
  outline-offset:1px;
}

/* The fallback control, and it is the POSTED one: with scripting off this textarea is what an author types
   markup into, which is Plate E's left-hand option — exact, and it never invents a tag. site.js hides it
   and puts the narrow editor above it, syncing back into it on every keystroke, so there is exactly one
   thing on the form and no hidden second copy of the value to disagree with. */
.hist-adm__raw{
  font-family:ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size:.78rem;
  line-height:1.5;
  resize:vertical;
}

.hist-adm__hint{
  margin:.35rem 0 0;
  font-size:.74rem;
  line-height:1.5;
  color:var(--slate-soft);
}

.hist-adm__hint em{
  color:var(--slate);
}

.hist-adm__parts{
  display:grid;
  gap:.4rem;
  margin-bottom:.6rem;
}

.hist-adm__part{
  display:grid;
  grid-template-columns:auto minmax(0,1fr) auto;
  align-items:center;
  gap:.5rem;
  padding:.4rem .5rem;
  border:1px solid var(--line-light);
  border-radius:7px;
  background:var(--parchment);
}

.hist-adm__part.is-open{
  border-color:var(--brass);
  background:var(--parchment-2);
}

.hist-adm__partpick{
  display:grid;
  gap:.1rem;
  min-width:0;
  text-align:left;
  border:0;
  background:none;
  padding:0;
  font:inherit;
  cursor:pointer;
}

.hist-adm__partkind{
  font-size:.76rem;
  font-weight:600;
  color:var(--heading);
}

.hist-adm__partkind i{
  font-style:normal;
  color:var(--brass-ink);
  margin-right:.3em;
}

.hist-adm__partsum{
  font-size:.72rem;
  color:var(--slate-soft);
  overflow:hidden;
  text-overflow:ellipsis;
  white-space:nowrap;
}

.hist-adm__partacts{
  display:flex;
  gap:.2rem;
}

.hist-adm__partacts button{
  width:1.55rem;
  height:1.55rem;
  display:grid;
  place-items:center;
  font-size:.78rem;
  line-height:1;
  color:var(--slate-soft);
  background:var(--surface);
  border:1px solid var(--line-light);
  border-radius:5px;
  cursor:pointer;
}

.hist-adm__partacts button:hover:not(:disabled){
  border-color:var(--brass);
  color:var(--heading);
}

.hist-adm__partacts button:disabled{
  opacity:.35;
  cursor:default;
}

/* The palette, and "Add a name" inside the alumni panel: the same pill either way, because they are the
   same act — one more shape in the thing being edited. */
.hist-adm__pal{
  display:flex;
  flex-wrap:wrap;
  gap:.35rem;
}

.hist-adm__pal button,
.hist-adm__add{
  font-family:var(--sans);
  font-size:.74rem;
  border:1px dashed color-mix(in srgb, var(--slate-soft) 55%, transparent);
  border-radius:999px;
  padding:.3em .8em;
  background:var(--surface);
  color:var(--slate);
  white-space:nowrap;
  cursor:pointer;
}

.hist-adm__pal button:hover,
.hist-adm__add:hover{
  border-style:solid;
  border-color:var(--brass);
  color:var(--heading);
}

.hist-adm__pal button i,
.hist-adm__add i{
  font-style:normal;
  color:var(--brass-ink);
  margin-right:.35em;
}

/* The two closed lists (a quotation's look, a milestone's weight) as radios that look like a segment. They
   are radios and not buttons because the value has to POST — a segmented control made of buttons on a form
   this size is a hidden field plus a click handler, which is two more things to get wrong. */
.hist-adm__seg{
  /* THE HIDDEN RADIO IS ABSOLUTELY POSITIONED, SO THIS BOX HAS TO BE ITS CONTAINING BLOCK — and this rule
     was written because the guard for it went red on the first run. Backlog §1 was exactly this defect: a
     1px radio in a settings row escaped to the dialog's scroll box, landed 1098px out, and scrolled the
     head of the dialog away every time the row took focus. Three chip families in the widget dialog carry
     the same `position:relative` for the same reason, and
     WidgetBackgroundPanelTests.EveryWrapperOfAnAbsolutelyPositionedRadio_EstablishesItsOwnContainingBlock
     is what refuses a fourth family that forgets it. */
  position:relative;
  display:inline-flex;
  border:1px solid var(--line-light);
  border-radius:7px;
  overflow:hidden;
}

.hist-adm__seg label{
  margin:0;
  display:block;
  font-size:.76rem;
  font-weight:400;
  color:var(--slate);
  cursor:pointer;
}

.hist-adm__seg label + label{
  border-left:1px solid var(--line-light);
}

.hist-adm__seg input{
  position:absolute;
  opacity:0;
  width:1px;
  height:1px;
}

.hist-adm__seg span{
  display:block;
  padding:.34rem .8rem;
  background:var(--surface);
}

.hist-adm__seg label.is-on span,
.hist-adm__seg input:checked + span{
  background:var(--brass);
  color:var(--on-brass);
  font-weight:600;
}

/* The focus ring has to be on the SPAN, because the input it belongs to is a 1px box off screen. */
.hist-adm__seg input:focus-visible + span{
  outline:2px solid var(--heading);
  outline-offset:-2px;
}

.hist-adm__cards{
  display:grid;
  gap:.7rem;
  margin-bottom:.6rem;
}

.hist-adm__cardrow{
  border:1px solid var(--line-light);
  border-radius:8px;
  padding:.6rem .65rem;
  background:var(--parchment);
}

.hist-adm__cardhead{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:.5rem;
  margin-bottom:.5rem;
}

.hist-adm__cardhead b{
  font-size:.76rem;
  color:var(--heading);
}

.hist-adm__actions{
  display:flex;
  flex-wrap:wrap;
  gap:.6rem;
  align-items:center;
}

.hist-adm__posture{
  margin:0;
  font-size:.72rem;
  line-height:1.5;
  color:var(--slate-soft);
}

/* THE NARROW EDITOR (gate Q9(a)). Bold, italic and paragraphs and nothing else, because those are the only
   inline shapes the 38 rules above draw — and every control the widget's editor has that this one does not
   (fonts, sizes, colour swatches, line spacing, headings, fit-to-card, links) is a way to put something on
   /History that the eleven-palette contrast sweep has never measured. */
.hist-adm__edtools{
  display:flex;
  gap:.25rem;
  margin-bottom:.3rem;
}

.hist-adm__edtools button{
  min-width:1.7rem;
  height:1.7rem;
  font-family:var(--sans);
  font-size:.8rem;
  line-height:1;
  color:var(--slate);
  background:var(--surface);
  border:1px solid var(--line-light);
  border-radius:5px;
  cursor:pointer;
}

.hist-adm__edtools button:hover{
  border-color:var(--brass);
  color:var(--heading);
}

.hist-adm__ed{
  min-height:5.5rem;
  max-height:24rem;
  overflow-y:auto;
  font-family:var(--sans);
  font-size:.88rem;
  line-height:1.6;
  color:var(--slate);
  background:var(--surface);
  border:1px solid var(--line-light);
  border-radius:6px;
  padding:.5rem .6rem;
}

.hist-adm__ed:focus{
  outline:2px solid var(--brass);
  outline-offset:1px;
}

/* The paragraph gap the timeline actually draws, so the editor shows the shape of what it is making. It is
   NOT the timeline's own rule reused: `.hist-entry__body p` also carries a 68ch measure and a 1.8
   line-height that belong to the page's column, not to a 300px rail. */
.hist-adm__ed p{
  margin:0 0 .55rem;
}

.hist-adm__ed p:last-child{
  margin-bottom:0;
}

/* =================================================================================================
   ITEM 31: the .rl- family, the role editor on /Admin/Users and what a role does on /Admin/Roles.
   Approved plates B, C and E, docs/mockups/role-management-gate.html.

   Every colour is a THEME token. The editor lives inside .user-mgmt-actions-panel, which is an
   absolutely positioned box 224px wide at its minimum, so the tick grid is two columns and the
   panel is widened rather than the grid being allowed to set it.
   ================================================================================================= */
.rl-editor{
  display:flex;
  flex-direction:column;
  gap:.3rem;
  padding:.1rem .15rem .35rem;
}

.rl-editor__lede{
  font-size:.78rem;
  color:var(--slate-soft);
  margin:0 0 .15rem;
  line-height:1.45;
}

/* Item 40: the note is now UNDER the grid rather than tucked under a single checkbox, so it needs
   the separation the rule line used to give it and no longer gets from a border. */
.rl-editor__note{
  font-size:.74rem;
  color:var(--slate-soft);
  margin:.55rem 0 0;
  line-height:1.45;
}

.rl-editor__grid{
  display:grid;
  grid-template-columns:repeat(2,minmax(0,1fr));
  gap:.3rem .7rem;
}

.rl-editor__actions{
  display:flex;
  gap:.4rem;
  align-items:center;
  padding-top:.5rem;
  margin-top:.25rem;
  border-top:1px solid var(--line-light);
}

/* The panel has to hold two columns of role names now. 224px was the width of a select and a button. */
.user-mgmt-actions-panel:has(.rl-editor){
  min-width:19rem;
}

/* ---- /Admin/Roles: what a role does ---- */
.rl-rule{
  border:1px solid var(--brass-soft);
  background:var(--parchment-2);
  border-radius:8px;
  padding:.6rem .75rem;
  font-size:.82rem;
  color:var(--slate);
  margin:0 0 1rem;
  line-height:1.5;
}

.rl-used{
  display:flex;
  flex-wrap:wrap;
  gap:.25rem .4rem;
  margin-top:.2rem;
}

.rl-use{
  font-size:.74rem;
  color:var(--slate-soft);
  border:1px solid var(--line-light);
  border-radius:4px;
  padding:.05rem .38rem;
  background:var(--parchment-2);
}

/* A use that lives in src/ rather than in a row: it cannot be cleared from any screen, so it is
   drawn differently from one that can. */
.rl-use--gate{
  border-style:dashed;
}

.rl-use--idle{
  background:transparent;
  font-style:italic;
}

.rl-why{
  font-size:.78rem;
  color:var(--claret);
  margin:.3rem 0 0;
  max-width:52ch;
  line-height:1.45;
}

.rl-orphan{
  margin-top:1.25rem;
  border-top:1px solid var(--line-light);
  padding-top:.9rem;
}

.rl-orphan h3{
  font-family:var(--serif);
  font-size:.95rem;
  margin:0 0 .3rem;
  color:var(--heading);
}


/* =====================================================================
   ITEM 42 SLICE 1 -- THE CONVERSATION SCREEN. MOBILE FIRST.

   The base rules below ARE the phone and the one media query at the end
   widens them for a desk. That is /Admin/FestiveBoard/Tonight's order and
   its stated reason, and it is the first time on this site the order has
   been available rather than retrofitted: backlog §44 measured 21 of 39
   admin pages carrying a control under the 44px floor, 145 instances,
   IDENTICAL at 320 / 390 / 440 -- no page in the admin area has a phone
   breakpoint at all, because every one of them was drawn for a desk and
   narrowed afterwards. This screen is new surface, so it is drawn the
   other way round.

   Guarded by ConversationPhonePlaywrightTests, whose nine claims were all
   watched fail before a rule here was written.
   ===================================================================== */

/* THE SHELL, AND `dvh` IS THE WHOLE OBLIGATION (plan §8.1 obligation 1).

   THIS FILE HAS CAREFULLY LEARNED `svh` AND THIS RULE WANTS THE OPPOSITE ONE.
   The note above .home-widget--mode-window says it: "on a phone `100vh` is the
   tallest the viewport ever gets", and it settles on `svh`, the smallest,
   BECAUSE A BOARD CARD MUST ALWAYS FIT. A chat pane has the opposite
   requirement. Sized in `svh` it leaves a permanent strip of dead space with
   the keyboard down; sized in `vh` the composer sits UNDERNEATH the keyboard
   with it up. `dvh` tracks the viewport as it actually is, which is the one
   this screen needs and the one unit this stylesheet did not use.

   Less the masthead, which is sticky and 75px: a shell of a whole viewport
   under a sticky bar is a viewport and a bit, and the overflow is exactly the
   composer. */
/* ==================================================================================================
   ITEM 47 — TWO PANES AT THE DESK (gate answers Q1(a), Q2(a), Q3(a), Q4(a)).

   THE PHONE COMES OUT EXACTLY AS IT WENT IN, and that is the constraint that made this a real piece of
   work rather than a media query. Everything below `.cnv-desk` is `display:contents` until 1024px, which
   means the wrapper is not in the layout at all on a phone: the hub is the list, a thread is the thread,
   and neither has a box round it that was not there before. The rail is `display:none` on the pages whose
   own content is not the list.

   1024px AND A 340px RAIL, WHICH IS MEASURED AND NOT CHOSEN. At 1280x900 the hub is 1,132px of full-width
   rows with the timestamp thrown to the far right, and the thread is a different page with a 784px pane.
   Teams is a rail of about 320 to 360 with the thread beside it. At 900px a 340 rail would leave 560 for
   the thread -- LESS than the 784 it has today -- so the thread would get worse on a small laptop in
   order to gain a rail. 1024 is the first width where both panes beat what either page has now.

   THE SHELL IS THE ROW AND THE PANES ARE ITS COLUMNS. Item 42 slice 3 recorded why the hub could not be
   a shell: claim 5 -- the pane scrolls and the document does not -- is unsatisfiable on a list as long
   as its contents, and a `dvh` shell cannot share a document with one. That is answered by making the
   hub a shell too, which is what `.cnv-desk` is inside: both columns scroll independently inside one
   `100dvh - masthead` box, and the document does not scroll at all.
   ================================================================================================== */
/* THE TWO COLUMNS SHARE A HEAD HEIGHT AND A GUTTER, AND THAT IS THE WHOLE OF THE SECOND PASS.

   The first draft gave the rail its own head and left the thread's band at the height a phone gave it
   (the phone's band padding, 14px over 12). The two came out 68 and 96 tall, one light
   and one near-black, with a hairline under the left and a 3px brass rule under the right. Two
   horizontals 28px apart across a vertical divide, and two grounds meeting at a seam that is not a line.

   THE USER'S WORDS WERE "none of the lines match up, and it gives me headache to look at", and that is
   the correct reading of it. What a desk layout of this shape owes is one head ROW: same height both
   sides, one ground, one rule running unbroken from the window's left edge to its right. These two
   tokens are how the two columns agree about it without either knowing the other's markup. */
.cnv-desk{
  display:contents;

  --cnv-head-h:68px;
  --cnv-gutter:1rem;
}

/* BOTH WRAPPERS ARE display:contents ON A PHONE, AND FORGETTING THE SECOND ONE COST A BROWSER RUN.

   THE SELECTOR THE PARAGRAPH BELOW IS ABOUT IS DELIBERATELY NOT SPELLED IN IT. TheThreadPaneIsSizedInDvh
   finds a rule by searching the stylesheet TEXT for its selector and reading the next brace pair, so a
   comment naming the conversation shell above a different rule hands that guard the wrong block -- which
   it did, and which is the fifth time a guard in this corpus has read prose as code.

   The conversation shell is a flex column whose second item -- `.cnv-body` -- carries `flex:1 1 auto;
   min-height:0`,
   and that pair is the entire reason the messages scroll inside the pane instead of laying the DOCUMENT
   out. Item 42 slice 3 wrote that down; item 42's own §1 was raised when it was not true.

   With only the outer wrapper flattened, the pane stayed an ordinary BLOCK between the shell and its
   contents -- so `flex:1 1 auto` applied to nothing and `min-height:0` guarded nothing. The net read
   1,836px of document below the fold at 390px, the composer's bottom edge at 2507 on a 508px viewport,
   and the pane refusing to move when pressed 4,000px. Four claims red on one missing line, in both
   engines, and not one of them visible at a desk. */
.cnv-desk__pane{
  display:contents;
}

.cnv-desk__rail{
  display:none;
}

/* THE HUB IS THE ONE PAGE WHOSE OWN CONTENT IS THE LIST, so its rail is visible at every width and is
   simply the page below 1024. Every other page in the section hides the rail on a phone and shows its
   own pane, which is what "the phone comes out exactly as it went in" means in CSS. */
.cnv-desk--list .cnv-desk__rail{
  display:flex;
  flex-direction:column;
  min-height:0;
}

@media (min-width:1024px){
  .cnv-desk{
    display:flex;
    align-items:stretch;
    /* min-height:0 for the conversation shell's own recorded reason, one level in: without it the messages lay the
       ROW out and the pane cannot scroll. */
    min-height:0;
    flex:1 1 auto;

    /* THE ROW IS THE dvh BOX, and that is what lets four pages share this layout when only one of them
       was a shell. The thread already sits inside that shell, where this is the same height it
       already had; the group page and the two pickers are ordinary documents and get it from here.
       Below 1024 the rule does not apply at all, so none of them gains a fixed-height box on a phone. */
    height:calc(100dvh - var(--masthead-h));
  }

  .cnv-desk__rail{
    display:flex;
    flex-direction:column;
    flex:0 0 340px;
    min-height:0;
    border-right:1px solid var(--line-light);
    background:var(--surface);
  }

  .cnv-desk__pane{
    display:flex;
    flex-direction:column;
    flex:1 1 auto;

    /* THE PANE SCROLLS AND THE DOCUMENT DOES NOT -- claim 5, now on both columns. The thread's own
       `.cnv-body` scrolls inside this and never overflows it, so the two do not fight; the group page
       and the pickers are ordinary content and this is what holds them. */
    overflow-y:auto;
    /* THE ONE THAT IS NOT OBVIOUS. A flex ITEM's default min-width is `auto`, which is its content --
       so a long unbroken word in a message would push this column wider than the row and take the rail
       off the screen with it. The pane is allowed to be narrower than its contents; the contents wrap. */
    min-width:0;
    min-height:0;
  }
}

.cnv-rail{
  display:flex;
  flex-direction:column;
  min-height:0;
  overflow:hidden;
}

/* DESK-ONLY. On a phone the hub draws its own band and its own full-width brass entrances above the
   list, which is item 42 slice 3's measured arrangement and stays exactly as it is. At 340px there is no
   room for either, so the head carries them instead. */
.cnv-rail__head{
  display:none;
  align-items:center;
  gap:.5rem;
  height:var(--cnv-head-h);
  padding-inline:var(--cnv-gutter);
  border-bottom:1px solid var(--line-light);
  flex:0 0 auto;
  box-sizing:border-box;
}

/* (4) THERE WAS NO WAY OUT. At the desk the section fills the window, and the rail's heading was plain
   text — so the only route back to the members' area was the masthead, which is about the SITE rather
   than about where you are. The heading is the way out, which is the phone's own "< Messages" idiom one
   level up. */
.cnv-rail__title{
  font-family:var(--serif);
  font-size:1.05rem;
  color:var(--heading);
  flex:1 1 auto;
  text-decoration:none;
  display:inline-flex;
  align-items:center;
  gap:.35rem;
  min-height:44px;
}

.cnv-rail__title:hover,
.cnv-rail__title:focus-visible{
  color:var(--brass);
}

/* ---------------------------------------------------------------------------------------------------------
   `--muted` DID NOT EXIST, AND FOUR RULES BELOW PAINTED WITH IT.

   The stylesheet's own comment at the photo-viewer mockup names where it came from: "The mockup hardcodes
   its palette (--chrome, --on-dark, --brass-soft, --muted, ...)". The token travelled out of that mockup
   into the conversation family and was never defined anywhere -- not in `:root`, not in `fonts.css`, and not
   in anything the theme system emits (`ThemeSwatchRoles` has no such role).

   `color:var(--muted)` with no fallback is INVALID AT COMPUTED-VALUE TIME, so the declaration does not fail
   loudly: `color` falls back to `inherit`. In the conversation band that inherited a near-white, and the
   thread's two eyebrows -- the phone's only way out, and "3 brethren in this group" -- rendered
   #F6F2E9 on #FFFFFF: measured 1.13:1. The user reported it as illegible, which is generous.

   They are `--slate-soft` now, which is what every other quiet line on a light surface here uses.
   `UndefinedCustomPropertyTests` is the guard, and it is the same shape item 42 slice 4 needed when a field
   had no box because two tokens did not exist.
   --------------------------------------------------------------------------------------------------------- */
.cnv-rail__title-arrow{
  color:var(--slate-soft);
  font-size:1.1rem;
  line-height:1;
}

/* 36px and not 44. These two sit in a 340px rail's head beside a title, they are the SECOND way to reach
   both doors -- the pane beside them carries the full-width brass entrances -- and item 44 has not yet
   said what a control in a desk-only chrome owes. Named here so the exception is a decision. */
/* (5) TWO GLYPH BUTTONS, AND NOBODY CAN READ A GLYPH. `+` and an empty square asked a Secretary to
   guess which door was which, on a screen whose whole job is two doors. They are words now. 36px and
   not 44 because they sit in a 340px rail's head beside a heading, and the pane's own full-width brass
   entrances are the primary route to both — named here so the exception stays a decision item 44 gets
   to overturn. */
.cnv-rail__new{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  padding:0 .6rem;
  height:36px;
  flex:none;
  border-radius:8px;
  border:1px solid var(--line-light);
  background:var(--card);
  color:var(--heading);
  text-decoration:none;
  font-size:.8rem;
  font-weight:600;
  line-height:1;
  white-space:nowrap;
}

.cnv-rail__new:hover,
.cnv-rail__new:focus-visible{
  border-color:var(--brass);
}

/* Desk-only, for the head's reason: the hub's own empty state is the page that DESCRIBES the feature on
   the morning it deploys, and that is the right thing on a phone. */
.cnv-rail__empty{
  display:none;
  margin:0;
  padding:1rem;
  color:var(--slate-soft);
  font-size:.9rem;
}

/* THE RAIL SCROLLS AND THE DOCUMENT DOES NOT -- claim 5, on the half of the screen item 42 slice 3 said
   could not have it. The list inside is the HUB's list, class for class (see _ConversationRail.cshtml);
   all this modifier does is let it scroll inside the rail instead of growing it.

   `overflow-y:auto` makes `overflow-x` compute to `auto` too -- item 42 slice 1's finding -- so the row's
   own ellipsis rules are what stop a sideways scrollbar appearing in a 340px column. */
.cnv-hub--rail{
  overflow-y:auto;
  flex:1 1 auto;
  min-height:0;
}

/* WHICH ONE HE IS READING. A wash rather than a border, because a border would move the row's text by a
   pixel as he moves between conversations -- the list would flicker under his hand. aria-current carries
   the same fact to a reader who cannot see the wash. */
/* A WASH AND A BRASS EDGE. The wash alone bled to the window's left edge and read as a tint rather than
   as a selection; the edge is what says "this one", and it lands exactly where the rail's own border
   would be if the row had one. */
.cnv-hub__row--open{
  background:color-mix(in srgb, var(--brass) 10%, transparent);
  box-shadow:inset 3px 0 0 var(--brass);
}

/* THE PANE WITH NOTHING IN IT, which the hub shows at the desk and nowhere else. Gate answer Q1(a)'s
   consequence said out loud: putting the rail on all three pages means the hub has a right-hand column,
   and something has to be in it. */
.cnv-desk__invite{
  display:none;
}

@media (min-width:1024px){
  .cnv-desk__invite{
    display:flex;
    flex-direction:column;
    align-items:center;
    justify-content:center;
    gap:.75rem;
    flex:1 1 auto;
    min-height:0;
    padding:2rem;
    text-align:center;
    color:var(--slate-soft);
  }

  .cnv-rail__head{display:flex}
  .cnv-rail__empty{display:block}

  /* THE HUB'S RIGHT-HAND COLUMN HAS NO BAND, so the head row's rule ran from the window's left edge to
     the divider and stopped dead — the same broken horizontal the thread had, on the one page where
     there is nothing to put in the other half. An empty strip of the shared height continues the line
     to the right edge, which is all that was missing: the rule is the thing the eye follows, and a rule
     that stops halfway is worse than no rule at all. */
  .cnv-desk--list .cnv-desk__pane::before{
    content:"";
    flex:0 0 auto;
    height:var(--cnv-head-h);
    border-bottom:1px solid var(--line-light);
  }

  /* ============================================================================================
     WHAT THE FIRST SCREENSHOT OF THIS LAYOUT FOUND, AND EVERY CLAIM IN THE SUITE WAS GREEN.
     Four faults, none of them visible to a geometry assertion, all of them obvious to a reader.
     ============================================================================================ */

  /* (1) THE ROW HAD NO GUTTER. On the hub the list sits inside `.wrap`, which is where its horizontal
     breathing room comes from; the rail has no `.wrap`, so the avatar was flush to the left edge of the
     screen and the date flush to the right. The padding belongs on the row, not on a wrapper the rail
     does not have. */
  .cnv-hub--rail .cnv-hub__open{
    padding-inline:var(--cnv-gutter);
  }

  /* (2) THE MESSAGES SAT AT THE TOP OF A FULL-HEIGHT PANE, so a short conversation was two bubbles under
     the band and then eight hundred pixels of nothing above the composer. A thread reads upwards from
     where you are typing: the newest line belongs just above the box you answer it in.

     `margin-top:auto` on the first child rather than `justify-content:flex-end` on the scroller, which
     is the same picture and makes content unreachable above the scroll origin in some engines. */
  .cnv-desk__pane .cnv-thread{
    display:flex;
    flex-direction:column;
  }

  .cnv-desk__pane .cnv-thread > :first-child{
    margin-top:auto;
  }

  /* (3) THE BAND IS THE OTHER HALF OF ONE HEAD ROW.

     It was a near-black 96px block with a 3px brass rule, beside a 68px light strip with a hairline —
     and `.wrap` centred "Admin" in the middle of the pane, nowhere near either column's edge. At the
     desk it becomes the rail head's twin: the same height, the same ground, the same hairline, and its
     title on the same baseline as the heading to its left. The rule then reads as ONE line across the
     window, which is the thing that was missing.

     A PHONE SEES NONE OF THIS. Below 1024 the band is the dark full-bleed masthead it has always been,
     which is what ConversationPhonePlaywrightTests measures at four widths in two engines. */
  /* `min-height`, NOT `height`, AND THE GROUP PAGE IS WHY.

     This band was authored for the THREAD, which puts a back link and one short title in it, and 68px is
     exactly right for that. /Members/Messages/Group puts FOUR things in the same band -- an eyebrow, the
     group's name, "N brethren in this group" and a brass button -- and a fixed height with
     `align-items:center` does not clip that, it CENTRES it: the content spilled out of BOTH edges, so the
     eyebrow rendered at y=35 with the band's own top at 75, behind the sticky masthead, and the title
     landed 7.6px underneath it. Reported from the live site as a title needing CSS work.

     THE THREAD KEEPS ITS FIXED HEIGHT AND ONLY THE ORDINARY DOCUMENTS GROW, which is why this is two
     rules rather than one word changed. A first cut simply made `height` into `min-height` here; that
     also grew the THREAD's band from 68px to 72.3px, because the chat shell's own band rule sets
     `padding:14px 0 12px` at a higher specificity and a fixed height had been clamping it. Four pixels
     off the message pane of every thread on the site is not a thing to trade for a fix to a different
     screen -- and the comment on that cut claimed the screen was unchanged to the pixel, which measuring
     it refuted.

     NO COMMENT IN THIS BLOCK NAMES THE SHELL'S SELECTOR, AND THAT IS NOT STYLE. TheThreadPaneIsSizedInDvh
     locates that rule by searching this file's TEXT for the selector and reading the next brace pair, so
     the first literal occurrence anywhere -- prose, or another rule's `:not()` -- is the block it reads.
     Two drafts of this change tripped it: one put the selector in a `:not(:has(...))` above, and the one
     after that QUOTED that selector while explaining the mistake. The rule above this comment already
     carries the same warning for the same reason. */
  .cnv-desk__pane > .page-hero{
    display:flex;
    align-items:center;
    min-height:var(--cnv-head-h);
    padding:.5rem 0;
    background:var(--surface);
    border-bottom:1px solid var(--line-light);
    box-sizing:border-box;
  }

  /* THE THREAD, AND IT IS NAMED POSITIVELY RATHER THAN BY WHAT IT IS NOT.

     The first cut spelled the other three pages as "body that does not contain a chat shell", which put
     that selector's text into this file EARLIER than the shell's own rule -- and the dvh guard finds that
     rule by searching the file text and reading the next brace pair. It read this block instead, found no
     `dvh` in it, and went red. `.cnv-desk--thread` is on the thread's own wrapper and names it outright,
     so nothing here needs to mention the shell at all. */
  .cnv-desk--thread .cnv-desk__pane > .page-hero{
    height:var(--cnv-head-h);
    min-height:0;
    padding:0;
  }

  /* THE TITLE SITS ON THE SAME LEFT EDGE AS EVERY OTHER LINE IN THE PANE, and the first attempt at this
     did the opposite: it pushed the band hard against the pane's edge with `max-width:none`, so the
     title started at 355 while the unread rule, the bubbles and the composer all started at 418. Sixty
     pixels of disagreement, on the one screen where four horizontal lines are stacked down the page.

     The measure is `.cnv-body`'s, restated rather than inherited because the band is the pane's sibling
     and not its child. 52rem and 24px are that rule's own numbers — the thread's 784px reading measure,
     which is where the item's own booking got that figure.

     THE THREAD ONLY, AND IT IS SCOPED BY A MODIFIER RATHER THAN BY THE SHELL IT SITS IN. The group
     page and the two pickers put `.wrap` round their content as well as their band, so both already
     agree; giving them this measure would be taking two things that match and making them differ.
     The first draft scoped it with the shell's own class name, which put that name earlier in the file
     than the rule that defines it — and TheThreadPaneIsSizedInDvh finds a rule by searching the
     stylesheet TEXT, so it read this one instead and reported the thread as unsized. Second time in
     one item; the cure is that no selector and no comment up here spells that class at all. */
  .cnv-desk--thread .cnv-desk__pane > .page-hero > .wrap{
    width:100%;
    max-width:52rem;
    margin-inline:auto;
    padding-inline:24px;
  }

  /* The title matches the rail's heading rather than shouting over it — they are two labels on one row,
     not a heading and a subheading. */
  .cnv-desk__pane > .page-hero h1{
    font-size:1.15rem;
    line-height:1.2;
    margin:0;
    color:var(--heading);
  }

  .cnv-desk__pane > .page-hero .eyebrow,
  .cnv-desk__pane > .page-hero .eyebrow a{
    color:var(--slate-soft);
  }

  /* TWO BACK LINKS 300px APART SAYING THE SAME WORD. The band's "‹ Messages" was the phone's way out and
     the rail's heading is the desk's; keeping both put the same instruction twice on one row. The
     thread's is marked in the markup rather than found by position, because the group page's SECOND
     eyebrow — how many brethren are in the room — is worth keeping and sits in the same place. */
  .cnv-desk__pane .cnv-back-row{
    display:none;
  }

  /* THE HUB'S PHONE CHROME STANDS DOWN AT THE DESK. Its band, its two full-width entrances, its long
     empty state and its footnote are all things a 340px rail has no room for and the head above replaces
     — and none of them is hidden below 1024, so the phone is untouched by every rule in this block. */
  .cnv-desk--list .page-hero,
  .cnv-desk--list .cnv-hub__new,
  .cnv-desk--list .cnv-hub__empty,
  .cnv-desk--list .cnv-foot{
    display:none;
  }

  /* The hub's list has the rail's gutter at the desk, not .wrap's. */
  .cnv-desk--list .page-body,
  .cnv-desk--list .page-body > .wrap{
    padding:0;
    margin:0;
    max-width:none;
    display:contents;
  }
}

.cnv-shell{
  display:flex;
  flex-direction:column;
  height:calc(100dvh - var(--masthead-h));
  /* min-height:0 is what actually permits the pane inside to scroll instead of
     growing. Without it the messages lay the SHELL out and push the composer
     off the bottom -- which no geometry claim sees on a short thread, and
     every one of them sees on a real one. */
  min-height:0;
}

/* THE BAND IS THE SHELL'S FIRST ROW, and the first run of the guard is why.

   With the band as a SIBLING above a shell sized `100dvh - masthead`, the page
   was a whole viewport BELOW a 60-odd pixel strip: the document overflowed by
   exactly the band and took the composer with it. The guard read the document
   scrolling 939px and the composer's bottom edge at 602 on a 508px viewport --
   three claims red on one cause, none of which a desk browser would ever have
   shown. */
/* ITEM 47 ADDED THE SECOND SELECTOR, and forgetting it would have reproduced the exact failure the
   paragraph above describes. The thread's band is now a child of `.cnv-desk__pane` rather than of the
   shell, so `.cnv-shell > .page-hero` stopped matching it — and an unpinned band inside a flex column
   is a band that shrinks, which is how it was found. */
.cnv-shell > .page-hero,
.cnv-desk__pane > .page-hero{
  flex:0 0 auto;
}

/* Everything that is not the band, and the one place this screen's side gutter
   is spelled. 16px is what .wrap itself drops to below 560px, so it is the
   site's own number rather than a new one.

   It is HERE and not on .cnv-shell because the band is deliberately full-bleed
   -- it carries its own .wrap -- and padding on the shell would inset the dark
   strip by 16px on a page where nothing else is inset. */
.cnv-body{
  flex:1 1 auto;
  min-height:0;
  display:flex;
  flex-direction:column;
  padding-inline:16px;
}

/* THE DOCUMENT DOES NOT SCROLL, ONLY THE PANE DOES (§8.2 claim 5).

   :has() rather than a class the page model sets, because the condition IS
   "this page is a thread screen" and the markup already says so. Thirty-one
   other rules in this file are written the same way.

   The site footer is below the shell and is taller than the screen, so
   without this the document scrolls, and a document that scrolls takes the
   composer with it while somebody is typing into it. */
body:has(.cnv-shell){
  /* `hidden` CLIPS BUT STAYS PROGRAMMATICALLY SCROLLABLE, AND AN iPHONE IS WHAT
     PROVED IT. A reader tapped the message box and the page scrolled up to show
     the FOOTER. `overflow:hidden` takes away the user's scrolling mechanism and
     leaves the scrollable overflow region intact -- so anything that calls
     scrollIntoView still moves the document, and bringing a focused caret into
     view is exactly what iOS does when the keyboard comes up.

     Measured in WebKit at 390x844 before the repair: the thread document was
     1,623px in an 844px viewport, and window.scrollTo(0, 5000) moved it 778.

     `clip` is the value that means what this rule intended: no scrolling
     mechanism AND no programmatic scroll. Declared after `hidden` so an engine
     that does not know it keeps the old behaviour rather than none.

     KEPT AND STATED AS UNGUARDED TODAY. A mutant putting it back to `hidden`
     alone SURVIVES claim 47, because the other half of the repair took the 779px
     away and a document with nowhere to scroll to does not scroll wherever its
     overflow is hidden or clipped. What this declaration buys is the day
     something else lands below the fold on a shell screen -- a section, a
     notice, a banner -- when `hidden` would let a phone scroll to it again and
     `clip` will not. Claim 47's second assertion is the one that would catch
     that, and this is the rule that makes it hold. */
  overflow:hidden;
  overflow:clip;
}

/* AND THE 779 PIXELS IT WAS SCROLLING TO.

   A `dvh` shell is a screen that is exactly one screen: the pane inside it
   scrolls and the document does not. The site footer sits after @RenderBody, so
   every thread screen carried 779px of address, links and copyright BELOW a
   design whose whole premise is that there is nothing below. It was unreachable
   by intent and reachable by accident, which is the worst of both.

   Deleting it costs a reader nothing he could get to, and it is the half of the
   repair that holds even if something else one day scrolls the document: there
   is no longer anywhere to scroll TO. Claim 47 asserts both halves.

   Scoped by :has(.cnv-shell) so it reaches all three thread screens -- the
   member's, the lodge's and the enquirer's tokenised one -- and no others. */
body:has(.cnv-shell) .site-foot{
  display:none;
}

/* The band on this page is a strip and not a shop window -- the thread needs
   the screen. Tonight's page made exactly this trade when it gave up its
   bespoke header for the shared band.

   SLICE 2 WIDENED THESE THREE FROM `.page-hero--admin` TO `.page-hero`, and
   that is a measurement rather than tidying: the enquirer's screen carries the
   PUBLIC band, which is `padding:64px 0 56px` -- a hundred and twenty pixels of
   a 508px viewport spent on a title, before a single message. Written for the
   admin variant alone the rule would have left the one thread screen that only
   ever appears on a phone with the tallest header on the site. */
body:has(.cnv-shell) .page-hero{
  padding:14px 0 12px;
}

body:has(.cnv-shell) .page-hero h1{
  font-size:1.15rem;
  line-height:1.2;
}

/* The public band's eyebrow, which the admin one does not have: at its shipped
   size it is a second line of chrome above the title on a screen that is short
   of room. */
body:has(.cnv-shell) .page-hero .eyebrow{
  font-size:.62rem;
  margin-bottom:.1rem;
}

body:has(.cnv-shell) .page-hero .page-hero__back-link{
  /* The floor, on the one control every thread screen has. */
  min-height:44px;
  display:inline-flex;
  align-items:center;
}

.cnv-statusrow{
  flex:0 0 auto;
  display:flex;
  align-items:center;
  flex-wrap:wrap;
  gap:.5rem .75rem;
  padding:.5rem 0;
  border-bottom:1px solid var(--line-light);
}

.cnv-statusrow__who{
  font-size:.8rem;
  color:var(--slate-soft);
  /* min-width:0 so a long address wraps instead of pushing the row sideways.
     An enquirer's address is the one string on this page nobody controls. */
  min-width:0;
  overflow-wrap:anywhere;
}

.cnv-statusrow__act{
  margin-left:auto;
}

.cnv-pill{
  display:inline-block;
  padding:.15rem .55rem;
  border-radius:999px;
  font-size:.72rem;
  font-weight:600;
  letter-spacing:.03em;
  text-transform:uppercase;
}

.cnv-pill--open{
  background:rgba(63,107,74,.14);
  color:var(--ok-ink, #2f5438);
}

.cnv-pill--closed{
  background:var(--line-light);
  color:var(--slate);
}

/* ITEM 42 SLICE 2 -- WHAT THE LODGE IS TOLD ABOUT THE REPLY LINK.

   A second row and not three more things in .cnv-statusrow, which already
   carries a pill, an address and an action and wraps to two lines at 320px
   with what it has. The sentence here is long by nature -- a date and an
   "opened" time -- so it takes the row and the action sits at its end.

   It is above the thread rather than below it because it is state and not a
   message; the composer is the only thing between the reader and the bottom of
   the screen, which is obligation 1. */
.cnv-linkrow{
  flex:0 0 auto;
  display:flex;
  align-items:center;
  flex-wrap:wrap;
  gap:.4rem .75rem;
  padding:.45rem 0;
  border-bottom:1px solid var(--line-light);
}

.cnv-linkrow__say{
  font-size:.78rem;
  color:var(--slate-soft);
  /* The same reason .cnv-statusrow__who has it: a date and a name are short,
     and this row must still wrap rather than push the page sideways at 320. */
  min-width:0;
  overflow-wrap:anywhere;
}

/* THE SCROLLER. The only one on the page. */
.cnv-thread{
  flex:1 1 auto;
  min-height:0;
  overflow-y:auto;
  /* A flick that reaches the end of the messages must not then scroll whatever
     is behind the page. */
  overscroll-behavior:contain;
  -webkit-overflow-scrolling:touch;
  display:flex;
  flex-direction:column;
  gap:.55rem;
  padding:.85rem 0 .5rem;
}

.cnv-daymark{
  align-self:center;
  font-size:.68rem;
  letter-spacing:.12em;
  text-transform:uppercase;
  color:var(--slate-soft);
  padding:.2rem 0 .1rem;
}

.cnv-msg{
  /* 85% and not 100%: a bubble that fills the line reads as a paragraph and
     the two sides stop being distinguishable at a glance. */
  max-width:85%;
  padding:.55rem .75rem .6rem;
  border-radius:14px;
  font-size:.9rem;
  line-height:1.45;
  display:flex;
  flex-direction:column;
}

/* THE WRAP AND THE BREAK ARE TWO RULES AND BOTH ARE NEEDED (§8.2 claim 6).

   `pre-wrap` keeps the newlines somebody typed, which is the whole difference
   between a message and a paragraph. `overflow-wrap:anywhere` is what breaks
   a token that has no space in it -- a pasted address or URL -- and prose
   alone never exercises it. Item 41 shipped a picker reading
   "14 Sep 2026 - E2E Phone Sweep boa" because CLIPPED CONTENT OVERFLOWS
   NOTHING, SCROLLS NOTHING AND IS UNDER NO TOUCH FLOOR: three green geometry
   claims over a cut-off name, found by a person. */
.cnv-msg__body{
  white-space:pre-wrap;
  overflow-wrap:anywhere;
}

/* MINE AND THEIRS, AND SLICE 2 RENAMED THESE FROM `--lodge` AND `--them`.

   Slice 1 had one thread screen, so "the lodge's side" and "the reader's own
   side" were the same set of bubbles and either name described it. Slice 2
   added the enquirer's screen, where the lodge is the OTHER side -- and a rule
   called `.cnv-msg--lodge` that had to be applied to the enquirer's messages
   would be a name that lied on the page it mattered on. The pair says what the
   rules actually do: right and dark is whoever is reading, left and light is
   whoever they are talking to. Slice 3's members' thread inherits both. */
.cnv-msg--theirs{
  align-self:flex-start;
  background:var(--surface);
  border:1px solid var(--line-light);
  border-bottom-left-radius:5px;
  color:var(--heading);
}

.cnv-msg--mine{
  align-self:flex-end;
  background:var(--claret);
  border-bottom-right-radius:5px;
  color:var(--on-dark);
}

.cnv-msg__author{
  font-size:.68rem;
  font-weight:600;
  letter-spacing:.03em;
  margin-bottom:.15rem;
  opacity:.78;
}

.cnv-msg__meta{
  font-size:.65rem;
  letter-spacing:.04em;
  opacity:.62;
  margin-top:.3rem;
  font-variant-numeric:tabular-nums;
}

.cnv-msg__edited{
  font-style:italic;
}

/* ============================================================================
   ITEM 46 SLICE 3 -- THE ROUND INITIAL, THE RUN AND THE UNREAD LINE
   (plan docs/chat-prominence-plan.md §3.3, §7.7, gate answer Q8).
   ============================================================================

   THE COLOURS ARE NOT HERE, THEY ARE IN MemberAvatar, AND THAT SPLIT IS
   CardArt's. A design value written only in C# is a stylesheet no designer
   can see, so the size, the radius and the border stay in this file; the
   contrast floor is arithmetic, so the six grounds have to be in C# where
   ContrastMath can be pointed at them. MemberAvatarStylesheetTests is the
   guard across the split, and it refuses a tone with no rule and a rule
   painting a colour the palette does not declare.

   THE BORDER IS NOT DECORATION. Every ground holds #FFFFFF at 5.33:1 or
   better -- that is the number the letters are read by -- but against the
   eleven seeded --surface values the worst GROUND-on-surface pair is 2.62:1,
   on Evergreen's raised green. So on the three dark palettes the circle's
   edge would be soft without this line. It is .rr-mentor__av's own remedy,
   arrived at the same way. */
.cnv-av{
  flex:none;
  width:32px;
  height:32px;
  border-radius:50%;
  display:inline-flex;
  align-items:center;
  justify-content:center;
  font-size:.72rem;
  font-weight:700;
  letter-spacing:.02em;
  color:#FFFFFF;
  border:1px solid var(--line-light);
  /* A name is free text, so two initials can be two wide glyphs. */
  overflow:hidden;
}

/* The hub's is larger because a list row has the width for it and a row is
   scanned rather than read. */
.cnv-av--row{
  width:38px;
  height:38px;
  font-size:.8rem;
  margin-top:.15rem;
}

/* THE SIX GROUNDS. MemberAvatar.Tones, in order, and the guard holds these
   two lists to each other -- a seventh tone with no rule here would paint a
   circle with no ground at all, which is white letters on whatever the page
   is. */
.cnv-av--0{background:#D40B25}
.cnv-av--1{background:#A85308}
.cnv-av--2{background:#497506}
.cnv-av--3{background:#067A44}
.cnv-av--4{background:#0A6CC2}
.cnv-av--5{background:#951EFC}

/* THE LINE A MESSAGE SITS ON, WHICH IS A NEW BOX AND NOT A CHANGE TO .cnv-msg.

   Three screens wear .cnv-msg -- /Admin/Enquiries/Thread, /Conversation and
   the member's own thread -- and only the third draws avatars. Touching the
   bubble's own align-self or max-width would move the other two, which is
   how a slice about the members' area comes to be measured on the lodge's.
   So the bubble is unchanged and it gains a parent.

   `max-width:85%` MOVES HERE and the bubble is released to 100% of it: the
   percentage has to be taken against the PANE, and a bubble inside a
   shrink-to-fit row taking 85% of its own parent is circular. The specificity
   is 0,2,0 against .cnv-msg's 0,1,0, so the child selector wins without
   !important. */
.cnv-line{
  display:flex;
  align-items:flex-start;
  gap:.4rem;
  max-width:85%;
  min-width:0;
  /* `position:relative` IS LOAD-BEARING AND A CLAIM WENT RED WITHOUT IT, WITH
     A NUMBER NOBODY WOULD HAVE GUESSED: 1,499px OF DOCUMENT BELOW THE FOLD.

     A tucked bubble keeps its author label for a screen reader and hides it
     with .admin-visually-hidden, which is `position:absolute` over a 1x1 box.
     An absolutely positioned box whose ancestors are all static resolves
     against the INITIAL containing block -- the document -- so each hidden
     label was placed at its static position measured from the page origin
     rather than from inside .cnv-thread's scroller, and seventeen of them
     stretched the document to the height of the whole conversation. This file
     already records the rule, two hundred lines up, on the members' portal's
     own hidden heading: an absolutely positioned box is clipped by an
     `overflow` ancestor only when that ancestor is also its containing block.

     ON .cnv-line AND NOT ON .cnv-msg: three screens wear the bubble and only
     this one has hidden labels in it. */
  position:relative;
}

.cnv-line--theirs{align-self:flex-start}
.cnv-line--mine{align-self:flex-end}

.cnv-line > .cnv-msg{
  max-width:100%;
  min-width:0;
}

/* A BUBBLE INSIDE A RUN IS PADDED, NOT GIVEN AN EMPTY CIRCLE. An invisible
   34px span would be a box a screen reader walks past and a box this
   stylesheet would then have to keep the same width as a real one; a padding
   is one declaration that cannot drift from the thing it is aligning to,
   because it is composed from the same two numbers. */
.cnv-line--tucked{
  padding-left:calc(32px + .4rem);
}

/* The reader's own side has no circle, so a tucked bubble of his needs no
   room made for one. */
.cnv-line--mine.cnv-line--tucked{
  padding-left:0;
}

/* THE UNREAD BOUNDARY (§2.6 measurement 4). A line ACROSS the stream and not
   a chip in the middle of it: what it marks is a place, and a place is a
   horizontal.

   --bad-ink and not --claret or --brass: the status pairs are the one family
   on this site that is provably readable on all eleven palettes without being
   themed, which is exactly what a mark that must be findable on any of them
   needs. See the pairs' own note at the top of this file. */
.cnv-newline{
  display:flex;
  align-items:center;
  gap:.5rem;
  margin:.2rem 0;
  color:var(--bad-ink);
  font-size:.68rem;
  font-weight:700;
  letter-spacing:.12em;
  text-transform:uppercase;
}

.cnv-newline::before,
.cnv-newline::after{
  content:"";
  flex:1 1 auto;
  height:1px;
  background:var(--bad-line);
}

.cnv-newline__say{
  flex:0 0 auto;
  white-space:nowrap;
}

/* THE COMPOSER, AND `env(safe-area-inset-bottom)` IS THE SECOND OBLIGATION.

   `safe-area-inset` appeared 0 times anywhere in src/ before this line -- not
   in the CSS, not in a view, not in a script -- because nothing on this site
   had ever docked anything to the bottom of a screen. Without it the send
   button sits underneath the home indicator on every iPhone made since 2017,
   and it is INVISIBLE in every desktop browser and every non-notched
   simulator, which is why ConversationPhonePlaywrightTests asserts it at the
   source rather than by measuring a page.

   max() and not a bare env(): on a device with no inset the function resolves
   to 0px, and the composer would then have no bottom padding at all. */
.cnv-composer{
  flex:0 0 auto;
  display:flex;
  align-items:flex-end;
  gap:.5rem;
  padding-top:.55rem;
  padding-bottom:max(.55rem, env(safe-area-inset-bottom));
  border-top:1px solid var(--line-light);
}

.cnv-composer__field{
  flex:1;
  /* ITEM 46 SLICE 4 -- THE BOX GROWS WITH THE MESSAGE, AND THIS DECLARATION IS
     THE HALF THAT ONLY ONE ENGINE HONOURS (plan §3.4, §7.9, gate answer Q11).

     §7.9 SAID TO TAKE THE WEBKIT READING BEFORE ANYTHING RELIED ON IT AND IT
     WENT THE BAD WAY. Measured in Playwright 1.54's WebKit 26.0 -- the engine
     an iPhone runs -- at 390x844, over these exact declarations:

       CSS.supports('field-sizing','content')  false
       getComputedStyle(el).fieldSizing        not a property at all
       1 / 3 / 6 / 40 lines typed              46.0px, 46.0, 46.0, 46.0
       what the content wanted at 40 lines     862px

     818 pixels of a man's message out of sight inside a one-line pill. Chromium
     139 over the same markup: 46.0, 67.6, 89.2, 153.9, then 337.6 -- which is
     40dvh at 844 exactly, the cap below reached for the first time on this site.

     SO THE SCRIPT IS THE MECHANISM AND THIS IS THE PROGRESSIVE HALF. site.js
     grows the box on every input, which reads 46.0 / 66.0 / 87.0 / 150.0 / 337.6
     in WebKit and within 0.9px of the declaration in Chromium -- one code path
     both engines take, rather than a branch only one of them ever runs.

     KEPT, AND GUARDED BY THE ONE CLAIM THAT CAN SEE IT. With script the inline
     height wins and this declaration is unobservable, so it is what a reader
     with no JavaScript gets -- growth in Chromium, today's one-line pill in
     WebKit -- and TheComposerStillGrowsWithNoScriptAtAll is the only reading in
     the file that would go red if it were deleted. This is the select-at-21px
     lesson: a rule measured in one engine is a rule measured in one engine. */
  field-sizing:content;
  /* THERE IS DELIBERATELY NO min-width:0 HERE, AND THE FIRST DRAFT HAD ONE.
     The reasoning was the usual one -- a flex item will not shrink below its
     intrinsic width -- and it was measured wrong: a <textarea> defaults to
     cols=20, about 200px at this font size and padding, and the narrowest
     line it has to fit into is 288px less the 44px send button and its gap.
     It never binds. The mutation pass proved it: removing the declaration
     left all twenty-two claims green in both engines.
     A redundant rule a mutant cannot kill is a rule the next reader has to
     work out is redundant, and claim 1 is what actually protects this. */
  min-height:44px;
  /* AND WHAT HAPPENS AT THE CAP IS THE TEXTAREA'S OWN BEHAVIOUR AND NEEDS NO
     DECLARATION. The first draft wrote `overflow-y:auto` here to say it out
     loud; a <textarea> already scrolls, so the rule could be deleted with every
     claim in two engines green -- which is this stylesheet's own standard for
     taking a rule out rather than leaving the next reader to work out that it
     decides nothing. Measured at the cap: 337.6px of box with 526px of message
     still reachable inside it. */
  max-height:40dvh;
  resize:none;
  border:1px solid var(--line-light);
  border-radius:22px;
  background:var(--surface);
  color:var(--heading);
  padding:.7rem 1rem;
  /* 1rem and NOT smaller. iOS Safari zooms the whole page when a field under
     16px takes focus, and the zoom is not undone when it blurs -- the reader
     is left on a page they now have to pan sideways. */
  font-size:1rem;
  font-family:inherit;
  line-height:1.35;
}

.cnv-composer__send{
  flex:0 0 44px;
  width:44px;
  height:44px;
  border-radius:50%;
  border:0;
  background:var(--brass);
  color:var(--ink);
  font-size:1.05rem;
  font-weight:700;
  font-family:inherit;
  cursor:pointer;
  display:grid;
  place-items:center;
}

.cnv-foot{
  flex:0 0 auto;
  margin:.4rem 0 0;
  padding-bottom:max(.4rem, env(safe-area-inset-bottom));
  font-size:.72rem;
  line-height:1.4;
  color:var(--slate-soft);
}

.cnv-foot--warn{
  color:var(--claret);
}

/* The list page's controls, raised to the floor. §44 measured six of them
   here: two .admin-enquiry__action at 20px (the class carries padding:0), the
   items-per-page <select> at 31, and the Previous/Next pair at 38. */
.cnv-act{
  min-height:44px;
  display:inline-flex;
  align-items:center;
  justify-content:center;
  gap:.35rem;
}

/* A SCREENSHOT FOUND THIS. .cnv-act is worn by two different things -- an
   anchor that is also a .btn, and a bare <button> -- and the bare one inherited
   the browser's own chrome: "Close enquiry" rendered as a grey box with a black
   border, in the middle of a page that has no other boxed control on it. Every
   claim in the guard was green, because a default button is exactly 44px tall
   and inside the viewport.

   Scoped to `button` rather than written onto .cnv-act, which would strip the
   brass fill off the "Open the thread" link beside it. The look is
   .admin-enquiry__action's, which is the same action one page away. */
button.cnv-act{
  background:none;
  border:0;
  padding:0;
  font:inherit;
  font-size:.85rem;
  font-weight:600;
  color:var(--brass-ink);
  cursor:pointer;
  text-decoration:underline;
  text-underline-offset:3px;
}

button.cnv-act:hover{
  color:var(--heading);
}

.admin-enquiry__actions .admin-enquiry__action{
  min-height:44px;
}

/* ITEM 48 SLICE 1 — THE TWO NEW CONTROLS, RAISED TO THE SAME FLOOR THIS PAGE
   ALREADY KEEPS.

   .cnv-act and .admin-enquiry__action above were both raised by §44's
   measurement of this page, and the controls this slice adds would otherwise
   have shipped BELOW a floor the page next to them already meets:
   .admin-btn-delete carries `padding:.25rem .7rem` at `font-size:.78rem`,
   which measures about 23px tall.

   That is worth doing here rather than leaving to item 44, and the reason is
   not consistency: THE SHORTEST CONTROL ON THIS PAGE WOULD HAVE BEEN THE ONE
   THAT CANNOT BE UNDONE. A 23px "Delete permanently" sitting beside a 44px
   "put it back", on a workspace with no backups, puts the smaller target on
   the destructive press and the larger one on the safe press — which is the
   levers backwards.

   SCOPED, NOT SITE-WIDE. .admin-btn-delete is worn on eight other admin
   screens and item 44's Q2 — floor at every width, or only below 640px — is
   still unanswered. Raising the class itself would answer that question here,
   by accident, for the whole site. */
/* ITEM 64 SLICE 4 ADDED .admin-btn HERE, AND WITH THE LEVERS THE OTHER WAY ROUND FROM THE NOTE ABOVE.
   /Admin/Requests draws its card actions in .admin-enquiry__actions too: "Decline and tell them" wears
   admin-btn-delete and is therefore already 44px, while "Admit" — the SAFE press, on the same row — sat
   at about 30px. Same fault as the one this block was written for, mirrored: the bigger target was on
   the press that closes somebody's account.

   Still scoped, still not on the class. Item 44's Q2 — floor at every width, or only below 640px — is
   as unanswered now as it was then, and .admin-btn is worn in three different toolbars. */
.admin-enquiry__actions .admin-btn,
.admin-enquiry__actions .admin-btn-delete,
.enq-purge .admin-btn-delete,
.enq-views__link{
  min-height:44px;
  display:inline-flex;
  align-items:center;
  justify-content:center;
}

/* The door to the other list sits at the left of the toolbar, opposite the
   items-per-page picker. No width of its own: .history-toolbar is already
   `space-between` with `flex-wrap`, so the two find their own ends and stack
   when the line runs out. */
.enq-views{
  display:flex;
  align-items:center;
  gap:.5rem;
}

/* ABOVE THE LIST, AND DIAGONALLY OPPOSITE THE WAY OUT.

   A SCREENSHOT MOVED THIS. Left-aligned under the toolbar it landed directly
   beneath "Back to enquiries" — two pills of similar width, about 15px apart,
   and on a 390px phone that is a thumb's width between "leave this list" and
   "destroy everything on it". Both presses were correct, both were above the
   touch floor, and every claim in the net was green.

   Right-aligned it sits opposite the back button on the wide screen and at the
   far edge on a phone, so the safe press and the irreversible one are never
   the two nearest things to the thumb. The confirm dialog is the second line
   of defence, not the first: a dialog an officer has learned to dismiss is not
   a defence at all. */
.enq-purge{
  display:flex;
  justify-content:flex-end;
  margin-block:1.4rem 1.2rem;
}

/* ITEM 48 SLICE 2 — the door's own count (gate answer Q11).
   Quiet on purpose. It is a note about what did NOT happen, sitting above a
   list of what did, and a boxed panel here would compete with the enquiries
   for the eye every time the page loads. The caveat sits on its own line
   rather than in brackets, because "this resets on a restart" is the sentence
   that stops the number being read as all-time. */
.enq-refusals{
  color:var(--slate-soft);
  font-size:.86rem;
  line-height:1.65;
  margin:0 0 1.2rem;
  padding-left:.9rem;
  border-left:2px solid var(--line-light);
}

.enq-refusals strong{
  color:var(--slate);
  font-variant-numeric:tabular-nums;
}

.enq-refusals__note{
  display:block;
  font-size:.79rem;
  font-style:italic;
}

/* The safe press, given the weight the words deserve: brass fill against the
   claret outline of the one that cannot be undone. Height matched to
   .admin-btn-delete beside it so the pair reads as a pair. */
.enq-restore{
  min-height:44px;
  display:inline-flex;
  align-items:center;
  justify-content:center;
}

.history-pager .btn,
.history-pager .history-pager__disabled{
  min-height:44px;
  display:inline-flex;
  align-items:center;
  justify-content:center;
}

/* THE PICKER TAKES THE WHOLE LINE AND ITS LABEL SITS ABOVE IT.

   Item 41's second iPhone report, applied before a complaint rather than
   after one. A <select> beside its label on a 320px phone either pushes the
   page sideways or clips the option text, and in WEBKIT it does the first
   with NO BOX ANYWHERE TO MEASURE -- a menulist propagates the width of its
   longest option to the document, so a guard that looks for boxes over the
   edge sees nothing at all.

   `height` and not `min-height`, which is the other half of that report:
   WEBKIT IGNORES min-height ON A <select> and lays a menulist out from its
   own metrics. Every control item 41 "raised to 44px" rendered at 21. */
.cnv-size-form{
  display:flex;
  flex-direction:column;
  align-items:stretch;
  gap:.25rem;
  min-width:0;
}

.cnv-select{
  width:100%;
  min-width:0;
  height:44px;
  font-size:1rem;
  font-family:inherit;
}

/* ================================================================
   ITEM 42 SLICE 3 -- THE HUB, AND Q10's EDIT. MOBILE FIRST.

   The list is an ORDINARY page and the thread is the shell, which is the
   split slice 1 made next door: a `dvh` shell cannot share a document
   with a list that is as long as its contents.

   Guarded by ConversationPhonePlaywrightTests, which measures
   /Members/Messages and /Members/Messages/Thread at 320/390/430/440 in
   two engines.
   ================================================================ */

.cnv-hub{
  list-style:none;
  margin:0;
  padding:0;
  border-top:1px solid var(--line-light);
}

.cnv-hub__row{
  border-bottom:1px solid var(--line-light);
}

/* THE WHOLE ROW IS THE CONTROL, and that is obligation 3 taken seriously
   rather than satisfied. A "View" link at the end of a row clears 44px and
   is still a target the width of two words; a row a thumb can land anywhere
   on is the one genuinely easy press on this page.

   `min-height:44px` WAS HERE AND IS DELETED, because a mutant showed it does
   nothing: every row is a name line and a preview line, the preview is never
   empty (IndexModel.PreviewOf writes a sentence for a thread nobody has
   written in), and two lines at these sizes clear the floor on their own --
   with the padding removed as well. It was protecting a one-line row that
   cannot occur.

   The floor is still MEASURED: .cnv-hub__open is in claim 3's selector list,
   which a mutant also had to ask for, and forcing this row to 30px turns that
   claim red on all four widths. */
/* ITEM 46 SLICE 3 -- THE GRID MOVED INTO .cnv-hub__body AND THIS BECAME A ROW.
   The row is now a circle and everything else, so the two-column grid the
   name, the stamp, the preview and the badge are placed in has to belong to
   the thing that holds all four. Moving it rather than nesting a second grid
   keeps every `grid-column` below pointing at the box it was written for. */
.cnv-hub__open{
  display:flex;
  align-items:flex-start;
  gap:.6rem;
  padding:.7rem 0;
  color:inherit;
  text-decoration:none;
}

.cnv-hub__body{
  flex:1 1 auto;
  min-width:0;
  display:grid;
  grid-template-columns:1fr auto;
  gap:.15rem .6rem;
}

.cnv-hub__open:hover,
.cnv-hub__open:focus-visible{
  background:color-mix(in srgb, var(--brass) 7%, transparent);
}

.cnv-hub__top{
  grid-column:1 / -1;
  display:flex;
  align-items:baseline;
  gap:.5rem;
  min-width:0;
}

/* THE NAME WRAPS AND IS NEVER CLIPPED, AND FOUR DELETED DECLARATIONS ARE WHY.

   The first draft had `min-width:0; overflow:hidden; text-overflow:ellipsis;
   white-space:nowrap` here, on the reasoning that a long display name would
   push the row sideways at 320px. FOUR MUTANTS SAY OTHERWISE: deleted
   together or one at a time, with a thirty-five character name in the
   fixture, every claim in ConversationPhonePlaywrightTests stays green. They
   were not protecting the page from anything -- without them the name simply
   WRAPS, which overflows nothing.

   So what they actually did was clip a brother's name mid-word to keep the
   row one line tall. That is item 41's own follow-up, which booked exactly
   that as a defect: "clipped content overflows nothing, scrolls nothing and
   is under no touch floor", so no geometry claim can ever see it. Two lines
   on a phone is not a problem; half a name is.

   `overflow-wrap:anywhere` STAYS, and it is deliberately not paired with
   `min-width:0` the way .cnv-statusrow__who is: `anywhere` (unlike
   `break-word`) reduces the min-content size itself, so the pair would be two
   spellings of one effect. IT IS UNGUARDED AND THAT IS STATED RATHER THAN
   HIDDEN -- what provokes it is a display name that is one unbroken
   forty-character run, and seeding the fixture with a name no brother has is
   the way a fixture starts lying about the page. */
.cnv-hub__who{
  font-weight:600;
  color:var(--heading);
  overflow-wrap:anywhere;
}

.cnv-hub__when{
  margin-left:auto;
  flex:0 0 auto;
  font-size:.75rem;
  color:var(--slate-soft);
}

/* ONE LINE, CLIPPED, and the clip is deliberate where the thread's own
   bubbles must never clip (claim 6). A preview is a hint about a
   conversation and the conversation is one press away; a MESSAGE is the
   thing itself, and item 41's second follow-up is what a clipped one costs. */
.cnv-hub__say{
  grid-column:1;
  min-width:0;
  font-size:.85rem;
  line-height:1.35;
  color:var(--slate);
  overflow:hidden;
  text-overflow:ellipsis;
  white-space:nowrap;
}

/* --on-brass AND NOT --ink, AND THIS IS THE THIRD BRASS PILL OF THREE.
   Slice 1 shipped .cnv-portal__badge as #fff on --brass -- 2.42:1 -- and slice
   2 found it while adding the masthead's, rather than by a sweep, because the
   crawler never loads a portal with a badge showing: it is `hidden` at zero.
   THIS ONE WAS LEFT BEHIND by that repair, wearing --ink, which is 7.82:1 on
   the default palette and an UNFLOORED PAIR OF THEME TOKENS on the other ten --
   the exact hazard this file's own header warns about ("two theme tokens paired
   is not safe; measure, do not reason"). --on-brass is derived by
   ThemeStyleComposer with a measured 4.5:1 floor on all eleven, and it is what
   the two badges beside it already use. Found by item 46 slice 3, the same way:
   by drawing a fourth thing in the same row. */
.cnv-hub__badge{
  grid-column:2;
  align-self:center;
  flex:0 0 auto;
  padding:.15rem .5rem;
  border-radius:999px;
  background:var(--brass);
  color:var(--on-brass);
  font-size:.7rem;
  font-weight:700;
  white-space:nowrap;
}

.cnv-hub__empty{
  display:grid;
  gap:.5rem;
  justify-items:start;
  padding:1.1rem 0;
}

.cnv-hub__empty b{
  font-family:var(--serif);
  font-size:1.05rem;
  color:var(--heading);
}

.cnv-hub__empty span{
  font-size:.9rem;
  line-height:1.5;
  color:var(--slate);
}

/* ================================================================
   ITEM 42 SLICE 4 -- Q7's PICKER (/Members/Messages/New) and the
   entrance to it on the hub.

   MOBILE FIRST, like the two pages above. The base rules ARE the
   phone; the one media query at the bottom of this file widens the
   search row for a desk.

   Guarded by ConversationPhonePlaywrightTests, which loads this page
   WITH a query already run, at 320/390/430/440 in two engines --
   an empty picker is a field and a sentence, and every geometric
   claim passes over it while measuring nothing anybody looks at.
   ================================================================ */

/* THE ENTRANCE. A block press on a phone rather than something tucked
   beside the heading: it is the only control on the hub that is not a
   conversation, and the reader looking for it is looking for it.

   KEPT DELIBERATELY AND STATED AS UNGUARDED, which is §41's answer for
   `.fb-add-form__select` and this slice's second such rule. A mutant
   reducing this to a bare inline button survives the whole net, and it
   should: `.btn` is 48px either way, so the touch floor holds, and an
   inline button does not overflow a 320px phone. What the block buys is
   that the one control on the page which is not a conversation is not a
   two-word target beside a heading. Raising a claim until this rule was
   load-bearing would be fitting the number to the page. */
.cnv-hub__new{
  display:block;
  width:100%;
  margin:0 0 1rem;
  text-align:center;
}

/* ITEM 42 SLICE 6 -- THE OFFER, on the thread screen, above the composer.

   It is .cnv-statusrow's shape rather than a new one: the same two-part row
   with the sentence on the left and the press on the right, and the same
   reason -- a brother meets both of them in the same place on the same screen,
   and two spellings of one row is the drift this item keeps paying to merge.
   What it does NOT share is the ground, because this row is asking for
   something and the status row is reporting.

   IT COSTS THE MESSAGE PANE NOTHING WHILE IT IS HIDDEN, which is why it is
   `hidden` rather than moved off screen: claim 21 measures the pane as a
   fraction of a short phone, and permanent chrome above the messages is what
   that claim exists to stop.

   AND `display:flex` HERE IS ONLY SAFE BECAUSE BOOTSTRAP'S REBOOT SAYS
   [hidden]{display:none!important} ON EVERY PAGE -- see .cnv-portal__badge,
   where the measurement is written out. An author `display` beats the UA's
   [hidden] rule by ORIGIN, so without an important somewhere this bar would be
   on every thread screen on the site, always, saying somebody had written.
   Claim 38 asks for its absence before it asks for its arrival, which is the
   claim that would catch it however it came back. */
.cnv-newbar{
  display:flex;
  gap:.6rem;
  align-items:center;
  justify-content:space-between;
  flex-wrap:wrap;
  padding:.35rem .9rem;
  background:color-mix(in srgb, var(--brass) 12%, transparent);
  border-top:1px solid var(--line-light);
}

.cnv-newbar__say{
  /* The sentence takes what is left and wraps rather than pushing the press
     off a 320px phone -- .cnv-statusrow__who's rule, and its reason: a name
     and a date are short until somebody's are not. */
  flex:1 1 auto;
  min-width:0;
  font-size:.82rem;
  color:var(--slate);
}

.cnv-pick{
  display:grid;
  gap:.4rem;
  margin:0 0 1rem;
}

/* THE FIRST VISIBLE .cnv-label ON THIS SITE, AND THE SWEEP WENT RED ON IT.

   `label.cnv-label` has been in claim 3's selector list since slice 3, and
   it had never measured anything: every other use of the class is also
   `admin-visually-hidden`, which has no rendered box, so the entry was
   inert. This page gave it a body and the claim turned red at all four
   widths in both engines -- which is what that entry was put there for.

   IT MEETS THE FLOOR RATHER THAN LEAVING THE SELECTOR. A <label for> IS a
   press: tapping it focuses the field. Moving this to a class the sweep
   does not name would have been fitting the guard to the page, which is the
   thing this project refuses everywhere else.

   `align-items:end` is what stops 44px looking like a mistake -- the text
   sits on the bottom of its box, directly above the field it names, and the
   height reads as the space a label is given rather than as a gap. */
/* SLICE 5 WIDENED THE SELECTOR AND THE GUARD IS WHAT ASKED FOR IT. Written
   as `.cnv-pick .cnv-label` this floor belonged to the picker's search box,
   so the group creation page's "What is the group called?" -- the same class,
   the same shape, a different parent -- rendered at 27px and claim 3 went red
   at all four widths in both engines.

   `:not(.admin-visually-hidden)` IS THE RULE'S ACTUAL SUBJECT, which the
   paragraph above was already describing: every other .cnv-label on this site
   is visually hidden and has no rendered box, so the floor is about the
   VISIBLE ones and always was. Scoped by parent it would have to be widened
   again by whoever adds the next visible label, and the way that gets noticed
   is a red build somebody has to diagnose. */
.cnv-label:not(.admin-visually-hidden){
  display:flex;
  align-items:end;
  min-height:44px;
  padding-bottom:.15rem;
  font-weight:600;
}

/* THE FIELD AND THE BUTTON STACK ON A PHONE. Side by side at 320 the
   field is about 180px, which is a name and a half, and every phone
   raises a keyboard over the bottom half of the screen anyway -- so
   the width is worth more than the row. They go side by side at the
   desk width, at the bottom of this file. */
.cnv-pick__row{
  display:grid;
  gap:.4rem;
}

/* `min-width:0` WAS HERE AND A MUTANT DELETED IT WITH EVERY CLAIM GREEN,
   SO IT IS GONE -- and unlike §41's `.fbr-picker{min-width:0}`, this one
   really is inert rather than unprovoked. An <input>'s intrinsic minimum
   comes from its `size` attribute, which defaults to 20 characters and is
   about 177px; the column it sits in is 288px at the narrowest phone this
   net measures. Nothing an author types into the field changes that, so
   there is no fixture that could make the rule bind. Measured: the field
   is 288px wide at 320 with the rule and 288px without it.

   The 44px floor below is a different matter -- deleting THAT turns claim
   3 red at all four widths in both engines. */
/* THE TOKENS ARE `--line-light`, `--surface` AND `--heading`, AND THE
   FIRST DRAFT INVENTED THREE THAT DO NOT EXIST.

   It said `border:1px solid var(--line)` and `background:var(--paper)`,
   and neither token is declared anywhere on this site. An undefined
   custom property makes the declaration it is in INVALID AT COMPUTED
   VALUE TIME -- so the background fell to transparent and, because the
   border was written as the SHORTHAND, the whole border went with it:
   computed `0px none`. The field had no box at all. On a cream page it
   read as a line of body text with a placeholder in it.

   NOT ONE CLAIM IN THIS ITEM'S NET COULD SEE THAT, and the list of why
   is the point: nothing overflowed, nothing scrolled, the gutter held,
   and the field was still exactly 44px tall because `min-height` is a
   different declaration and survived. A SCREENSHOT found it, which is
   the third time in item 42 that has happened.

   The composer next door has carried the right three tokens since
   slice 1; this is now the same field in a different shape. Claim 26
   asks for the box, so the next invented token is a red build. */
.cnv-pick__field{
  width:100%;
  min-height:44px;
  padding:.55rem .7rem;
  border:1px solid var(--line-light);
  border-radius:.35rem;
  background:var(--surface);
  color:var(--heading);
  font:inherit;

  /* 1rem AND NOT SMALLER, which is .cnv-composer__field's note word for
     word. iOS Safari zooms the whole page when a field under 16px takes
     focus and does not undo the zoom when it blurs, leaving the reader
     on a page he has to pan. Invisible in every desktop browser and in
     Chromium's phone emulation alike. */
  font-size:1rem;
}

.cnv-pick__field:focus{
  outline:none;
  border-color:var(--brass);
}

/* NO `min-height` HERE, AND A MUTANT IS WHY. Deleting one from this rule
   changed nothing: the button is a `.btn`, which is 48px on its own, so a
   44px floor written here was a second answer to a question `.btn` had
   already answered -- and a weaker one. Claim 3 still measures it, by
   .cnv-pick__go being in the selector list. */
.cnv-pick__go{
  width:100%;
}

.cnv-pick__note,
.cnv-pick__said{
  margin:0 0 1rem;
  font-size:.85rem;
  line-height:1.5;
  color:var(--slate);
}

.cnv-pick__list{
  list-style:none;
  margin:0;
  padding:0;
  border-top:1px solid var(--line-light);
}

.cnv-pick__row-item{
  border-bottom:1px solid var(--line-light);
}

.cnv-pick__row-item form{
  margin:0;
}

/* THE WHOLE ROW IS THE PRESS, which is .cnv-hub__open's reasoning one
   page along: obligation 3 wants a target a thumb can land anywhere on,
   not a "Write" link the width of one word at the end.

   A <button> AND NOT A LINK, because it WRITES -- the first press opens
   a conversation. So it is reset out of its own appearance: a button
   inherits a centred, bordered, background-filled box that would look
   nothing like the hub row it is meant to match.

   A COLUMN, AND A MUTANT'S SURVIVOR IS WHAT PUT IT THAT WAY. This was a
   flex ROW -- name on the left, "Write to him" on the right, the name
   given `overflow:hidden` and `text-overflow:ellipsis` to fit. Deleting
   `min-width:0` from the name survived, and running that survivor to
   ground measured what the row was actually doing at 320: the name's
   content was 245px inside a 199px box. It was ELLIPSISING every long
   name on the site's one page where a name is the whole content, and
   the two brethren this item's own fixture holds --
   Wadsworth-Ellery and Wadsworth-Brigg -- truncate to the same string.

   That is §41's clipped-picker finding arriving on a new page, and it
   had the same property: clipped content overflows nothing, scrolls
   nothing and is under no touch floor, so claims 1, 2, 3 and 7 were all
   green over it. §41's repair was to give the control the whole line and
   assert it; so is this one. Claim 25.

   NO `min-height` EITHER, and that is the hub row's finding word for
   word: two lines and .7rem of padding make this row 50px on its own,
   measured, so a 44px floor written here was protecting a one-line row
   that cannot occur. Claim 3 still MEASURES it -- .cnv-pick__start is in
   the selector list, and forcing this row to 30px turns that claim red
   at all four widths. */
.cnv-pick__start{
  display:grid;
  gap:.15rem;
  width:100%;

  /* THE PADDING IS SPACING AND IS STATED AS UNGUARDED. A mutant deleting
     it survives: two lines of text make this row about 46px on their own,
     so the 44px floor still holds without it and no claim can see the
     difference. It is here because a list of rows a thumb lands on wants
     air between them, which is a thing a reader judges and an instrument
     does not. */
  padding:.7rem 0;
  border:0;
  border-radius:0;
  background:none;
  color:inherit;
  font:inherit;
  text-align:left;
  cursor:pointer;

  /* SLICE 5 PUT AN <a> IN THIS CLASS. The creation page's result rows add a
     man to a list that lives in the URL, so they write nothing and are links
     -- and a link brings an underline and the visited colour with it, which
     a <button> does not. Declared here rather than on a modifier because the
     rule is about what this row LOOKS like, and both tags wear it.

     KEPT AND STATED AS UNGUARDED. A mutant putting the underline back survives
     every claim in the net, and no geometry claim could ever see it -- the row
     is the same box either way. What it prevents is the picker's rows and the
     creation page's rows, the same control doing the same job on two screens a
     brother moves between, looking like two different things. Claim 26's shape
     (ask the COMPUTED value) could cover it; raising a claim for an underline
     would be fitting the net to the rule. */
  text-decoration:none;
}

.cnv-pick__start:hover,
.cnv-pick__start:focus-visible{
  background:color-mix(in srgb, var(--brass) 7%, transparent);
}

/* THE NAME TAKES THE LINE AND WRAPS RATHER THAN ENDING IN AN ELLIPSIS.
   There is deliberately no `overflow`, no `text-overflow` and no
   `white-space` here: on a picker the surname is the entire decision, and
   a row that hides the end of it is asking a brother to choose between
   two men it has just made identical.

   `overflow-wrap` is for the name no space can break -- a double-barrelled
   surname longer than a 288px phone column has to break inside itself or
   it leaves the row, which claim 2 would see and claim 25 would not. */
.cnv-pick__who{
  overflow-wrap:anywhere;
  font-weight:600;
}

.cnv-pick__go-hint{
  color:var(--slate);
  font-size:.8rem;
}

/* Q10's CONTROL, IN THE BUBBLE'S META ROW.

   It sits beside the time rather than floating over the bubble, because a
   control that only appears on hover has no equivalent on a phone at all --
   which is the surface this whole item is drawn for.

   .cnv-act carries the 44px floor; this only says what it looks like. */
.cnv-msg__edit{
  color:inherit;
  font-weight:600;
  text-decoration:underline;
  text-underline-offset:3px;
  /* The meta row is .72rem and the floor is 44px, so an inline-flex control in
     it would push every bubble taller by half a line. The padding buys the
     target without the box: the pressable area overlaps the bubble's own
     bottom padding, which is dead space a thumb lands in anyway. */
  padding:.55rem .35rem;
  margin:-.55rem -.35rem -.55rem .1rem;

  /* AND THE OTHER DIMENSION, WHICH A SCREENSHOT AND A MEASUREMENT FOUND AND THE
     GUARD DID NOT. The word "Edit" made this 33px WIDE by 44 tall, and the
     sweep's TooSmallScript asks `r.height < floor` -- so a control a third
     narrower than the floor cleared it perfectly. WCAG 2.5.5 is 44 BY 44.
     Claim 3 asks both dimensions now, which is what makes this rule
     load-bearing rather than tidy. */
  min-width:44px;
  justify-content:center;
}

/* ============================================================================
   ITEM 46 SLICE 4 -- A MESSAGE'S ACTIONS SIT BEHIND A PRESS, AND THE PRESS IS
   THE STAMP THAT WAS ALREADY THERE (plan §3.4, §7.7, gate answer Q8).

   THE MEASUREMENT IS §2.6's SEVENTH: "Edit is a permanent link on every one of
   the reader's own bubbles." On the seeded thread that is the word six times
   down the right-hand side of a 390px screen, on a page whose whole job is to
   be read.

   A PRESS AND NOT A HOVER. §3.4 and §7.7 settle it with the same sentence --
   "on a phone there is no hover" -- and slice 3 refused to hide the time behind
   one for the same reason. A control revealed by something a phone cannot do is
   a control that is gone.

   <details> AND NOT A SCRIPT, for three reasons that are all about what it
   already is. It is focusable and Enter-operable in every engine with no keydown
   handler of ours; it carries its own aria-expanded, which a div with a click
   handler does not; and it works with no script at all, which is this site's
   standing promise and claim 41's subject.

   AND THE HANDLE IS THE STAMP BECAUSE NOTHING ELSE WAS FREE. A "..." control
   would have replaced a permanent word with a permanent glyph and saved the
   reader nothing. The time is on every bubble already -- slice 3 refused to
   hide it, "a time nothing can reveal is a time that is gone" -- so it is the
   one handle on this bubble that costs no pixels at rest. */
.cnv-msg__acts{
  margin-top:.3rem;
}

.cnv-msg__acts > summary{
  /* The stamp keeps .cnv-msg__meta, so the margin it brings would be applied
     twice -- once by the details above and once here. */
  margin-top:0;
  cursor:pointer;
  /* THE DISCLOSURE TRIANGLE, TAKEN OFF IN BOTH OF THE WAYS ENGINES ASK FOR.
     `list-style` is the standard spelling and the -webkit- pseudo-element is
     what older WebKit reads; the `display:inline-flex` .cnv-act brings removes
     it in Chromium regardless. The chevron below replaces it because a marker
     that sits outside the flex box cannot be placed after the text. */
  list-style:none;
}

.cnv-msg__acts > summary::-webkit-details-marker{
  display:none;
}

/* THE PADDING BUYS THE TARGET AND THE NEGATIVE MARGIN GIVES THE HEIGHT BACK,
   which is .cnv-msg__edit's trick two rules down for .cnv-msg__edit's reason:
   the meta row is .65rem and the floor is 44px, so a control that paid for its
   target in layout would make every one of the reader's own bubbles half a line
   taller than everybody else's. */
.cnv-msg__acts > summary.cnv-act{
  padding:.55rem .35rem;
  margin:-.55rem -.35rem;
  /* NO min-width, AND .cnv-msg__edit TWO RULES DOWN HAS ONE, WHICH IS THE
     DIFFERENCE WORTH WRITING DOWN. That rule exists because the word "Edit" made
     a control 33px wide by 44 tall and the sweep only asked about height. This
     one is a stamp -- five tabular digits, a chevron and the gap between them --
     which measures about 55px before the padding at every width the sweep
     drives, so a min-width here would be a declaration that never binds. The
     floor is still asked of it: `summary` is in the sweep's own selector list. */
}

/* THE ONLY THING ON THE BUBBLE THAT SAYS THERE IS SOMETHING BEHIND THE STAMP.
   Drawn out of borders rather than written as a glyph in `content`, because a
   generated "v" is text some screen readers speak: the accessible name here is
   the visually-hidden "Message actions" in the markup, and this is the picture
   of it. */
.cnv-msg__acts > summary::after{
  content:"";
  width:.3rem;
  height:.3rem;
  border-right:1.5px solid currentColor;
  border-bottom:1.5px solid currentColor;
  transform:translateY(-.15em) rotate(45deg);
}

.cnv-msg__acts[open] > summary::after{
  transform:translateY(.05em) rotate(-135deg);
}

/* THE REVEALED LINK IS A ROW UNDER THE STAMP, NOT A WORD BESIDE IT, so
   .cnv-msg__edit's VERTICAL negative margin would pull it back under the very
   summary that revealed it. The horizontal half is kept -- it still must not
   widen the bubble -- and the height is paid in full, because a control a man
   has just asked for is a control he is about to press. */
.cnv-msg__acts > .cnv-msg__edit{
  margin:.15rem -.35rem 0;
}

/* AND THE ONE THAT ACTUALLY CLOSES IT, WHICH THE FIRST BUILD DID NOT HAVE AND
   THE GUARD FOUND AT ALL FOUR WIDTHS: "6 of the 6 edit controls on this page
   are drawn before anybody presses anything."

   A closed <details> hides its contents through a UA rule, and .cnv-act carries
   `display:inline-flex` -- an AUTHOR declaration, which beats the user agent's
   whatever the disclosure is doing. So the markup was a disclosure and the
   screen was exactly the page §2.6 complained about, with a chevron added.

   THIS IS THIS STYLESHEET'S OWN RECORDED TRAP. Item 22 shipped
   `.iv[open]{display:grid}` and watched a CLOSED dialog paint, for the same
   reason in the same direction: a `display` an author wrote outranks the one
   the browser was using to hide the thing. Written as :not([open]) rather than
   by taking display off .cnv-act, which would strip the 44px floor's own
   inline-flex off eleven other controls. */
.cnv-msg__acts:not([open]) > .cnv-msg__edit{
  display:none;
}

.cnv-edit{
  display:grid;
  gap:.4rem;
  width:100%;
}

.cnv-edit__acts{
  display:flex;
  gap:.75rem;
  align-items:center;
}

/* ================================================================
   ITEM 42 SLICE 5 -- GROUPS: making one (/Members/Messages/NewGroup)
   and looking after one (/Members/Messages/Group/{id}).

   MOBILE FIRST, like the four screens above. The base rules ARE the
   phone; the one media query at the bottom of this file widens the
   search rows, which these two pages share with the picker.

   THE TWO PAGES BORROW THE PICKER'S VOCABULARY DELIBERATELY.
   .cnv-pick, .cnv-pick__field, .cnv-pick__row, .cnv-pick__list,
   .cnv-pick__start and .cnv-pick__who are all slice 4's and are all
   reused here unchanged -- a second family of classes for "search the
   roll and press a row" would be two answers to one question, which is
   the drift this project keeps paying to merge. What is below is only
   the things a GROUP has that a pair does not: a name, a list of men
   picked so far, and a membership with presses beside it.
   ================================================================ */

/* THE WAY OFF A THREAD SCREEN, AND IT HAD NO RULE AT ALL UNTIL SLICE 5.

   `.cnv-back` has been in the markup since slice 1 and in no stylesheet: it
   was a marker class on an <a> in the eyebrow, about 18px tall, and it is the
   ONLY way off a screen whose whole design is a fixed shell with no room for a
   second. Claim 3 could not see it because a bare `a` is deliberately not in
   its selector list -- a link inside a message body is prose -- which is the
   same hole .cnv-hub__open and .cnv-pick__start each fell through one slice
   earlier. Slice 5 put a SECOND control in the class (the link into a group's
   people) and that is what made it worth measuring.

   THE PADDING BUYS THE TARGET AND THE NEGATIVE MARGIN GIVES THE HEIGHT BACK,
   which is .cnv-msg__edit's trick for .cnv-msg__edit's reason: the band must
   not grow to buy a target.

   MEASURED, AND THE NUMBER IS WHY IT IS KEPT WHILE BEING UNGUARDED. At
   390x844 -- an ordinary phone with the keyboard down, which is the height a
   man READS at -- taking the negative margin away moves the band from 119.9px
   to 164.7px and the message pane from 462.4 to 417.7: forty-five pixels, on
   every thread screen on the site, 54.8% of the phone down to 49.5%.

   AND A MUTANT SURVIVES IT, FOR A REASON WORTH WRITING DOWN. Claim 21 is the
   only claim that measures the pane and it measures at 390x508, the height
   with the keyboard up -- where the media query at the foot of this file has
   already compressed this band and the eyebrow no longer sets its height. At
   508 the same mutation moves nothing at all: band 56.2 to 56.2, pane 312.3
   to 312.3, measured. So the rule matters exactly where the guard does not
   look, and does nothing where it does. Raising a claim at 844 to catch it
   would be fitting a number to this rule; the cost is stated here instead. */
.cnv-back{
  display:inline-flex;
  align-items:center;
  min-height:44px;
  min-width:44px;
  padding:.7rem .5rem;
  margin:-.7rem -.5rem;
}

/* THE SECOND ENTRANCE ON THE HUB, AND IT IS QUIETER THAN THE FIRST ON
   PURPOSE. Writing to one brother is the commoner press by a long way;
   two brass blocks of equal weight would make the reader choose between
   them every time he opens his messages. It keeps .cnv-hub__new's full
   width and floor -- .btn is 48px -- so claim 3 measures it either way. */
.cnv-hub__new--quiet{
  border:1px solid var(--line-light);
  background:none;
  color:var(--heading);
}

.cnv-hub__new--quiet:hover,
.cnv-hub__new--quiet:focus-visible{
  background:color-mix(in srgb, var(--brass) 10%, transparent);
}

/* THE NAME, THE COUNT AND THE PRESS THAT MAKES THE ROOM. A grid rather
   than a bare stack so the gap is one declaration, which is .cnv-pick's
   shape one page along. */
.cnv-group-make{
  display:grid;
  gap:.4rem;
  margin:0 0 1rem;
}

/* FULL WIDTH ON A PHONE, for .cnv-hub__new's reason: it is the one press
   on this page that finishes it, and the reader looking for it is looking
   for it. It narrows at the desk width with the rest. */
.cnv-group-make__go{
  width:100%;
}

/* THE MEN PICKED SO FAR. A list and not chips: a name is long -- this
   item's own fixture holds a thirty-five character one -- and chips at
   320px would either wrap inside themselves or ellipsise, which on a page
   about choosing between brethren is §41's finding all over again. */
.cnv-group-chosen{
  list-style:none;
  margin:0 0 1rem;
  padding:0;
  border-top:1px solid var(--line-light);
}

.cnv-group-chosen__row{
  display:flex;
  align-items:center;
  gap:.5rem;
  padding:.35rem 0;
  border-bottom:1px solid var(--line-light);
}

/* THE NAME TAKES WHAT IS LEFT AND WRAPS RATHER THAN CLIPPING, which is
   .cnv-pick__who's rule and its reason: on a page about telling two
   brethren apart, a row that hides the end of a name asks a man to choose
   between two people it has just made identical.

   `overflow-wrap:anywhere` IS LOAD-BEARING AND THE FIXTURE HAD TO BE
   STRENGTHENED TO PROVE IT. With two hyphenated surnames in the picked
   list it survived mutation, because a hyphen is a break opportunity;
   MemberNaming.Describe falls back to the E-MAIL ADDRESS for a brother
   with no display name, and that is one unbreakable fifty-character run
   on a 288px column. Seeded, the mutant is red at every width.

   `min-width:0` WAS BESIDE IT AND IS GONE, WHICH IS .cnv-hub__who's OWN
   NOTE APPLIED: `anywhere` (unlike `break-word`) reduces the min-content
   size ITSELF, so the pair is two spellings of one effect. It survived
   mutation with the unbreakable name in the fixture, which is what said
   which of the two is doing the work. */
.cnv-group-chosen__who{
  flex:1;
  overflow-wrap:anywhere;
}

.cnv-group-chosen__drop{
  flex:0 0 auto;
}

/* THE MEMBERSHIP LIST. Same shape as the chosen list above and
   deliberately not the same class: this one carries up to two presses per
   row and a tag, and the day one of them needs a second line the two
   pages should not move together. */
.cnv-group-list{
  list-style:none;
  margin:0 0 1rem;
  padding:0;
  border-top:1px solid var(--line-light);
}

/* WRAPS ON A PHONE, and that is the whole reason this is flex-wrap and not
   a two-column grid. A thirty-five character name beside "Make owner" and
   "Remove" is about 300px of controls on a 288px column; wrapped, the
   presses drop to their own line under the name and every one of them keeps
   its 44px. A grid would have shrunk them instead.

   KEPT DELIBERATELY AND STATED AS UNGUARDED, which is §41's answer for
   `.fb-add-form__select` and this item's fourth such rule. A mutant setting
   this to nowrap survives the whole net and it should: the name is
   `flex:1 1 12rem` with a break rule on it, so it shrinks and wraps inside
   itself rather than pushing anything over the edge, and the presses keep
   their floor either way. What it buys is a row a man can read -- a
   thirty-two character name squeezed into 130px is four lines of two words
   -- which is a thing a reader judges and no instrument in this file
   measures. Raising a claim until this rule was load-bearing would be
   fitting the number to the page. */
.cnv-group-list__row{
  display:flex;
  flex-wrap:wrap;
  align-items:center;
  gap:.35rem .6rem;
  padding:.35rem 0;
  border-bottom:1px solid var(--line-light);
}

/* `overflow-wrap` IS LOAD-BEARING and `min-width:0` was the same second
   spelling deleted from .cnv-group-chosen__who above, for .cnv-hub__who's
   stated reason. Provoked by the brother with no display name, whose name
   is his address: without the break rule that run leaves the row and claim
   2 goes red at every width. */
.cnv-group-list__who{
  flex:1 1 12rem;
  overflow-wrap:anywhere;
  font-weight:600;
}

/* "owner" AND "you". Lower case and small, because they are labels on a
   name rather than headings -- and a badge shape here would compete with
   the unread count on the hub, which is the one pill on this feature that
   means "do something". */
.cnv-group-list__tag{
  margin-left:.4rem;
  padding:.05rem .35rem;
  border:1px solid var(--line-light);
  border-radius:999px;
  font-size:.7rem;
  font-weight:600;
  color:var(--slate);
  white-space:nowrap;
}

.cnv-group-list__acts{
  display:flex;
  flex:0 0 auto;
  gap:.25rem;
}

.cnv-group-list__acts form{
  margin:0;
}

.cnv-group-foot{
  display:grid;
  gap:.4rem;
  justify-items:start;
  margin:1.2rem 0 0;
}

.cnv-group-foot form{
  margin:0;
}

/* THE PRESS INTO THE CONVERSATION, on the group's own band. Full width on
   a phone for .cnv-hub__new's reason. */
.cnv-group__open{
  display:block;
  width:100%;
  margin:.6rem 0 0;
  text-align:center;
}

/* ================================================================
   A SHORT VIEWPORT SPENDS ITS HEIGHT ON THE THREAD (item 42 slice 2).

   MEASURED, and the measurement is the reason this rule exists at all.
   At 390x508 -- the height a phone has left with the keyboard up -- the
   lodge's thread screen was spending 149px on its band and 121px on its
   status row, and THE MESSAGE PANE WAS 22 PIXELS TALL. Twenty-two. An
   officer answering an enquiry could see the composer, the title and the
   contact details, and not one word of what he was replying to.

   Every claim was green over it. Claim 4 asks whether the composer is on
   the screen and it was; claims 1, 2, 3, 6 and 7 are about width; claim 5
   asks whether the pane scrolls and a 22px pane scrolls beautifully.
   Slice 2 added claim 13 -- the pane still shows a whole message -- and
   this block is what satisfies it.

   The band keeps its back link (the only way off this screen) and its
   title, and loses the eyebrow and most of its padding. The link row goes,
   and it is the one thing here that is genuinely hidden rather than
   compressed: it is administrative state about a credential, nobody needs
   it while typing a sentence, and it comes back the moment the keyboard
   does down.

   620px and not 560: 508 is the number the guard uses, a phone in
   landscape is shorter still, and an ordinary phone in portrait is 844.
   The gap between 620 and 844 is wide enough that no real device sits in
   it by accident.
   ================================================================ */
@media (max-height:620px){
  body:has(.cnv-shell) .page-hero{
    padding:6px 0 4px;
  }

  body:has(.cnv-shell) .page-hero .eyebrow{
    display:none;
  }

  body:has(.cnv-shell) .page-hero h1{
    font-size:1rem;
  }

  /* The address and the pill are state the reader already knows by the time
     they are typing.

     THE CLOSE ACTION USED TO STAY HERE, "because it is the one press that ends
     the job they are on", AND SLICE 3's CLAIM 21 MEASURED WHAT IT COST: the
     lodge's thread pane was 203px of 508 -- FORTY PER CENT -- with a 61px row
     above it carrying a pill and one button. Slice 2 called 203px the repair,
     because the claim it wrote (a whole message is visible) was satisfied.

     The row goes whole, on the same reasoning the link row below it already
     used: nobody closes an enquiry WHILE typing the answer to it, and the
     press comes back the moment the keyboard does down -- which is the moment
     he has sent. The pane is 264 of 508 with this rule, and 52%. */
  body:has(.cnv-shell) .cnv-statusrow{
    display:none;
  }

  /* .cnv-statusrow--quiet AND .cnv-statusrow--unread BOTH HAD A `display:none`
     OF THEIR OWN HERE AND BOTH ARE GONE, because the rule above now hides
     every status row on a thread screen and two more selectors saying the same
     thing is the over-determination item 41 paid four mutants to find: rules
     that look load-bearing, survive mutation together, and are individually
     inert. One rule, one reason, one thing to change. */

  .cnv-linkrow{
    display:none;
  }

  /* ITEM 42 SLICE 3, AND BOTH OF THESE WERE FOUND BY A SCREENSHOT OVER A GREEN
     NET -- which is the third time in this item that has happened.

     MEASURED at 390x508 with both of them drawn: the unread row took 61px and
     the edit warning 55, and THE MESSAGE PANE WAS 190 OF 508. Not slice 2's
     twenty-two, but 116px of a short screen spent on two things nobody needs
     while typing a sentence.

     THE UNREAD ROW WAS ALSO NONSENSE AT THIS HEIGHT. The rule above hides
     .cnv-statusrow__who, so "8 messages you have not read" went and left
     "Mark as read" floating alone on a bordered strip -- a control whose
     subject had been hidden from the man being asked to press it. It is
     .cnv-statusrow--quiet's finding exactly, one row along, and it takes the
     same answer: the row goes whole and comes back the moment the keyboard
     does down. Both are hidden by the one .cnv-statusrow rule above.

     THE WARNING is the link row's shape: it is about something he is not doing
     -- it explains what an EDIT cannot reach -- and the control it belongs to
     is on a bubble he has to scroll to. */
  /* KEPT DELIBERATELY AND STATED AS UNGUARDED, which is item 41's answer for
     .fb-add-form__select. A mutant deleting it survives: the pane is 312px of
     508 with this rule and 257 without, and 257 still clears claim 21's floor
     of 45%. What it buys is fifty-five pixels of thread in exchange for a
     paragraph explaining what an EDIT cannot reach, on a screen where the
     reader is typing and the control it describes is on a bubble he has to
     scroll to. Raising the claim's floor until this rule was load-bearing
     would be fitting the number to the page, which is what this project
     refuses everywhere else. */
  .cnv-foot{
    display:none;
  }

  .cnv-thread{
    padding:.4rem 0 .3rem;
  }
}

@media (min-width:700px){
  /* THE ONE QUERY, AND IT ONLY WIDENS. Everything above is the phone.

     The shell keeps its dvh sizing at a desk as well: a thread pane that
     scrolled the document on a wide screen and itself on a narrow one would be
     two behaviours to hold in mind, and the desk version is no worse for it. */
  .cnv-body{
    width:100%;
    max-width:52rem;
    margin-inline:auto;
    padding-inline:24px;
  }

  .cnv-msg{
    max-width:70%;
  }

  .cnv-size-form{
    flex-direction:row;
    align-items:center;
    gap:.5rem;
  }

  .cnv-select{
    width:auto;
  }

  /* ITEM 42 SLICE 4. The search row goes side by side, which is the only
     thing about the picker that wants a desk: at 320 the field would be a
     name and a half wide beside a button, and the field is the page. */
  .cnv-pick__row{
    grid-template-columns:1fr auto;
    align-items:start;
  }

  .cnv-pick__go{
    width:auto;
    padding-inline:1.2rem;
  }

  /* The entrance stops being a full-width block once there is room for it
     to sit as an ordinary button. It keeps its floor either way -- the
     height is .btn's and claim 3 measures .cnv-pick__go and the row. */
  .cnv-hub__new{
    display:inline-block;
    width:auto;
  }

  /* ITEM 42 SLICE 5. The two presses that are full-width blocks on a phone
     stop being blocks at the desk width, which is .cnv-hub__new's rule
     directly above and the same argument: the block exists because a thumb
     wants the whole line, and a mouse does not. */
  .cnv-group-make__go,
  .cnv-group__open{
    display:inline-block;
    width:auto;
  }
}


/* =====================================================================
   ITEM 53 SLICE 2 -- /Members/Account, a member's own account
   (docs/member-account-plan.md §5.1).

   SIX PANELS ON ONE PAGE, and .admin-panel has no stacking rule of its
   own: every admin screen that uses it renders ONE. Giving the class a
   margin would have changed thirty other pages to space six sections on
   this one, so the gap is on the stack instead.

   THE 44px FLOOR IS DELIBERATE AND IT IS AHEAD OF ITS ITEM. The masthead
   control this page's work sits beside measures 83x43 -- one pixel under
   the floor item 44 exists to put under every control on the site -- and
   .btn's 12px padding lands in the same place. Item 44 is the next item
   in the queue and it will measure this page; shipping a new page a
   pixel short, knowing the number, would be shipping a known fault to
   save four lines.

   Scoped to .acct-stack rather than written as a site-wide .btn rule,
   because raising every button on the site is item 44's call to make
   across eleven palettes and two engines, not this slice's to smuggle in.
 ===================================================================== */
.acct-stack{
  display:grid;
  gap:1.25rem;
}

/* The two forms in the "You" panel are one subject and two failures, so
   they are separated by a rule rather than by a heading -- a second <h2>
   would claim they are two subjects. */
.acct-rule{
  border:0;
  border-top:1px solid var(--line-light);
  margin:1.5rem 0;
}

/* A form inside a panel that already has a lead paragraph above it. The
   gap is .form-block's; this only stops the first label sitting on the
   prose. */
.acct-form{
  margin-top:.75rem;
}

/* THE FIXED STATUS TRIPLE, ALL THREE OF IT. site.css's header: the
   --ok/--warn/--bad triples are identical on all eleven palettes on
   purpose, and taking two of the three while the third comes from the
   theme is the pairing StylesheetPairingTests refuses. */
.acct-flag{
  display:inline-block;
  padding:.05rem .45rem;
  border-radius:999px;
  font-size:.8rem;
  font-weight:600;
  color:var(--warn-ink);
  background:var(--warn-fill);
  border:1px solid var(--warn-line);
}

/* A CHECKBOX IS 16.8px AND ITS LABEL IS THE CONTROL. The box itself is
   1.05rem by .pref-toggle's own rule and raising it would redesign a
   control this page copied verbatim from the card it absorbed; what a
   finger actually presses is the <label>, because a press anywhere on it
   toggles the box. So the floor goes on the label -- measured on the
   phone by the E2E net, which found these at 16.8px and was right to. */
.acct-stack .pref-toggle{
  min-height:44px;
}

/* Sign out, and sign out everywhere else. They WRAP rather than shrink:
   at 320px the second label is four words and a button that shortened to
   fit would be a control whose meaning depends on the screen. */
.acct-actions{
  display:flex;
  flex-wrap:wrap;
  align-items:center;
  gap:.75rem;
}

.acct-stack .btn,
.acct-stack .form-field input{
  min-height:44px;
}

/* ITEM 55 DELETED THIS PAGE'S SCOPED BOX RULE, WHICH IS WHAT IT WAS FOR.

   A bare .btn has a transparent border and no background, so on a light
   panel it is a control with no box -- found by the screenshot pass on
   this page with every assertion green, and fixed here alone because
   inventing a secondary-on-light site-wide from inside an account slice
   would have restyled eight pages nobody asked about.

   The site-wide answer is now `.btn-line`, and the four controls on this
   page carry it like every other. The rule that used to sit here said in
   its own words that it was "not the site's fix"; leaving it behind the
   real one would be two answers to one question, which is the failure
   this project has recorded more often than any other.

   The 44px floor above stays: it is about this page's geometry, it
   covers the form inputs too, and .btn-line carries its own. */


/* ==================================================================================================
   ITEM 53 SLICE 4 — /Admin/COPY, THE WORDING LEDGER (migration 097).

   Fifty rows on one screen, so the design job here is scanning rather than styling: a lodge arrives
   looking for one sentence, and the two things that have to be instant are which group it is in and
   whether anybody has already changed it.
   ================================================================================================== */

.cpy-notice{
  margin:0 0 1.25rem;
  padding:.75rem 1rem;
  border-left:3px solid var(--brass);
  background:var(--parchment-2);
  color:var(--slate);
  font-size:.92rem;
}

.cpy-intro{
  margin:0 0 1.5rem;
}

.cpy-group{
  margin-bottom:1.5rem;
  /* The contents strip jumps here and the masthead is sticky, so without this the heading lands underneath
     it. 74px is --masthead-h's arithmetic, which is the same sentence the home board's snap margin makes. */
  scroll-margin-top:90px;
}

/* ==================================================================================================
   ITEM 60 — THE LEDGER'S GROUPS ARE THE HUB'S GROUPS, and the classes are shared rather than copied:
   `.cpy-group` carries `.adm-group` too, so the chevron, the summary's grid and — the part that
   matters — the 900px query that forces every drawer OPEN at a desk all come from one place. Item 54
   half A wrote that treatment and a probe proved which declaration actually opens a closed <details>
   in both engines today; writing it twice would be two answers to one question.

   WHAT IS HERE IS ONLY WHAT THE TWO PAGES DO NOT SHARE: the hub's body is one `.adm-cards` grid and
   this page's body is N `.cpy-row` forms with no wrapper, so the open/closed pair has to name them.
   ================================================================================================== */

/* Closed is the shipped state below 900px, and this is the half an older engine obeys — the hub's
   comment, and its ordering rule with it: the 900px override must stay BELOW this. */
.cpy-group > .cpy-row{display:none}
.cpy-group[open] > .cpy-row{display:block}

/* A JUMP FROM THE CONTENTS STRIP OPENS WHAT IT JUMPS TO. Without this, following one of those links on
   a phone scrolls to a shut heading and nothing else happens, which is a contents strip that does not
   work — and it is CSS rather than script because the strip is seven anchors and needs none. */
.cpy-group:target::details-content{content-visibility:visible}
.cpy-group:target > .cpy-row{display:block}

/* The summary has no eyebrow on this page, so the count takes that grid area — above the group's name
   on a phone, and beside it at a desk. */
summary.adm-group__head .cpy-group__count{
  grid-area:eyebrow;
  justify-self:start;
  font-family:var(--sans);
  font-size:.72rem;
  font-weight:700;
  letter-spacing:.08em;
  text-transform:uppercase;
  color:var(--brass-ink);
}

@media (min-width:900px){
  .cpy-group::details-content{content-visibility:visible}
  .cpy-group > .cpy-row{display:block}

  /* The summary turns into a flex row up here (see the hub's block) and everything in it is placed by
     `order`. Without this the count has order:0 and leads the row. */
  summary.adm-group__head .cpy-group__count{order:2}
}

/* ITEM 53 SLICE 4 - the contents strip. Thirty-nine boxes is ten screens, and this is what stops the
   answer to "where is the footer's address" being a scroll. Anchors, so it needs no script and hides
   nothing - which is the whole reason it is not a set of drawers. */
.cpy-contents{
  display:flex;
  flex-wrap:wrap;
  gap:.5rem;
  margin:0 0 1.5rem;
}

.cpy-contents a{
  display:inline-flex;
  align-items:center;
  gap:.45rem;
  min-height:44px;
  padding:.4rem .85rem;
  border:1px solid var(--line-light);
  border-radius:999px;
  background:var(--surface);
  color:var(--heading);
  text-decoration:none;
  font-size:.85rem;
  font-weight:600;
}

.cpy-contents a:hover,
.cpy-contents a:focus-visible{
  border-color:var(--brass);
}

.cpy-contents a span{
  font-variant-numeric:tabular-nums;
  font-weight:500;
  color:var(--slate-soft);
}

.cpy-row{
  padding:1rem 0;
  border-top:1px solid var(--line-light);
}

.cpy-row:first-of-type{
  border-top:0;
  padding-top:.25rem;
}

/* The one piece of colour on the page, and it is on the LEFT EDGE rather than on the text: a reader
   scrolling fifty rows is looking for "which of these have we touched", and an edge mark answers that
   in the margin without recolouring a sentence they are about to read. */
.cpy-row--edited{
  border-left:3px solid var(--brass);
  padding-left:.85rem;
  margin-left:-.85rem;
}

.cpy-row__head{
  display:flex;
  flex-wrap:wrap;
  align-items:baseline;
  justify-content:space-between;
  gap:.5rem;
  margin-bottom:.4rem;
}

.cpy-row__label{
  font-weight:600;
  color:var(--heading);
  font-size:.95rem;
}

.cpy-row__mark{
  font-size:.78rem;
  color:var(--brass-ink);
  font-weight:600;
}

.cpy-row__mark--shipped{
  color:var(--slate-soft);
  font-weight:500;
}

.cpy-row__text{
  width:100%;
  font-family:var(--sans);
  line-height:1.6;
  resize:vertical;
  /* WebKit has no field-sizing at all — item 46's measurement — so the row count is computed server-side
     from the shipped words and this is the floor under it. */
  min-height:64px;
}

.cpy-row__note{
  margin:.45rem 0 0;
  font-size:.8rem;
  color:var(--slate-soft);
  line-height:1.5;
}

.cpy-row__note span{
  color:var(--slate);
}

.cpy-row__note--template b{
  font-family:var(--mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  color:var(--brass-ink);
}

.cpy-row__act{
  margin-top:.6rem;
}

/* 44 from the first commit, like the user menu two slices along. A ledger of fifty Save buttons is the
   worst possible place to ship a control one pixel under the floor. */
.cpy-row__act .btn{
  min-height:44px;
}

@media(max-width:560px){
  .cpy-row--edited{
    margin-left:0;
    padding-left:.7rem;
  }
}



/* ==================================================================================================
   ITEM 53 SLICE 5 - EDITING THE WORDS ON THE PAGE THEY APPEAR ON
   (docs/member-account-plan.md §5.6; this slice's gate answers Q1(a) the toggle, Q2(a) the panel,
   Q3(a) the bar's list).

   THE MARKER HAS NO RULE AT REST AND THAT ABSENCE IS THE DESIGN. Every editable sentence on an
   administrator's page ships wrapped in <span class="cpy-live">, on every page, whether he is editing
   or not - so if this block styled `.cpy-live` unconditionally it would repaint the live site for one
   reader. Everything below hangs off `body.cpy-editing`, which only copy-inline.js adds. A rule setting
   nothing would be dead CSS, so there is none: the span inherits, occupies no box of its own, and is
   invisible until the mode is on.
   ================================================================================================== */

/* THE WORDS ARE THE CONTROL, so the box is drawn round the text rather than beside it. An outline and
   not a border: a border is layout, and a border appearing on twenty sentences would reflow the page
   the moment somebody started editing it - the one thing an edit-in-place screen must not do is move
   the thing you were about to press. */
body.cpy-editing .cpy-live{
  outline:2px dashed var(--brass);
  outline-offset:3px;
  border-radius:2px;
  cursor:pointer;
  /* Padding with the same negative margin: the hit area grows and the type does not move. Item 44's
     44px floor is NOT met here and cannot be - this control is a sentence, and a 44px-tall eyebrow
     would be a redesign of four public pages to fit a control that only an administrator in edit mode
     can see. The floor is for controls that are boxes; the compensation here is that the target is as
     wide as the words. */
  padding:.1em .2em;
  margin:-.1em -.2em;
  /* THE BAR HIDES THE WORDS THE BROWSER HAS JUST SCROLLED TO, AND A BROWSER FOUND IT RATHER THAN THIS
     COMMENT PREDICTING IT. `body{padding-bottom}` below makes room at the END of the document and does
     nothing about this: scrolling a marker into view puts it at the BOTTOM EDGE of the viewport, which is
     exactly where a fixed bar is - so the footer's affiliation line, one of the seven pieces most likely
     to be edited, was under the bar and unclickable with every claim about the marker green. Playwright
     said it in its own words: "cpy-bar__wrap intercepts pointer events".

     scroll-margin, not padding, because the thing that is wrong is where the SCROLL stops. It fixes the
     keyboard's version of the same defect for free - tabbing onto a marker near the foot of the page
     scrolled it under the bar too - and the top value is the sticky masthead's, which would have done the
     same thing at the other end. */
  scroll-margin-bottom:8rem;
  scroll-margin-top:calc(var(--masthead-h) + 12px);
}

body.cpy-editing .cpy-live:hover,
body.cpy-editing .cpy-live:focus-visible{
  outline-style:solid;
  background:rgba(199,162,74,.16);
}

/* The pencil rides the words. `content` on ::after and not an element, because an element inside the
   span would be a second child inside an <a> - two of these markers ARE the text of a link. */
body.cpy-editing .cpy-live::after{
  content:"\270E";
  margin-left:.25em;
  /* ABSOLUTE AND NOT `em`, WHICH A SCREENSHOT DECIDED. At .85em the pencil inherits the type it sits
     beside, so the one on a 48px <h1> came out at 41px and read as a drawing rather than as a control,
     while the one on an 11px eyebrow was a speck. It is one control and it is the same size everywhere. */
  font-size:.8rem;
  color:var(--brass-ink);
  vertical-align:middle;
}

/* ---------------------------------------------------------------------------- the bar, while editing */

/* ROOM MADE FOR IT RATHER THAN A BAR THAT COVERS THINGS. The bar is fixed to the bottom, and the
   bottom of every page on this site is the footer - which carries SEVEN of the thirty-nine editable
   pieces, including the lodge's address. A bar sitting over exactly the words an administrator came to
   change would be the defect this whole slice is about. */
/* THE FLOOR, AND copy-inline.js REPLACES IT WITH THE BAR'S MEASURED HEIGHT. A fixed 7rem is right at a
   desk and wrong on a phone, where the bar wraps to three or four rows - so the number that matters is the
   one only the browser knows. This is what the page gets in the moment between the class landing and the
   measurement, and what it keeps if the script is interrupted. */
body.cpy-editing{
  padding-bottom:7rem;
}

/* AND THE BAR DOES NOT TAKE A PRESS THAT WAS MEANT FOR THE PAGE. The scroll-margin above fixes where a
   scroll STOPS, and a browser proved that is only half of it: a marker already inside the viewport is not
   scrolled at all, so the footer's affiliation line sat under the bar and every press landed on the bar
   instead - "cpy-bar__wrap intercepts pointer events", in Playwright's own words, with the marker
   reporting itself visible, enabled and stable.

   pointer-events:none on the bar and :auto on the two controls it has. The bar is a strip of text and two
   buttons; there is nothing on it that wants a press except the buttons, and a toolbar that eats clicks
   meant for the thing it is a toolbar FOR is a control fighting its own purpose. */
.cpy-bar{
  position:fixed;
  left:0;
  right:0;
  bottom:0;
  z-index:1100;
  background:rgba(0,0,0,.94);
  color:var(--on-dark);
  border-top:2px solid var(--brass);
  pointer-events:none;
}

.cpy-bar__piece,
.cpy-bar__done{
  pointer-events:auto;
}

.cpy-bar__wrap{
  /* A PHONE MADE THIS NECESSARY: four "also on this page" buttons wrap to four rows at 390px and the bar
     came out 240px tall, a third of the screen. Capped and scrolled rather than folded into a drawer -
     item 58 found a page-level alarm invisible inside a shut <details> and item 59 is open because saving
     inside one shuts it, and a list of controls nobody can see is the same failure in a smaller box. */
  max-height:40vh;
  overflow-y:auto;
  display:flex;
  flex-wrap:wrap;
  align-items:center;
  gap:.5rem 1rem;
  max-width:var(--maxw);
  margin:0 auto;
  /* padding-block, and the sides are their own declaration: the shorthand is how a page loses its
     gutter at 320px, which is the trap item 53 slice 3 hit with the user menu. */
  padding-block:.6rem;
  padding-left:16px;
  padding-right:16px;
}

.cpy-bar__state{
  margin:0;
  font-size:.85rem;
  color:var(--on-dark);
}

.cpy-bar__also{
  display:flex;
  flex-wrap:wrap;
  align-items:center;
  gap:.4rem;
  font-size:.8rem;
  color:color-mix(in srgb, var(--on-dark) 78%, transparent);
}

/* The five with nowhere to put a pencil. A real button, 44px, because unlike the marker above this one
   IS a box and there is no reason for it to miss the floor. */
.cpy-bar__piece,
.cpy-bar__done{
  min-height:44px;
  padding:.35rem .8rem;
  border-radius:8px;
  border:1px solid var(--brass);
  background:transparent;
  color:var(--on-dark);
  font:inherit;
  font-size:.8rem;
  cursor:pointer;
}

.cpy-bar__done{
  margin-left:auto;
  background:var(--brass);
  color:var(--on-brass);
  font-weight:600;
}

.cpy-bar__piece:hover,
.cpy-bar__piece:focus-visible{
  background:rgba(199,162,74,.2);
}

/* ------------------------------------------------------------------------------------- the editor */

/* WIDTH IS CAPPED AGAINST THE VIEWPORT AND NOT AGAINST THE WORDS. The script clamps the left edge;
   this clamps the width. Both are needed, and slice 3 of this item is why: its menu was 250px wide
   with every claim about it green and made a 320px phone scroll sideways, because the thing that was
   wrong was the DOCUMENT and every assertion was about the element. */
.cpy-pop{
  z-index:1150;
  width:min(32rem, calc(100vw - 2rem));
  padding:.85rem;
  border:1px solid var(--line-light);
  border-radius:10px;
  background:var(--parchment);
  color:var(--slate);
  box-shadow:0 18px 40px rgba(0,0,0,.28);
}

.cpy-pop__label{
  margin:0 0 .45rem;
  font-size:.75rem;
  letter-spacing:.08em;
  text-transform:uppercase;
  color:var(--brass-ink);
  font-weight:700;
}

.cpy-pop__text{
  display:block;
  width:100%;
  min-height:64px;
  padding:.55rem .65rem;
  border:1px solid var(--line-light);
  border-radius:6px;
  background:var(--surface);
  color:var(--slate);
  font:inherit;
  font-size:.95rem;
  line-height:1.5;
  resize:vertical;
}

.cpy-pop__mark,
.cpy-pop__note,
.cpy-pop__ships{
  margin:.45rem 0 0;
  font-size:.78rem;
  line-height:1.5;
  color:var(--slate-soft);
}

.cpy-pop__mark{
  color:var(--brass-ink);
  font-weight:600;
}

.cpy-pop__act{
  display:flex;
  flex-wrap:wrap;
  gap:.5rem;
  margin-top:.7rem;
}

.cpy-pop__act button{
  min-height:44px;
  padding:.4rem .9rem;
  border-radius:8px;
  border:1px solid var(--line-light);
  background:var(--surface);
  color:var(--slate);
  font:inherit;
  font-size:.85rem;
  cursor:pointer;
}

/* Written as a descendant rather than as .cpy-pop__save alone, because the rule above it is
   `.cpy-pop__act button` and a single class loses to a class plus a type. !important would have worked
   and is the wrong tool: item 38 session 6's finding is that !important REVERSES the cascade, so the
   next person editing these two rules would be reading them in the opposite order to the one they are
   written in. */
.cpy-pop__act .cpy-pop__save{
  border-color:var(--brass);
  background:var(--brass);
  color:var(--on-brass);
  font-weight:600;
}

.cpy-pop__act button:disabled{
  opacity:.6;
  cursor:default;
}

@media(max-width:560px){
  .cpy-bar__done{
    margin-left:0;
  }
}


/* ==================================================================================================
   ITEM 61 - VISITS TO OTHER LODGES (.vst-*), plan docs/lodge-visits-plan.md §9.

   MOBILE FIRST THROUGHOUT: everything below IS the phone, and the one media query at the foot widens
   the card list for a desk. The suite is Chromium-only by default and the owner reads on an iPhone,
   so every claim about these rules runs in both engines.

   THE .mp-* FAMILY DOES THE HEROES AND THE SECTION HEADINGS. This family is only the parts the portal
   has no shape for: the visit card, the field groups, the car block and the people list. That split is
   /Members/Summons' (.ld-*) exactly, and it is the reason there is no hero rule in this block.

   EVERY PRESS IN HERE CLEARS 44px. Item 55's floor, put under these controls on the day they are
   written rather than left for item 44 to raise - shipping a known fault twice is what that costs.
   ================================================================================================== */

.vst-note{
  margin:1rem 0;
  color:var(--slate-soft);
  line-height:1.6;
}

.vst-hint{
  margin:.2rem 0 .9rem;
  font-size:.82rem;
  line-height:1.5;
  color:var(--slate-soft);
}

/* ---- the list of visits (plate B) ---------------------------------------------------------- */

.vst-list{
  list-style:none;
  margin:0 0 2rem;
  padding:0;
  display:grid;
  gap:.8rem;
}

.vst-card{
  padding:.9rem 1rem;
  border:1px solid var(--line-light);
  border-radius:10px;
  background:var(--surface);
}

/* A CALLED-OFF VISIT IS DIMMED AND NOT HIDDEN, which is the page model's own decision: eleven brethren
   arranged their evening around it, and a row that vanished would leave them to discover the
   cancellation by its absence. Dimmed rather than struck through, because the words inside it - the
   reason - are the thing he came back to read. */
.vst-card--off{
  opacity:.72;
}

/* THE WHOLE HEAD OF THE CARD IS THE LINK, so the press is the size of the thing a thumb aims at rather
   than the size of the lodge's name. It is a block link with two spans, not a link inside a heading:
   a 44px floor on an inline anchor is a floor on a box that does not exist. */
.vst-card__open{
  display:block;
  min-height:44px;
  color:inherit;
  text-decoration:none;
}

.vst-card__open:hover .vst-card__who,
.vst-card__open:focus-visible .vst-card__who{
  text-decoration:underline;
}

.vst-card__when{
  display:block;
  font-size:.8rem;
  font-weight:600;
  letter-spacing:.02em;
  text-transform:uppercase;
  color:var(--brass-ink);
}

.vst-card__who{
  display:block;
  margin-top:.15rem;
  font-family:var(--serif);
  font-size:1.05rem;
  font-weight:600;
  color:var(--heading);
  overflow-wrap:anywhere;
}

.vst-card__meta{
  margin:.35rem 0 0;
  font-size:.85rem;
  color:var(--slate-soft);
}

.vst-card__said{
  margin:.45rem 0 0;
  font-size:.85rem;
  font-weight:600;
  color:var(--slate);
}

/* HE HAS NOT ANSWERED, and this is the only place on the card that asks for anything. Brass ink rather
   than a background: a filled pill here would read as a warning about the visit rather than a note
   about the reader. */
.vst-card__said--open{
  color:var(--brass-ink);
}

.vst-card__off{
  margin:.45rem 0 0;
  font-size:.85rem;
  color:var(--slate);
}

/* ---- one visit (plates D and E) ------------------------------------------------------------ */

.vst-block{
  margin:0 0 1.6rem;
  padding:1rem;
  border:1px solid var(--line-light);
  border-radius:10px;
  background:var(--surface);
}

/* THE TWO PRIVILEGED BLOCKS, and they share a rule because they are the same statement to a reader:
   this part of the page is yours. The organiser's block and the answer block are the only two, which
   is what the page's own header comment says and what this rule is the visible half of. */
.vst-block--mine{
  border-color:var(--brass);
}

.vst-block__h{
  margin:0 0 .6rem;
  font-family:var(--serif);
  font-size:1.05rem;
  color:var(--heading);
}

.vst-block__sub{
  margin:1rem 0 .5rem;
  font-size:.9rem;
  font-weight:600;
  color:var(--heading);
}

.vst-block__note{
  margin:.6rem 0 0;
  line-height:1.6;
  white-space:pre-line;
}

.vst-facts{
  margin:0;
}

.vst-facts__row{
  display:flex;
  flex-wrap:wrap;
  gap:.15rem .6rem;
  padding:.35rem 0;
  border-bottom:1px solid var(--line-light);
}

.vst-facts__row:last-child{
  border-bottom:0;
}

.vst-facts dt{
  flex:0 0 9rem;
  font-size:.85rem;
  color:var(--slate-soft);
}

.vst-facts dd{
  flex:1 1 12rem;
  margin:0;
  overflow-wrap:anywhere;
}

/* THEIR WORDS, quoted. A left rule and not a box, because it sits inside a bordered block already and
   two borders round one paragraph is the picture-frame effect item 36 spent a phase removing. */
.vst-quote{
  margin:.6rem 0;
  padding:.1rem 0 .1rem .9rem;
  border-left:3px solid var(--brass);
  line-height:1.6;
  white-space:pre-line;
}

.vst-quote__from{
  margin-top:.4rem;
  font-size:.85rem;
  color:var(--slate-soft);
}

.vst-prefill{
  margin:0 0 1.2rem;
  padding:.9rem 1rem;
  border:1px solid var(--line-light);
  border-radius:10px;
  background:var(--parchment);
}

.vst-prefill__k{
  margin:0;
  font-size:.75rem;
  font-weight:600;
  letter-spacing:.04em;
  text-transform:uppercase;
  color:var(--slate-soft);
}

.vst-prefill__t{
  margin:.15rem 0 0;
  font-weight:600;
  color:var(--heading);
}

.vst-owns{
  margin:0 0 1.4rem;
  padding:.8rem 1rem;
  border-left:3px solid var(--brass);
  line-height:1.6;
}

/* ---- the form (plate C, and the edit screen) ----------------------------------------------- */

.vst-form{
  margin:0 0 2rem;
}

.vst-set{
  margin:0 0 1.4rem;
  padding:.9rem 1rem 1.1rem;
  border:1px solid var(--line-light);
  border-radius:10px;
}

.vst-set__legend{
  padding:0 .4rem;
  font-family:var(--serif);
  font-size:.95rem;
  font-weight:600;
  color:var(--heading);
}

.vst-label{
  display:block;
  margin:.8rem 0 .25rem;
  font-size:.85rem;
  font-weight:600;
  color:var(--heading);
}

/* `width:100%` WITH `box-sizing` INHERITED FROM THE RESET. A field at 100% inside a padded fieldset is
   what puts the right-hand gutter back on a 320px phone, and it is the rule that makes the whole form
   one column without a media query. */
.vst-field{
  display:block;
  width:100%;
  min-height:44px;
  padding:.55rem .7rem;
  border:1px solid var(--line-light);
  border-radius:8px;
  background:var(--surface);
  color:var(--slate);
  font:inherit;
}

.vst-field:focus-visible{
  border-color:var(--brass);
  outline:2px solid var(--brass);
  outline-offset:1px;
}

/* WEBKIT IGNORES `min-height` ON A <select>, AND THIS ITEM'S OWN NET CAUGHT IT. Chromium drew the car
   picker at 44px and WebKit at 25px, with every Chromium claim green -- which is exactly why the sweep
   runs in both engines and why the owner's is the one that matters: he reads this site on an iPhone.

   `height` rather than a taller `padding`, because padding on a <select> is the other thing WebKit
   disagrees about, and a fixed height is the one spelling both engines honour. */
select.vst-field{
  height:44px;
}

.vst-field--short{
  max-width:11rem;
}

.vst-field--note{
  min-height:5.5rem;
  resize:vertical;
}

/* STEP 15's LETTER. Taller than a note because it holds a whole message rather than a line about the
   parking, and `pre-wrap`-shaped: the prefill is a letter with paragraph breaks and an indented list of
   what the kitchen has to know, and a box that reflowed it into one block would be a box whose contents
   did not look like what arrives.

   A MONOSPACE STACK IS DELIBERATELY NOT USED. It would make the indented list line up perfectly and make
   the letter read as code, which is the opposite of what the organiser is being asked to write. */
.vst-field--letter{
  min-height:18rem;
  resize:vertical;
  line-height:1.55;
  white-space:pre-wrap;
}

/* THE FACT A FIELD WOULD HAVE BEEN. On the edit screen the host lodge cannot change, so it is printed
   rather than drawn as a disabled input - a disabled control is a thing a reader tries to use. */
.vst-fact{
  display:flex;
  flex-wrap:wrap;
  gap:.15rem .6rem;
  margin:.6rem 0 0;
}

.vst-fact__k{
  font-size:.85rem;
  color:var(--slate-soft);
}

.vst-fact__v{
  font-weight:600;
  color:var(--heading);
  overflow-wrap:anywhere;
}

.vst-check{
  display:flex;
  align-items:center;
  gap:.5rem;
  min-height:44px;
  margin:.6rem 0 .2rem;
  line-height:1.5;
}

.vst-check input{
  flex:0 0 auto;
  width:1.1rem;
  height:1.1rem;
}

.vst-form__act{
  display:flex;
  flex-wrap:wrap;
  gap:.6rem;
  margin-top:1.2rem;
}

/* ---- answering, and how he is travelling --------------------------------------------------- */

.vst-answer__row{
  display:flex;
  flex-wrap:wrap;
  gap:.5rem;
  margin:0 0 .6rem;
}

/* A RADIO DRESSED AS A PRESS, and the input is kept in the flow rather than hidden: a visually-hidden
   radio inside a label is a control a screen reader can still reach, but it is also one keyboard users
   navigate by an outline this rule would have had to redraw. Left visible, the native focus ring and
   the native arrow-key grouping both work and there is nothing to reimplement. */
.vst-radio{
  display:flex;
  align-items:center;
  gap:.45rem;
  min-height:44px;
  padding:.35rem .8rem;
  border:1px solid var(--line-light);
  border-radius:999px;
  cursor:pointer;
}

.vst-radio--on{
  border-color:var(--brass);
  font-weight:600;
  color:var(--heading);
}

.vst-travel{
  margin:1.2rem 0 0;
  padding-top:1rem;
  border-top:1px solid var(--line-light);
}

.vst-travel--danger{
  border-top-color:var(--claret);
}

/* ---- the cars ------------------------------------------------------------------------------ */

.vst-car{
  margin:0 0 .9rem;
  padding:.75rem .9rem;
  border:1px solid var(--line-light);
  border-radius:10px;
}

/* HIS OWN CAR. The same brass edge the two privileged blocks carry, and for the same reason: it is the
   one car on the page with a control inside it that nobody else sees. */
.vst-car--mine{
  border-color:var(--brass);
}

.vst-car__who{
  margin:0;
  overflow-wrap:anywhere;
}

.vst-car__seats{
  margin:.2rem 0 0;
  font-size:.85rem;
  font-weight:600;
  color:var(--brass-ink);
}

.vst-car__note{
  margin:.25rem 0 0;
  font-size:.85rem;
  color:var(--slate-soft);
}

.vst-car__tag{
  margin-left:.4rem;
  padding:.05rem .35rem;
  border:1px solid var(--line-light);
  border-radius:999px;
  font-size:.7rem;
  font-weight:600;
  color:var(--slate);
  white-space:nowrap;
}

.vst-car__list{
  list-style:none;
  margin:.5rem 0 0;
  padding:0;
}

/* WRAPS, and it is .cnv-group-list__row's answer for its reason: a thirty-five character name beside
   "Give him a seat" and "No room" is more controls than a 288px column has, and wrapped they drop to
   their own line under the name with their 44px intact. */
.vst-car__list li{
  display:flex;
  flex-wrap:wrap;
  align-items:center;
  gap:.35rem .6rem;
  padding:.3rem 0;
  border-top:1px solid var(--line-light);
  overflow-wrap:anywhere;
}

.vst-car__list--asking li{
  color:var(--slate-soft);
}

.vst-car__acts{
  display:flex;
  flex-wrap:wrap;
  gap:.35rem;
}

.vst-car__list form,
.vst-car__acts form{
  margin:0;
}

/* .vst-act IS .btn-line's ARGUMENT AT A SMALLER SIZE, and it is a named class for item 55's reason: a
   bare button inside a car row has no box on any light surface, and four authors in a row have written
   one expecting otherwise. 44px, a real border, and a brass edge on the one that says yes. */
.vst-act{
  min-height:44px;
  padding:.3rem .7rem;
  border:1px solid var(--line-light);
  border-radius:999px;
  background:var(--surface);
  color:var(--heading);
  font:inherit;
  font-size:.82rem;
  cursor:pointer;
}

.vst-act:hover,
.vst-act:focus-visible{
  border-color:var(--brass);
}

.vst-act--yes{
  border-color:var(--brass);
  font-weight:600;
}

/* ---- who is going -------------------------------------------------------------------------- */

.vst-people{
  list-style:none;
  margin:0 0 .8rem;
  padding:0;
  border-top:1px solid var(--line-light);
}

.vst-people__row{
  display:flex;
  flex-wrap:wrap;
  align-items:baseline;
  gap:.15rem .6rem;
  padding:.35rem 0;
  border-bottom:1px solid var(--line-light);
}

.vst-people__who{
  flex:1 1 12rem;
  overflow-wrap:anywhere;
  font-weight:600;
}

.vst-people__how{
  flex:0 0 auto;
  font-size:.82rem;
  color:var(--slate-soft);
}

/* ITEM 61 S4 - a brother's guests, under his own name. It takes the WHOLE row after the flex items above
   it, so that "Bringing Bro. Finch and one more" sits beneath his name rather than competing with his
   travel line for the same baseline. `flex-basis:100%` and not a second list: a guest belongs to the man
   who is bringing him, and a separate list of guests would be a list of people with nobody attached. */
.vst-people__guests{
  flex:1 0 100%;
  font-size:.82rem;
  color:var(--slate-soft);
}

/* ITEM 61 S4 - the guest name boxes inside the answer form (plan §4.3).

   A <fieldset> and not a <div>, because these boxes are one question asked several times and a legend is
   how that is said to a screen reader: without it "Guest 1" and "Guest 2" are two unrelated fields on a
   form that already has three. No border of its own - the block it sits in is already a card, and a second
   edge inside it reads as a second card. */
.vst-guests{
  border:0;
  margin:0;
  padding:0;
  min-width:0;
}

/* Each guest is a pair of fields that must not be mistaken for the next guest's pair, and a rule between
   them is the cheapest way to say so. The first has none: a line under the legend would read as an
   underline on it. */
.vst-guests__row + .vst-guests__row{
  margin-top:.9rem;
  padding-top:.9rem;
  border-top:1px solid var(--line-light);
}

.vst-off__sub{
  display:block;
  margin-top:.3rem;
  font-size:.85rem;
}

/* ---- the summons list's two new pieces (.ld-* , because they live on that page) ------------- */

/* THE COVERING MESSAGE, SHUT. ITEM 58's TRAP APPLIES TO EVERY CLAIM ABOUT WHAT IS INSIDE THIS:
   `InnerText` on a closed <details> is the empty string, so a claim that does not open the drawer
   passes against nothing.

   AND ITEM 54's: the `display` trick for revealing a closed <details> is dead in both engines. If a
   desk-width rule ever needs this open, it is `::details-content{content-visibility:visible}`. */
.ld-invite{
  margin:0 0 .5rem;
  padding:0 0 0 .2rem;
}

/* A CARET, AND A SCREENSHOT IS WHY. `display:flex` on a <summary> loses its `::marker` in every engine
   -- which .adm-group__head's own comment already says -- so the first draft of this drawer rendered as
   a line of brass prose with nothing to say it opened. Every claim was green over it: `InnerText` on a
   closed <details> is the empty string, so the claim that opens the drawer proves the drawer works and
   says nothing about whether a reader can tell.

   The caret is TEXT rather than the SVG .pap-drawer__caret uses, because this one sits inline in a
   sentence rather than inside a pill, and a `&rsaquo;` is what the breadcrumbs on the same page use. */
.ld-invite__open{
  min-height:44px;
  display:flex;
  align-items:center;
  gap:.4rem;
  list-style:none;
  font-size:.85rem;
  font-weight:600;
  color:var(--brass-ink);
  cursor:pointer;
}

.ld-invite__open::-webkit-details-marker{
  display:none;
}

.ld-invite__caret{
  display:inline-block;
  flex:0 0 auto;
  transition:transform .15s;
}

.ld-invite[open] .ld-invite__caret{
  transform:rotate(90deg);
}

.ld-invite__open:hover,
.ld-invite__open:focus-visible{
  text-decoration:underline;
}

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

.ld-invite__said{
  margin:.2rem 0 .4rem;
  padding:.1rem 0 .1rem .9rem;
  border-left:3px solid var(--brass);
  line-height:1.6;
  white-space:pre-line;
}

.ld-invite__act{
  margin:0 0 1rem;
}

/* ---- the desk --------------------------------------------------------------------------------
   ONE QUERY, AND IT ONLY WIDENS THE LIST. Everything above is the phone; a desk gets two visit cards
   across and a wider label column in the fact lists, and nothing else moves. */
@media(min-width:760px){
  .vst-list{
    grid-template-columns:repeat(2,minmax(0,1fr));
  }

  .vst-facts dt{
    flex:0 0 11rem;
  }
}

}
/* ---- /Members/Summons: the visiting row, whole (item 63) -------------------------------------
   THE USER READ THIS AT THE DESK AND SAID THE BUTTON WAS "BUTTED UP TO THE LEFT HAND SIDE", and it
   was: `.ld-invite` carried `padding-left:.2rem` and `.ld-invite__act` carried nothing at all, while
   every other thing in the card starts at `.ld-doc`'s 20px. The drawer and the button were SIBLINGS
   of the row rather than part of it, so they inherited the card's edge and not the row's gutter.

   A GRID AND NOT A PADDING VALUE, and that is the whole decision. The indent written as a number is
   20px + `.ld-doc__kind`'s 92px + the 14px gap = 126px -- and `.ld-chip` is `white-space:nowrap`, so
   "VISITING SUMMONS" measures wider than 92px and the badge column is wider than 126px on this very
   row. The number would have been wrong on the screenshot that reported the fault. `auto` is whatever
   the widest chip in the list actually is, and lines 2 and 3 of the row start where the TITLE starts
   because they are told to start at the same grid line, not at a copy of it.

   THIS IS ITEM 62's ANSWER ONE PAGE OVER: an alignment that is measured by the layout engine cannot
   drift when the words change, and one that is measured by an author can. */
@layer site {
  .ld-doc--visit{
    display:grid;
    grid-template-columns:auto minmax(0,1fr) auto;
    align-items:center;
    column-gap:14px;
    row-gap:6px;
  }

  /* Line 2 and line 3 of the row: under the title, and out to the row's right-hand gutter. */
  .ld-doc--visit > .ld-invite,
  .ld-doc--visit > .ld-invite__act{
    grid-column:2 / -1;
    margin:0;
    padding-left:0;
  }

  /* THE PHONE KEEPS THE REFLOW `.ld-doc` ALREADY HAD, and it has to be spelled again because the
     shared rules do it with `order` and `flex-basis`, and `flex-basis` means nothing to a grid item.
     The chip and the download share the first line; the title, the drawer and the button each take a
     whole one. At this width the badge column is the row's left edge, so "under the title" and "at
     the gutter" are the same place -- which is the right answer on a 320px screen and would not be at
     the desk. */
  @media(max-width:640px){
    .ld-doc--visit{
      grid-template-columns:auto minmax(0,1fr);
    }

    .ld-doc--visit > .ld-doc__body{
      grid-column:1 / -1;
    }

    .ld-doc--visit > .ld-invite,
    .ld-doc--visit > .ld-invite__act{
      grid-column:1 / -1;
      order:4;
    }
  }
}
