/* ============================================================================================
   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;
  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;
}

.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);
}

/* 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;
}

/* 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.
   `inline-grid` is doing the real work. 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 plain `display:grid` here would instead stretch the row to the full card
   and give three enormous buttons — the same rule, a completely different picture. */
.content-block-cta--match{
  display:inline-grid;
  grid-auto-flow:column;
  grid-auto-columns:1fr;
  gap:14px;
  max-width:100%;
}

.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. */
  .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;
  }
  .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}
  /* 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);
}

.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;
}

/* 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;
}

/* 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;
}

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

.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;
}

.adm-group__head{
  display:flex;
  align-items:baseline;
  gap:16px;
  margin-bottom:22px;
  flex-wrap:wrap;
}

.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;
}

@media(max-width:640px){
  .fb-board-card__docs{ grid-template-columns:1fr; }
}

/* 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}

.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 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}

.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)}
.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}

.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;
}
.fbm-closed h2{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}
  .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}

@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}
}

/* ---------------------------------------------------------------------
   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}

/* .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))}
/* 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)}

/* 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))}
}
@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))}
}
@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 papers themselves — /Members/Summons's archive and /Admin/Documents's two forms.
     .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 (both admin pages and the members' archive) ---- */
.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}
.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;
}
.ld-search{min-width:200px}
.ld-select:focus-visible,.ld-search:focus-visible{outline:2px solid var(--brass);outline-offset:2px}

.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}

/* ---- /Admin/Documents ---- */
.ld-upload__file{display:flex;flex-direction:column;gap:.6rem}
.ld-upload__row{display:grid;grid-template-columns:1fr 1fr;gap:1rem}
.ld-upload__field{display:flex;flex-direction:column;gap:.6rem}
.ld-upload__actions{display:flex;align-items:center;gap:.75rem;flex-wrap:wrap;margin-top:.5rem}

/* The replace control is a file input beside its own submit, inside a row that is already a flex.
   It takes a LINE OF ITS OWN (flex-basis:100%) rather than sitting beside Publish and Remove: the first
   screenshot had a native file input squeezed between two buttons, rendering as "No fi…osen", and a
   control the operator cannot read is not one. Two tidy lines beat one crowded one. */
.ld-replace{
  display:flex;align-items:center;justify-content:flex-end;gap:.5rem;flex-wrap:wrap;
  margin:0;flex-basis:100%;
}
.ld-replace__label{display:flex;align-items:center;gap:.4rem;font-size:.78rem;color:var(--slate-soft)}
.ld-replace__label input{max-width:220px;font-size:.75rem}

/* The ritual-material group on the upload form (MJ Phase 6a, approved supplement screen B). A real
   <fieldset> because it is one: the two fields inside apply to one kind and a screen reader should be told
   so by the legend rather than by their proximity.

   NEUTRAL, not brass-tinted, and that is a screenshot's finding rather than a preference. The supplement
   drew this group brass-tinted, which is a warm gold wash on Heritage — but --brass is #C60000 on the
   Grammarians palette, so `color-mix(brass 8%)` under a 45%-brass border rendered a PINK BOX WITH A RED
   BORDER around two perfectly valid fields, which on a form reads as validation failure. Its legend is what
   says the group is conditional; the box only has to hold it together. */
.ld-ritual{
  border:1px solid var(--line-light);
  border-radius:12px;
  background:var(--parchment-2);
  padding:.9rem 1.1rem 1.1rem;
  margin:.25rem 0 .25rem;
}
.ld-ritual>legend{
  font-size:.82rem;font-weight:600;color:var(--heading);padding:0 .4rem;
  display:flex;align-items:baseline;gap:.5rem;flex-wrap:wrap;
}
.ld-ritual .admin-list__meta{margin:0}

/* The audience ticks. A wrapping row rather than the settings page's column: there are seven or eight of
   these and they are short, so a column would push the section select a screen away from the roles that
   qualify it. */
.ld-audience{display:flex;flex-wrap:wrap;gap:.4rem .75rem;padding-top:.2rem}
.ld-audience__role{font-weight:500;font-size:.85rem}

/* ---- /Admin/Documents: the inline paper editor (item 34, approved drawing
   docs/mockups/lodge-paper-editing-gate.html plate C) ----

   A <details> on the row, the shape the festive board's meal editor already uses. The row is a flex with
   space-between, so the panel takes a LINE OF ITS OWN the way .ld-replace does — flex-basis:100% plus wrap
   on the row, rather than a third column that would squeeze the title to nothing. */
.admin-list__item--edits{flex-wrap:wrap}

.ld-edit{
  flex-basis:100%;
  margin:.35rem 0 0;
  text-align:right; /* carries the Edit control to the actions' own column; the body puts it back */
}

/* The summary IS the Edit button. list-style and the webkit marker are both cleared, because a triangle
   beside a brass button reads as two controls. */
.ld-edit__toggle{
  display:inline-flex;align-items:center;gap:.35rem;cursor:pointer;
  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);
  list-style:none;
}
.ld-edit__toggle::-webkit-details-marker{display:none}
.ld-edit__toggle:hover{background:var(--parchment-2)}
.ld-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. */
.ld-edit__toggle-open{display:none}
.ld-edit[open] > .ld-edit__toggle > .ld-edit__toggle-shut{display:none}
.ld-edit[open] > .ld-edit__toggle > .ld-edit__toggle-open{display:inline}

.ld-edit__body{
  text-align:left;
  margin:.6rem 0 .2rem;padding-top:.85rem;
  border-top:1px dashed var(--line-light);
}
.ld-edit__grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.85rem 1.1rem}
.ld-edit__field{display:grid;gap:.3rem;min-width:0;align-content:start}
.ld-edit__field--wide{grid-column:1/-1}
.ld-edit__field label{font-size:.8rem;font-weight:600;color:var(--heading)}
.ld-edit__note{margin:.25rem 0 0;font-size:.78rem;color:var(--slate-soft);line-height:1.5}

/* The audience ticks in a box of their own: they are the one control on this panel that is a LIST rather
   than a field, and without the rule they read as loose text under the label. */
.ld-edit__audience{
  display:flex;flex-wrap:wrap;gap:.4rem .9rem;
  padding:.5rem .6rem;border:1px solid var(--line-light);border-radius:6px;background:var(--parchment-2);
}

/* NEUTRAL, never brass-tinted — the same finding the upload form's .ld-ritual carries: --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. */
.ld-edit__ritual{
  grid-column:1/-1;margin:0;padding:.85rem 1rem;
  border:1px solid var(--line-light);border-radius:8px;background:var(--parchment-2);
}
.ld-edit__ritual>legend{
  font-size:.78rem;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
  color:var(--slate-soft);padding:0 .4rem;
}

.ld-edit__actions{display:flex;gap:.6rem;align-items:center;margin-top:1rem;flex-wrap:wrap}
.ld-edit__gap{flex:1 1 auto}
.ld-edit__cancel{font-size:.82rem;color:var(--slate-soft);text-decoration:none;border-bottom:1px solid var(--line-light)}
.ld-edit__cancel:hover{color:var(--slate)}
.ld-edit__cancel:focus-visible{outline:2px solid var(--brass);outline-offset:2px}

/* ---- /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}
  .ld-upload__row{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 ADMIN list row reflows too (MJ Phase 6a). At 360px the ritual row's four controls — Publish,
     Replace's file input, Upload, Remove — 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. */
  .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}
  .ld-replace{justify-content:flex-start}
  .ld-audience__role{min-height:44px}

  /* Item 34: the editor is a form, and a two-column form in 360px is two columns of nothing. Its control
     joins the actions at the left edge rather than floating alone on the right. */
  .ld-edit{text-align:left}
  .ld-edit__toggle{min-height:44px}
  .ld-edit__grid{grid-template-columns:1fr}
  .ld-edit__actions .btn{min-height:44px}
}

/* ==========================================================================================
   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)}

/* ---- 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} }
.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}
.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}

@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}
}

/* =========================================================================================================
   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;
}

/* The rule line. Administrator sits ABOVE it and everything additive below, which is the whole of
   the exclusivity rule said in geometry before it is said in words. */
.rl-editor__sole{
  border-bottom:1px solid var(--line-light);
  padding-bottom:.4rem;
  margin-bottom:.15rem;
}

.rl-editor__note{
  font-size:.74rem;
  color:var(--slate-soft);
  margin:.2rem 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);
}

/* Set by the page's own script when Administrator is ticked. The boxes are DISABLED as well as
   dimmed, because a dimmed control a keyboard can still reach is a picture of a rule. */
.rl-editor.is-sole .rl-editor__grid .admin-check{
  opacity:.42;
}

/* 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);
}

/* Plate C's cost line. Present in the markup always and empty until Administrator is ticked, so the
   panel does not jump when the script fills it; :empty keeps the margin off an empty paragraph. */
.rl-editor__cost{
  font-size:.74rem;
  color:var(--claret);
  margin:.3rem 0 0;
  line-height:1.45;
}

.rl-editor__cost:empty{
  display:none;
}

}
