/* ==========================================================================
   Bluefield Area Shag Society — Design System
   Palette: Beach Club Regatta (approved 2026-07-31)
   Audience-first: 18px+ body, high contrast, generous targets (48px min)
   ========================================================================== */

:root {
  /* DECLARED IN BOTH DIRECTIONS, or the browser's own furniture stays light on
     a dark page: scrollbars, text carets, date pickers and the spinners inside
     a number field are drawn by the platform, not by this file, and they take
     their cue from here. The dark block sets `color-scheme: dark`. */
  color-scheme: light;
  /* ---- THE CLUB'S PALETTE ------------------------------------------------
     Everything in this block is the CLUB's, and everything below it is the
     PRODUCT's. That line is the whole point of naming them separately.

     `platform` in CLAUDE.md records the decision this serves: the colour half
     of "customize the brand logo and text font individually per site" was
     never built, so every club on this platform is currently Bluefield's
     colours with somebody else's name on them. The plan puts the PLUMBING in
     Stage 1 and the palettes themselves with club two, because plumbing is
     invisible and a palette is a decision that needs a yes.

     A PALETTE IS PICKED WHOLE. The shades below are named rather than
     computed, and that is deliberate. Deriving --navy-deep from --navy needs a
     contrast validator in both themes, which is what `burnrate` had to build
     to choose three colours — and a club that picked a navy would otherwise
     get deeps that no longer match it, which is worse than not offering the
     choice. `platform` already says the shape: four or five palettes proposed
     per club, validated on save, never a free colour picker. A palette is a
     set of these tokens, chosen together.

     Every one of them is servable from the club's own document — see
     brandStyle() in server.js, which emits only what DIFFERS from the values
     here, so a club that has chosen nothing gets markup byte-identical to
     what it got before any of this existed. */
  --navy: #0F2A43;          /* Regatta Navy — headers, nav, footer */
  --navy-deep: #0A1D30;
  /* The gradient family. Named because a hero drawn from literals stays one
     club's blue however its navy is set — which was true of all seven of the
     gradients on the home page. */
  --navy-dark: #081726;     /* the darkest stop, top of the hero */
  --navy-mid: #143656;
  --navy-lift: #17395C;     /* the common second stop */
  --navy-bright: #1A466F;   /* the lightest, bottom of the hero */
  --navy-steel: #8FA5BC;    /* type on navy — the rail's own quiet grey-blue */
  --gold: #C9A227;          /* Champagne Gold — CTAs, accents */
  --gold-deep: #9A7B14;     /* accessible gold for text/charts */
  --gold-light: #E5C963;    /* the lift in the gold gradient */
  --gold-hover: #DDB93E;    /* the gold button, pressed */
  --gold-pale: #E9D9A6;
  --harbor: #3E6C93;        /* Harbor Blue — links, secondary accents */
  --harbor-tint: #E2E7ED;
  --paper: #F8F7F2;         /* Sailcloth White — page background */
  --card: #FFFFFF;
  --ink: #1C2836;           /* primary text */
  --muted: #4E5A68;         /* secondary text */
  /* Form labels are navy rather than --ink on purpose: they are signposts, not
     prose. Their own token because the dark portal has to move them and the
     literal was baked into one rule far below. */
  --label-ink: var(--navy);
  --line: #DDE1E6;
  --good: #1E7B4F;
  --danger: #B3362B;
  --chart-income: #2F6DB0;  /* validated chart series colors */
  --chart-expense: #9A7B14;
  --radius: 12px;
  --shadow: 0 4px 18px rgba(15, 42, 67, 0.09);
  --shadow-lg: 0 10px 34px rgba(15, 42, 67, 0.16);
  --serif: Georgia, "Iowan Old Style", "Times New Roman", serif;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;

  /* ---- system tokens (Design Review II) ----------------------------------
     The layer underneath: spacing, tints and surfaces are named once so a
     systematic change — a new palette, a denser default, dark mode — is a
     token edit here, never a hunt through call sites. */
  --s1: 4px;   /* icon to label */
  --s2: 8px;   /* within a control */
  --s3: 12px;  /* between siblings */
  --s4: 16px;  /* card padding, compact */
  --s5: 24px;  /* card padding, comfortable */
  --s6: 40px;  /* between sections */

  /* status tints — status is carried by a quiet pill on white, never by a
     saturated fill, so a reconciled night and an unreconciled one stay
     distinguishable without the page turning into a traffic light */
  --gold-tint: #FBF1DA;
  --good-tint: #E6F2EB;
  --danger-tint: #FBEBE9;

  /* surfaces — dark mode (Wave 3) remaps these instead of adding rules */
  --surface: #FFFFFF;       /* cards, tables, panels */
  --surface-2: #F4F6F8;     /* table heads, toolbars, wells */
  --hairline: var(--line);

  /* The board portal's side menu. Named because three things have to agree
     about it: the rail's own width, the room the portal reserves beside it,
     and the footer's inset. Below 1025px it is not a side menu at all — it is
     the bottom tab bar — and nothing here applies. */
  --rail-w: 236px;
}

* { margin: 0; padding: 0; box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
  font-family: var(--sans);
  font-size: 18px;
  line-height: 1.65;
  color: var(--ink);
  background-color: var(--paper);   /* sand texture layers on top — see below */
  -webkit-font-smoothing: antialiased;
}
img { max-width: 100%; display: block; }
a { color: var(--harbor); text-decoration-thickness: 1.5px; text-underline-offset: 3px; }
a:hover { color: var(--navy); }

h1, h2, h3 { font-family: var(--serif); color: var(--navy); font-weight: 600; line-height: 1.18; }
h1 { font-size: clamp(34px, 5vw, 52px); }
h2 { font-size: clamp(26px, 3.4vw, 36px); }
h3 { font-size: 22px; }

.wrap { max-width: 1120px; margin: 0 auto; padding: 0 24px; }

.eyebrow {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--gold-deep);
  margin-bottom: 14px;
}

/* ------------------------------- header -------------------------------- */
.site-header {
  background: var(--navy);
  color: #fff;
  position: sticky;
  top: 0;
  z-index: 50;
  box-shadow: 0 2px 14px rgba(10, 29, 48, 0.35);
}
/* THE LOCKUP IS ON THE PAGE'S OWN LEFT EDGE, NOT CENTRED ON THE WINDOW.

   This bar used to be a 1240px block centred in the viewport, which put the
   club's mark on an edge nothing else on the screen shares. In the portal that
   is worst: the menu is fixed to the left edge of the WINDOW and the pane
   starts 32px inside it, so on a 1600px screen the mark landed at x=204 —
   204px right of the menu it sits over, 76px left of the heading it sits
   above, floating over the seam between the two.

   Flush left instead, and the 24px of padding it already had does the whole
   job: the rail's own 12px gutter plus its buttons' 12px puts BOARD PORTAL and
   every menu icon at exactly 24px too, so the mark, the label naming the menu
   and the sections beneath it all stand on one line. The header, the menu and
   the wall below it read as one piece of navy rather than three.

   Full width and not a wider centred block, on the public pages as well as the
   portal, because the club's name belongs on the same edge on every screen
   somebody moves between. The nav goes to the far right for the same reason it
   always did — `margin-left: auto` — which now means the window's right edge. */
.site-header .bar {
  display: flex;
  align-items: center;
  gap: 20px;
  padding: 14px 24px;
}
.brand { display: flex; align-items: center; gap: 13px; text-decoration: none; color: #fff; }
.brand .mark { flex: none; }
.brand .name {
  font-family: var(--serif);
  font-size: 19px;
  letter-spacing: 0.02em;
  line-height: 1.15;
}
.brand .name small {
  display: block;
  font-family: var(--sans);
  font-size: 10.5px;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--gold);
  margin-top: 3px;
}
.main-nav { margin-left: auto; display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.main-nav a {
  color: rgba(255, 255, 255, 0.88);
  text-decoration: none;
  font-size: 16px;
  font-weight: 600;
  padding: 10px 14px;
  border-radius: 8px;
  transition: background 0.15s;
}
.main-nav a:hover { background: rgba(255, 255, 255, 0.1); color: #fff; }
.main-nav a.active { background: rgba(255, 255, 255, 0.14); color: var(--gold); }
.main-nav a.nav-cta {
  /* SMALLER, NOT FLATTENED — still its own gold pill, still the one link on
     this bar that says "click me first," just taking less of the row than it
     used to. A sixth nav item ("Other Clubs") joined this bar and the CTA's
     old footprint (10px 20px, 16px text) was the single biggest thing on it
     — shrinking it, not demoting it to a plain link, is what buys the room
     for six items to sit across two rows instead of three. */
  background: var(--gold);
  color: var(--navy-deep);
  font-weight: 700;
  font-size: 15px;
  border-radius: 20px;
  padding: 8px 15px;
  margin-left: 8px;
}
.main-nav a.nav-cta:hover { background: #DDB93E; }

/* -------------------------------- buttons ------------------------------- */
.btn {
  display: inline-block;
  font-size: 17px;
  font-weight: 700;
  padding: 13px 28px;
  border-radius: 28px;
  border: none;
  cursor: pointer;
  text-decoration: none;
  font-family: var(--sans);
  transition: transform 0.12s, box-shadow 0.12s, background 0.15s;
}
.btn:active { transform: translateY(1px); }
.btn-gold { background: var(--gold); color: var(--navy-deep); box-shadow: 0 3px 12px rgba(201, 162, 39, 0.35); }
.btn-gold:hover { background: var(--gold-hover); color: var(--navy-deep); }
.btn-navy { background: var(--navy); color: #fff; }
.btn-navy:hover { background: var(--navy-lift); color: #fff; }
.btn-ghost { background: transparent; color: var(--navy); border: 2px solid var(--navy); }
.btn-ghost:hover { background: var(--navy); color: #fff; }
.btn-sm { font-size: 15px; padding: 9px 18px; }
.btn-danger { background: #fff; color: var(--danger); border: 1.5px solid var(--danger); }
.btn-danger:hover { background: var(--danger); color: #fff; }

/* --------------------------------- hero --------------------------------- */
.hero {
  background:
    radial-gradient(1100px 500px at 85% -10%, rgba(62, 108, 147, 0.35), transparent 60%),
    linear-gradient(170deg, var(--navy) 0%, #16385A 55%, #1D4670 100%);
  color: #fff;
  padding: 88px 0 0;
  overflow: hidden;
}
.hero .wrap { display: grid; grid-template-columns: 1.15fr 0.85fr; gap: 40px; align-items: center; }
.hero h1 { color: #fff; margin-bottom: 20px; }
.hero .eyebrow { color: var(--gold); }
.hero p.lead { font-size: 20px; color: #C7D3E0; max-width: 540px; margin-bottom: 30px; }
.hero .btns { display: flex; gap: 14px; flex-wrap: wrap; margin-bottom: 60px; }
.hero-art { text-align: center; padding-bottom: 30px; }
.hero-waves { display: block; width: 100%; margin-top: -6px; }
@media (max-width: 880px) {
  .hero .wrap { grid-template-columns: 1fr; }
  .hero-art { display: none; }
}

/* ------------------------------- sections ------------------------------- */
.section { padding: 72px 0; }
.section.tint { background: var(--harbor-tint); }
.section-head { max-width: 640px; margin-bottom: 40px; }
.section-head p { color: var(--muted); font-size: 18px; margin-top: 12px; }
.section-head.center { margin: 0 auto 40px; text-align: center; }

/* --------------------------- sand texture ("Beach Drift") ------------------
   A nod to beach music in the Sailcloth White areas. Four layers, painted
   bottom-up: a vertical wash from dry sand to damp, warm shadow specks, white
   highlight specks, and rare gold mica — crushed shell catching the light.
   The specks are feTurbulence pushed through a threshold (feColorMatrix row 4
   is alpha = k·R − c), so they land as discrete GRAINS rather than a haze.

   It goes on the page canvas, not on individual sections, and that is load-
   bearing. Per-section, the wash restarts at every band — which puts a hard
   damp-to-dry step wherever a section meets bare page, and .site-footer's
   80px margin-top guarantees one on every page. One sheet across the canvas
   cannot seam. It also costs no stacking context, which matters because
   .section#portal-view holds fixed overlays (.chart-tip, the mobile
   .portal-rail) whose z-index has to stay global.

   Everything with its own background paints straight over it: the navy header,
   bands and footer, .section.tint, and every white card. That is the intent —
   the cards need to stay clean to lift off the sand.

   Strength is baked in at 0.46 — study option #10 at 115% (0.40 × 1.15). To
   re-tune, scale all four alphas below by the same factor: the three
   opacity='0.46' attributes and the two rgba() alphas in the gradient.
   To remove it entirely: delete this block. .sand is the opt-in hook for
   putting the same texture on an isolated panel.
   -------------------------------------------------------------------------- */
body,
.sand {
  background-image:
    /* rare gold mica — crushed shell catching the light */
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='m' x='0' y='0' width='100%25' height='100%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.45' numOctaves='1' seed='31' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0.79 0 0 0 0 0.63 0 0 0 0 0.15 9 0 0 0 -7.2'/%3E%3C/filter%3E%3Crect width='300' height='300' filter='url(%23m)' opacity='0.46'/%3E%3C/svg%3E"),
    /* dry-sand highlight specks */
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='a' x='0' y='0' width='100%25' height='100%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.95' numOctaves='1' seed='5' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 5 0 0 0 -3.2'/%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23a)' opacity='0.46'/%3E%3C/svg%3E"),
    /* shadow side of each grain */
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='b' x='0' y='0' width='100%25' height='100%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.95' numOctaves='1' seed='11' stitchTiles='stitch'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0.40 0 0 0 0 0.33 0 0 0 0 0.21 5 0 0 0 -3.1'/%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23b)' opacity='0.46'/%3E%3C/svg%3E"),
    /* the wash: dry sand at the top of the page, damp at the bottom */
    linear-gradient(179deg, rgba(255, 255, 255, 0.276) 0%, rgba(206, 188, 150, 0.156) 100%);
  background-size: 300px 300px, 220px 220px, 220px 220px, auto;
}
/* .sand is opaque on purpose — it has to cover the canvas texture rather than
   stack on top of it, or the grain doubles wherever the class is used. */
.sand { background-color: var(--paper); }

/* Purely decorative — it carries no meaning, so drop it wherever the reader has
   asked for maximum legibility, and never spend ink on it. */
@media (prefers-contrast: more) {
  body,
  .sand { background-image: none; }
}
@media print {
  body,
  .sand { background-image: none; }
}

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

/* ---- A COLUMN MAY NEVER BE WIDER THAN THE GRID IT SITS IN -----------------
   `1fr` is shorthand for `minmax(auto, 1fr)`, and that `auto` is the item's
   own min-content. One unbreakable word in one card — an email address, an
   account number, a member's surname — and the column grows past the grid,
   the grid past the pane, and the pane past the phone.

   A page wider than the screen is not a cosmetic overflow on iOS. Safari
   answers it by zooming the entire page out to fit, which pulls the layout
   viewport away from the visual one; `position: fixed` is measured against
   the layout viewport, so the bottom bar is then drawn somewhere the screen
   is no longer showing. It floats. The bar has been reported floating four
   times and fixed four times AT THE BAR. This is the same bug fixed at the
   measurement that causes it.

   Waived on the ITEMS rather than written as `minmax(0, 1fr)` across a dozen
   templates, because it is the item's automatic minimum size that is being
   given up and one rule is one place to read it. Wherever there is room it
   changes nothing at all — which is every desktop width, and why the
   screenshots in tests/visual.mjs do not move. */
.grid-2 > *, .grid-3 > *, .fin-grid > *, .stat-row > *, .glance > *,
.camp-grid > *, .camp-agg > *, .fh-grid > *, .bk-summary > *,
.cat-manage > *, .compose-grid > *, .door-tiles > *,
.gallery-grid > *, .album-grid > * { min-width: 0; }

/* --------------------------------- cards -------------------------------- */
.card {
  background: var(--card);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  border: 1px solid rgba(15, 42, 67, 0.06);
  overflow: hidden;
}
.card .pad { padding: var(--s5); }
/* Hover feedback is a promise of interaction — a read-only card doesn't make
   it. Only cards that really open something raise on hover. */
.card.clickable:hover { box-shadow: var(--shadow-lg); }

/* ------------------------- drawn icon set --------------------------------
   One monoline family: 24px grid, 1.6px stroke, currentColor. Every icon is
   <svg class="ic"><use href="#i-name"/></svg> against the sprite injected at
   the top of board.html — restyle or swap a glyph there and it changes
   everywhere. Emoji remain only where they are content (a birthday note, a
   member-chosen category icon), never furniture. */
svg.ic {
  width: 1.15em; height: 1.15em; flex: none;
  stroke: currentColor; fill: none; stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round;
  vertical-align: -0.2em;
}

/* ------------------------- status pills ----------------------------------
   The one way status is written anywhere in the portal. */
.pill {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; font-weight: 700; padding: 3px 10px; border-radius: 999px;
  white-space: nowrap;
}
.pill .dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: none; }
.pill.ok   { background: var(--good-tint);   color: #14603C; }
.pill.warn { background: var(--gold-tint);   color: #7A5F0B; }
.pill.bad  { background: var(--danger-tint); color: #8E2B22; }
.pill.info { background: var(--harbor-tint); color: var(--navy); }

.event-card { display: flex; gap: 18px; padding: 22px; align-items: flex-start; }
.date-block {
  flex: none;
  background: var(--navy);
  color: #fff;
  border-radius: 10px;
  text-align: center;
  padding: 10px 16px;
  line-height: 1.1;
  min-width: 68px;
}
.date-block .mo { font-size: 12px; letter-spacing: 0.16em; color: var(--gold); font-weight: 700; }
.date-block .da { font-size: 27px; font-weight: 700; }
.tag {
  display: inline-block;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 3px 10px;
  border-radius: 12px;
  margin-bottom: 6px;
}
.tag-Fundraiser { background: #F5EBCB; color: #7A6110; }
.tag-Lessons { background: #DCE8F5; color: #29517A; }
.tag-Social { background: #DDEFE6; color: #1E6B46; }
.tag-Trip { background: #EFE0F0; color: #7A3B7E; }
.event-card h3 { font-size: 20px; margin-bottom: 4px; }
.event-card .meta { font-size: 15px; color: var(--muted); }
.event-card .desc { font-size: 16px; color: var(--muted); margin-top: 8px; }

/* -------------------------------- footer -------------------------------- */
.site-footer { background: var(--navy-deep); color: #A9B8C8; margin-top: 80px; }
.site-footer .cols {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr;
  gap: 36px;
  padding: 54px 0 36px;
}
@media (max-width: 800px) { .site-footer .cols { grid-template-columns: 1fr; } }
.site-footer h4 {
  color: #fff;
  font-family: var(--serif);
  font-size: 18px;
  margin-bottom: 14px;
}
.site-footer a { color: #C7D3E0; text-decoration: none; }
.site-footer a:hover { color: var(--gold); }
.site-footer ul { list-style: none; }
.site-footer li { margin-bottom: 9px; font-size: 16px; }
/* ------------------------------- Facebook ------------------------------- */
/* The club's Facebook page is where the day-to-day conversation actually
   happens, so it gets a real target rather than a line of body copy. The mark
   is drawn in currentColor, not Facebook blue: #1877F2 goes muddy on the navy
   footer, and the badge reads more clearly in the club's own gold. */
.fb-follow {
  display: inline-flex;
  align-items: center;
  gap: 11px;
  margin-top: 4px;
  padding: 9px 20px 9px 11px;
  border: 1.5px solid rgba(255, 255, 255, 0.24);
  border-radius: 30px;
  color: #DCE5EE;
  font-size: 15.5px;
  font-weight: 700;
  text-decoration: none;
  transition: border-color 0.16s, background 0.16s, color 0.16s, transform 0.16s;
}
.fb-follow .fb-mark { width: 26px; height: 26px; flex: none; fill: currentColor; display: block; }
.site-footer .fb-follow:hover {
  background: rgba(255, 255, 255, 0.08);
  border-color: var(--gold);
  color: var(--gold);
  transform: translateY(-1px);
}
.fb-follow:focus-visible { outline: 3px solid var(--gold); outline-offset: 3px; }

/* header: icon only, sized to the 48px minimum target the rest of the nav keeps */
.main-nav a.nav-social {
  display: grid;
  place-items: center;
  flex: none;              /* the mobile nav is a nowrap scroll strip — don't squash */
  width: 44px;
  height: 44px;
  padding: 0;
  border-radius: 50%;
  color: rgba(255, 255, 255, 0.88);
}
.main-nav a.nav-social svg { width: 22px; height: 22px; fill: currentColor; display: block; }
.main-nav a.nav-social:hover { background: rgba(255, 255, 255, 0.12); color: var(--gold); }

.site-footer .legal {
  border-top: 1px solid rgba(255, 255, 255, 0.12);
  padding: 20px 0 26px;
  font-size: 14px;
  display: flex;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}

/* ------------------------------ forms ----------------------------------- */
label { display: block; font-size: 15px; font-weight: 700; color: var(--label-ink); margin: 16px 0 6px; }
/* Listed by exclusion rather than by name: a typed allowlist silently drops any
   field whose type isn't on it, and that is how the join form's phone number
   ended up as a bare browser default sitting under two styled fields. */
input:where(:not([type="checkbox"], [type="radio"], [type="range"], [type="hidden"], [type="submit"], [type="button"])),
select, textarea {
  width: 100%;
  font-size: 17px;
  font-family: var(--sans);
  padding: 12px 14px;
  border: 1.5px solid var(--line);
  border-radius: 9px;
  background: #fff;
  color: var(--ink);
}
input:focus, select:focus, textarea:focus {
  outline: 3px solid rgba(62, 108, 147, 0.35);
  border-color: var(--harbor);
}
textarea { min-height: 110px; resize: vertical; }

/* ---- password fields: the show/hide eye (see addPasswordEye in app.js) ----
   The button sits *inside* the field's box rather than beside it, so the
   field keeps its full width on a phone; the input is given room on its
   right so a long password never runs under the eye. 44px is the tap target
   an older member using this at the door needs. */
.pw-field { position: relative; }
.pw-field input { padding-right: 52px; }
.pw-eye {
  position: absolute;
  top: 0;
  right: 0;
  height: 100%;
  width: 46px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted);
  cursor: pointer;
  border-radius: 0 9px 9px 0;
}
.pw-eye svg { width: 22px; height: 22px; display: block; }
.pw-eye:hover { color: var(--navy); }
/* Its own focus ring: the input's outline is drawn on the input, and a
   keyboard user tabbing onto the eye would otherwise see nothing move. */
.pw-eye:focus-visible {
  outline: 3px solid rgba(62, 108, 147, 0.35);
  outline-offset: -3px;
  color: var(--navy);
}
/* Revealed is a state worth seeing at a glance — it is the one state in
   which somebody standing behind you can read the password. */
.pw-eye[aria-pressed="true"] { color: var(--harbor); }

.notice { padding: 12px 16px; border-radius: 9px; font-size: 15.5px; margin: 14px 0; }
.notice-err { background: #FBEAE8; color: #8C2B22; border: 1px solid #EFC7C2; }
.notice-ok { background: #E4F3EB; color: #16603C; border: 1px solid #BFE3CF; }
/* A doubt, not a refusal. Used where the portal has found something worth
   stopping for but is not certain enough to refuse — an address that looks
   like a typo, above all. Amber deliberately sits between the two: a red
   notice reads as "this failed", and this has not failed yet. Never the only
   channel — every use of it carries wording and a button. */
.notice-warn { background: #FCF3DE; color: #6E5A0F; border: 1px solid #E7D49B; }

/* --------------------------------------------------------------- how-to --
   Basic instructions for a board screen.

   These screens used to open with two or three paragraphs of small grey prose
   explaining the reasoning behind the feature — 1,372 words of it across the
   portal. The reasoning was good and it was the wrong thing to put between a
   volunteer and the errand they came to do.

   The first attempt at replacing it failed for an instructive reason: it was
   shorter, but it still was not instructions. "Tick a box to give a duty" is a
   description written in this application's own voice — clipped, editorial, and
   using the vocabulary of the code ("duty", "role") rather than the vocabulary
   of the person reading it. Board feedback, verbatim: *what we really need is
   basic instructions, and they don't read like basic instruction.*

   So the rules for anything in here:

     · NUMBERED STEPS, because a procedure looks like a procedure.
     · START WHERE THEY ARE — "find the person's name in the list below".
     · GOAL FIRST, THEN ACTION — "To change their job title — use the…".
     · THE READER'S WORDS, NOT THE CODE'S. Click, not tick. Job title, not
       role. Never "duty", "capability", "record" or "entity".
     · NO EMOJI. This board asked for them to go; they read as decoration to
       people who have been reading real instructions their whole lives.

   Folded away by default. A screen that permanently explains itself looks like
   a screen with a problem — the board said so — and anything actually wrong
   gets its own notice above this, naming that one thing and how to fix it. */
/* --------------------------------------------------------- the link sheet --
   The window that says a sign-in link has been sent.

   It exists because the confirmation used to be written into a strip further
   up the page — off screen on a phone, and always away from the button that
   was pressed. "The interface is a little bit stale, and you may not realize
   something has happened yet." A control whose effect is invisible gets
   pressed twice, and the second press kills the link the first one made. */
.link-layer .link-sheet {
  background: var(--card); color: var(--ink);
  border-radius: 14px; padding: 26px 26px 22px;
  width: min(560px, calc(100vw - 32px)); max-height: calc(100vh - 64px); overflow-y: auto;
  box-shadow: 0 18px 50px rgba(0,0,0,.28);
}
.link-layer .link-sheet h2 { margin: 0 0 14px; font-size: 21px; color: var(--ink); }
.link-layer .ls-note { font-size: 15.5px; line-height: 1.5; color: var(--muted); margin: 12px 0 14px; }
.link-layer .ls-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.link-layer .ls-row input {
  flex: 1 1 240px; min-width: 0; font-size: 14px; padding: 10px 11px;
  border: 1px solid var(--line); border-radius: 8px;
  background: var(--field-bg, #fff); color: var(--ink);
}
.link-layer .ls-done { margin-top: 18px; }
.link-layer .notice { margin: 0 0 12px; }

.howto { margin: 2px 0 18px; max-width: 720px; }
.howto > summary {
  cursor: pointer; list-style: none; display: inline-flex; align-items: center; gap: 8px;
  min-height: 44px; font-size: 16px; font-weight: 600; color: var(--harbor);
}
.howto > summary::-webkit-details-marker { display: none; }
/* DRAWN, NOT TYPED. This was the character ▸ at 13px, which is a hairline on a
   phone and invisible to the eyes this portal is actually read by — the board
   asked for it bigger in those words. A glyph cannot be made reliably heavier
   (▸ has no bold, and ▶ turns into a colour emoji on some phones), so it is a
   solid triangle drawn in CSS: it inherits the colour, it cannot be swapped for
   an emoji by the operating system, and its size is ours to set. */
.howto > summary::before {
  content: ""; flex: 0 0 auto; width: 0; height: 0;
  border-left: 13px solid currentColor;
  border-top: 9px solid transparent;
  border-bottom: 9px solid transparent;
  transition: transform .15s;
}
.howto[open] > summary::before { transform: rotate(90deg); }
.howto-steps {
  margin: 4px 0 0; padding: 0 0 0 26px;
  display: grid; gap: 13px;
  font-size: 17px; line-height: 1.5; color: var(--ink);
}
.howto-steps li { padding-left: 4px; }
.howto-steps li::marker { font-weight: 700; color: var(--harbor); }
/* ------------------------------------------------ saying it was saved --
   Beside the button, and it does not go away on a timer. A board member on a
   long form presses Save at the bottom and looks at the bottom; a "Saved" at
   the top of the card is a message nobody sees, which is exactly how it was
   reported: "it's just a button, and you don't understand if it helped". */
.saverow { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; margin-top: 20px; }
.saidsaved[hidden] { display: none; }
.saidsaved .ok {
  display: inline-flex; align-items: center; gap: 8px;
  color: var(--good); font-weight: 600; font-size: 16px;
}
.saidsaved .ic { width: 20px; height: 20px; }

/* --------------------------------------------------- bringing in a roster --
   Three stages with their state on screen, because the three questions a
   person actually has are "did it take my file", "did it read it", and "is it
   finished" — and every earlier version of this panel answered all three only
   by implication. The strip at the top is the answer to all three at once.

   Colour is never the only channel here, for the same reason it is not on the
   burn-rate bars: the step that is finished carries a tick, the one in hand
   carries its number in a filled disc, and each one says its state in a word. */
.imp-head { display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap; }
.imp-head h3 { margin: 0; }
.imp-head .btn { margin-left: auto; }

.imp-steps {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px;
  margin: 18px 0 20px; padding: 0; list-style: none; counter-reset: none;
}
.imp-steps li {
  display: grid; grid-template-columns: auto 1fr; grid-template-rows: auto auto;
  gap: 2px 10px; align-items: center;
  padding: 12px 14px; border-radius: var(--radius);
  background: var(--surface-2); border: 1px solid var(--line);
}
.imp-steps .n {
  grid-row: 1 / 3; width: 30px; height: 30px; border-radius: 50%;
  display: grid; place-items: center; font-weight: 700; font-size: 15px;
  background: var(--line); color: var(--muted);
}
.imp-steps .t { font-weight: 600; font-size: 15px; color: var(--muted); line-height: 1.25; }
.imp-steps .s { font-size: 13px; color: var(--muted); }
/* In hand. */
.imp-steps li.now { border-color: var(--gold); background: var(--gold-tint); }
.imp-steps li.now .n { background: var(--gold); color: var(--navy); }
.imp-steps li.now .t { color: var(--ink); }
.imp-steps li.now .s { color: var(--ink); font-weight: 600; }
/* Finished. The tick is what says so where colour cannot. */
.imp-steps li.done .n { background: var(--good); color: #fff; font-size: 0; }
.imp-steps li.done .n::after { content: "✓"; font-size: 17px; line-height: 1; }
.imp-steps li.done .t { color: var(--ink); }
.imp-steps li.done .s { color: var(--good); font-weight: 600; }
@media (max-width: 700px) { .imp-steps { grid-template-columns: 1fr; gap: 8px; } }

.imp-lead { font-size: 17px; line-height: 1.5; color: var(--ink); margin: 0 0 16px; max-width: 62ch; }
.imp-note { font-size: 14px; color: var(--muted); margin: 10px 0 0; }
.imp-act { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; margin-top: 16px; }
/* A `display` of its own beats the browser's own [hidden] rule, which is only
   a UA default — so setting .hidden on this row left it fully on screen while
   every property said otherwise. Anything given a display here has to say what
   hidden means for it. */
/* ONE RULE FOR THE WHOLE PANEL, rather than one per element.
   [hidden] is only a UA default, so anything given a display of its own —
   .imp-act is flex, .btn is inline-flex — renders straight through it. That
   caught me twice: a Check button and an Undo button both fully on screen
   while every property said they were hidden, and both times the assertion
   agreed with the property rather than with the screen. */
.imp [hidden], .imp-stage[hidden] { display: none !important; }
.imp-file { font-size: 17px; font-weight: 600; color: var(--ink); margin: 0; }

/* THE BOX. A bare <input type="file"> is drawn by the browser as a small grey
   "Choose File" beside "No file chosen", and nothing about it says a file may
   be dropped on it — because on a bare input, one may not.

   The button inside it is the path, not the ornament: clicking works in every
   browser and every hand, and dragging does not. That order is the reverse of
   what shipped first, and it is the lesson of four attempts. */
.dropzone {
  display: grid; place-items: center; gap: 10px;
  padding: 34px 20px; text-align: center;
  border: 2px dashed var(--line); border-radius: var(--radius);
  background: var(--surface-2);
  transition: border-color .15s, background .15s;
}
/* Solid, not dashed: the border stops being an invitation and becomes a
   boundary the moment there is something to drop inside it. */
.dropzone.over { border-style: solid; border-color: var(--gold); background: var(--gold-tint); }
.dz-sub { margin: 0; font-size: 14px; color: var(--muted); }
/* The name of the file that was taken, and — in the danger colour — a drop
   that carried nothing usable. Directly under the box, because with the box
   filling a laptop screen anything further down is a sentence nobody reads. */
.dz-picked { margin: 12px 0 0; font-size: 16px; color: var(--ink); }
.dz-picked.bad { color: var(--danger); font-weight: 600; }

/* The folded halves of the check: what was skipped, and the names that would
   arrive. Both are the audit rather than the answer — a club's file is fifty
   people, and fifty rows between the verdict and the button that acts on it is
   how the button stops existing as far as anybody is concerned. */
.imp-fold { margin: 14px 0 0; }
.imp-fold > summary { font-size: 15px; }

.imp-check {
  display: flex; align-items: center; gap: 10px; margin: 12px 0 0;
  font-size: 16px; color: var(--ink); cursor: pointer; min-height: 44px;
}
.imp-check input { width: 20px; height: 20px; }
.imp-last {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin: 16px 0 0; padding: 12px 14px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface-2); font-size: 15px; color: var(--muted);
}
.imp-aside { font-size: 16px; font-weight: 400; color: var(--muted); }
.imp-done { font-size: 19px; font-weight: 600; color: var(--ink); line-height: 1.5; }
.imp-done .n { color: var(--good); }

/* ------------------------------------------------------------- mail log --
   The record of what the club has sent, folded away.

   It is a book of record rather than a daily read — somebody opens it when
   they are chasing one particular message, which is rare — and left open it
   pushed the four tiles that answer the everyday questions off the top of the
   screen. So it folds, and its summary carries the one fact worth a glance:
   what went out last, and whether it arrived.

   Nothing that is WRONG lives in here. Failures gather in the trouble panel
   above, which draws only when there is something to say and never folds. */
.maillog > summary {
  cursor: pointer; list-style: none;
  display: flex; align-items: baseline; gap: 12px; flex-wrap: wrap;
  min-height: 44px;
}
.maillog > summary::-webkit-details-marker { display: none; }
.maillog > summary::before {
  content: ""; flex: 0 0 auto; width: 0; height: 0; align-self: center;
  border-left: 13px solid var(--harbor);
  border-top: 9px solid transparent;
  border-bottom: 9px solid transparent;
  transition: transform .15s;
}
.maillog[open] > summary::before { transform: rotate(90deg); }
.maillog .ml-head { font-family: var(--serif, Georgia, serif); font-size: 20px; font-weight: 700; color: var(--ink); }
.maillog .ml-latest { font-size: 14.5px; color: var(--muted); }
.maillog .ml-latest b { font-weight: 600; color: var(--ink); }

.howto-note {
  margin: 14px 0 0; font-size: 16px; line-height: 1.5; color: var(--muted);
}
.addr-fix { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }

/* --------------------------- calendar (events) --------------------------- */
.cal-toolbar { display: flex; align-items: center; gap: 16px; margin-bottom: 22px; flex-wrap: wrap; }
.cal-toolbar h2 { min-width: 240px; }
.cal-nav-btn {
  width: 48px; height: 48px;
  border-radius: 50%;
  border: 2px solid var(--navy);
  background: #fff;
  color: var(--navy);
  font-size: 20px;
  cursor: pointer;
  font-weight: 700;
}
.cal-nav-btn:hover { background: var(--navy); color: #fff; }
.cal-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 6px;
  background: transparent;
}
.cal-dow {
  text-align: center;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.1em;
  color: var(--muted);
  padding: 8px 0;
  text-transform: uppercase;
}
.cal-cell {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 9px;
  min-height: 96px;
  padding: 8px;
  font-size: 14px;
}
.cal-cell.dim { background: transparent; border-color: transparent; }
.cal-cell .dnum { font-weight: 700; color: var(--muted); font-size: 14px; }
.cal-cell.today { border: 2px solid var(--gold); }
.cal-cell.today .dnum { color: var(--gold-deep); }
.cal-evt {
  display: block;
  margin-top: 5px;
  background: var(--navy);
  color: #fff;
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1.25;
  padding: 5px 7px;
  border-radius: 6px;
  cursor: pointer;
  text-decoration: none;
}
.cal-evt:hover { background: var(--harbor); color: #fff; }
.cal-evt.c-Fundraiser { background: var(--gold-deep); }
.cal-evt.c-Lessons { background: var(--harbor); }
.cal-evt.c-Trip { background: #7A3B7E; }
@media (max-width: 760px) { .cal-grid { display: none; } .cal-toolbar .cal-nav-btn { display: none; } }

/* ------------------------------- gallery -------------------------------- */
.gallery-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px; }
@media (max-width: 900px) { .gallery-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 600px) { .gallery-grid { grid-template-columns: 1fr; } }
.photo-card img { width: 100%; aspect-ratio: 10/7; object-fit: cover; }
.photo-card .pad { padding: 16px 18px; }
.photo-card .t { font-weight: 700; font-size: 17px; color: var(--navy); }
.photo-card .c { font-size: 14.5px; color: var(--muted); margin-top: 2px; }

/* ---- gallery: the event index -------------------------------------------
   A flat grid gave every photo equal weight, so one big night buried six
   other events. Browsing by evening restores the shape of the club's year;
   "All Photos" keeps the flat view one click away. */
.gal-modes { display: flex; gap: 6px; background: var(--harbor-tint); padding: 5px; border-radius: 12px; margin: 4px 0 24px; width: fit-content; }
.gal-modes button {
  font-family: var(--sans); font-size: 14.5px; font-weight: 700;
  color: var(--muted); background: none; border: none;
  padding: 9px 17px; border-radius: 9px; cursor: pointer; white-space: nowrap;
}
.gal-modes button:hover { color: var(--navy); }
.gal-modes button.active { background: var(--surface); color: var(--ink); box-shadow: 0 1px 4px rgba(15,42,67,.16); }
/* The container is `--harbor-tint`, which the dark block remaps to a dark
   navy — so the pill on top of it has to move as well, or the one thing
   marking which view is selected stays a white chip on a dark bar. Same
   treatment the portal's own segmented control already carries. */
:root.theme-dark .gal-modes button.active { background: #1E3145; color: #E7EDF4; box-shadow: none; }

.album-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px; }
@media (max-width: 900px) { .album-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 600px) { .album-grid { grid-template-columns: 1fr; } }
.album-card {
  display: block; text-align: left; padding: 0; cursor: pointer;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  overflow: hidden; box-shadow: var(--shadow); font: inherit;
  transition: transform .22s, box-shadow .22s;
}
.album-card:hover { transform: translateY(-5px); box-shadow: var(--shadow-lg); }
.album-card .cover { display: block; position: relative; }
.album-card .cover img { display: block; width: 100%; aspect-ratio: 16/10; object-fit: cover; }
.album-card .cover .count {
  position: absolute; right: 10px; bottom: 10px;
  background: rgba(10,29,48,.86); color: #fff;
  font-size: 12px; font-weight: 700; padding: 4px 11px; border-radius: 999px;
}
.album-card .meta { display: block; padding: 14px 16px 16px; }
.album-card .meta .t { display: block; font-family: var(--serif); font-size: 18.5px; color: var(--navy); font-weight: 600; line-height: 1.25; }
.album-card .meta .d { display: block; font-size: 14px; color: var(--muted); margin-top: 3px; }

.album-crumb { display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap; margin-bottom: 18px; }
.album-crumb .back {
  font: inherit; font-size: 14.5px; font-weight: 700; cursor: pointer;
  background: #fff; border: 1.5px solid var(--line); color: var(--harbor);
  padding: 8px 15px; border-radius: 9px;
}
.album-crumb .back:hover { border-color: var(--harbor); color: var(--navy); }
.album-crumb .t { font-family: var(--serif); font-size: 22px; color: var(--navy); }
.album-crumb .n { font-size: 14px; color: var(--muted); }

/* ABOVE EVERYTHING. It sat at 200 — below the drawers (301), the command
   palette (400) and Door Mode (511) — which held only for as long as nothing
   inside any of those had a picture in it. A photograph opened from a drawer
   would have been drawn underneath the drawer that opened it. */
.lightbox {
  position: fixed; inset: 0; z-index: 600;
  background: rgba(10, 29, 48, 0.92);
  display: flex; align-items: center; justify-content: center;
  padding: 30px; flex-direction: column; gap: 14px;
}
.lightbox img { max-width: min(1000px, 92vw); max-height: 78vh; border-radius: 10px; }
.lightbox .cap { color: #fff; font-size: 17px; text-align: center; }
/* The way out. It sits over a dark backdrop on a photograph and over a white
   card on a modal, so it carries its own disc — a bare white glyph is
   invisible on half the layers it appears on. 46px because it is the control
   somebody reaches for one-handed, in a hurry, having got stuck. The inset is
   measured rather than assumed: an installed portal draws under the status bar
   on a notched phone, and a × behind the clock cannot be pressed. */
.lightbox .close {
  position: absolute; z-index: 2;
  top: max(14px, env(safe-area-inset-top)); right: max(14px, env(safe-area-inset-right));
  width: 46px; height: 46px; border-radius: 50%; padding: 0;
  display: flex; align-items: center; justify-content: center;
  background: rgba(10, 29, 48, 0.82);
  border: 1.5px solid rgba(255, 255, 255, 0.38); color: #fff;
  font-size: 30px; line-height: 1; cursor: pointer; backdrop-filter: blur(6px);
}
.lightbox .close:hover { background: var(--navy-deep); border-color: #fff; }
/* The page behind a layer must not scroll — a photograph filling an iPhone
   with the roster still moving underneath reads as the app having broken. */
body.overlay-open { overflow: hidden; }
/* A picture that opens somewhere is a picture that says so. */
[data-view], a[href$=".jpg"] > img, a[href$=".jpeg"] > img,
a[href$=".png"] > img, a[href$=".webp"] > img, a[href$=".heic"] > img { cursor: zoom-in; }

/* ============================= board portal ==============================
   Two levels of navigation, deliberately built as different species:

     level 1  .portal-rail   which SECTION of the portal — vertical, dark
     level 2  .subnav        which VIEW of that section — horizontal, light

   Because they run on different axes they cannot be confused with each other,
   and a vertical list has no width limit — adding a tenth section is a line of
   markup, not another navigation redesign. Below 760px the rail becomes the
   fixed bottom tab bar (see the phone block near the end of this file). */

/* THE RAIL IS ANCHORED TO THE EDGE OF THE SCREEN, NOT FLOATED IN A COLUMN.
   It used to be a rounded navy card sitting in the first cell of a two-column
   grid inside the 1340px wrap: it began 24px in from the page's own gutter,
   ended where its list of sections ended, and took a 30px grid gap out of the
   content beside it. That is three separate strips of screen spent on the
   frame around a menu rather than on the register, the roster or the sheet
   the board actually came to read.

   Fixed to the left edge instead, full height, square: the width it occupies
   is the width of the menu itself, and the content column keeps everything
   else. It is `fixed` rather than `sticky` because a full-height side menu
   must not scroll away with the page — that is the whole difference between
   an anchored menu and a card that happens to stay put for a while.

   The geometry lives in the min-width block below, NOT here: at 1024px and
   under this same markup is the bottom tab bar, and a width or a height set
   at the top level would follow it there. */
.portal-shell { display: block; }
body[data-page="board"] #portal-view > .wrap { max-width: 1340px; }

.portal-rail {
  background: var(--navy);
  padding: 16px 12px 18px;
  display: flex; flex-direction: column; gap: 3px;
}

@media (min-width: 1025px) {
  .portal-rail {
    position: fixed; left: 0; bottom: 0;
    width: var(--rail-w);
    /* THE HEADER AND THE MENU ARE ONE PIECE OF NAVY, AND TWO THINGS WERE
       DRAWING A LINE BETWEEN THEM.

       `--header-h` is rounded UP from a header that really measures 74.39px,
       so `top: var(--header-h)` left six tenths of a pixel of the page's own
       paper showing through — a pale hairline the full width of the menu. The
       measurement is rounded up on purpose (see setHeaderHeight in app.js: a
       header that rounds DOWN lets the page show through the other way, under
       everything that slides beneath it), so the pixel is given back here.

       And the header's drop shadow was falling down the first 14px of the
       menu, banding it darker than the rest. Above the header rather than
       under it, the menu takes that shadow underneath itself instead. They
       never overlap by more than the pixel above, and it is the same navy on
       both sides of it, so nothing is hidden by the swap. */
    top: calc(var(--header-h, 96px) - 1px);
    z-index: 51;
    /* 12px at the sides, not the 14 this rule was written with. Those two
       pixels a side were the difference between "Members & Email" fitting
       beside a two-figure badge and being squeezed until it broke over two
       lines — the row has 184px of usable width and wants 188. Nothing else in
       the menu is near the edge, so there is no reason to spend them. */
    padding: 16px 12px 18px;
    /* Nine sections, a brand block and the account do not fit under the header
       on a short laptop window. The menu takes its own scrollbar rather than
       hiding whichever section falls off the bottom. The room at the foot is
       for the account, which is pinned over it — see below. */
    overflow-y: auto; overscroll-behavior: contain;
    padding-bottom: 74px;
    scrollbar-width: thin; scrollbar-color: rgba(255, 255, 255, 0.28) transparent;
    /* A hairline, not the card's drop shadow: an edge that runs the whole
       height of the screen reads as a smear if it is soft. */
    box-shadow: 1px 0 0 rgba(10, 29, 48, 0.3);
  }
  /* THE ACCOUNT IS PINNED TO THE FOOT OF THE SCREEN, AND `fixed` IS LOAD-BEARING.
     The rail is now as tall as the screen, so "last in the list" and "at the
     bottom" stopped being the same place — but `margin-top: auto` inside the
     scroll box was not the answer: a scroll box clips what its children draw
     outside it, and the account's popover is 250px wide inside a 236px rail.
     It opened with Light/Dark and half of Sign Out sliced off at the rail's
     edge — the menu holding the way out of the portal.

     A fixed element's containing block is the viewport, so it hangs outside
     the scroll box's clip and its popover comes with it. The rail reserves
     this row's height above, or a scrolled menu's last section would sit
     underneath it.

     The child selector is not decoration: `.rail-account-wrap { position:
     relative }` is declared further down this file, so a bare class here loses
     the tie on source order and the wrap stays in the scroll box. */
  .portal-rail > .rail-account-wrap {
    position: fixed; left: 0; bottom: 0; width: var(--rail-w);
    padding: 8px 14px 18px; background: var(--navy);
    border-top: 1px solid rgba(255, 255, 255, 0.1);
  }

  /* The room the menu occupies, reserved by the portal rather than by a grid
     column, because the menu is out of flow. Scoped to the board page: the
     rail exists on no other. */
  body[data-page="board"] #portal-view { padding-left: var(--rail-w); }
  /* 24px of gutter against a full-height navy wall is a crease, not a margin. */
  body[data-page="board"] #portal-view > .wrap { padding: 0 32px; }
  /* The footer is outside the portal section and the menu is fixed over it, so
     it needs the same inset — but only while somebody is signed in and the menu
     is actually drawn. `portal-open` is set by boot() in board.js.

     ON THE FOOTER, NOT ON ITS WRAP. The wrap is what holds the footer's own
     max-width, so padding there came out of the columns rather than out of the
     page: the three of them lost 212px between them and the club's mission
     statement started wrapping where it never used to. Insetting the footer
     itself leaves the columns exactly the width they have always been, sitting
     centred in what is left of the page. */
  body[data-page="board"].portal-open .site-footer { padding-left: var(--rail-w); }
}
/* A label, not a lockup — see the comment on it in board.html. The steel is
   the rail's own quiet colour, already carrying the account's role line, at
   5.6:1 on the navy; the tracking and the size are the breadcrumb's, so the
   thing naming the menu and the thing naming the pane read as one family. */
.rail-brand {
  font-size: 11px; font-weight: 800;
  letter-spacing: .15em; text-transform: uppercase; color: #8FA5BC;
  padding: 3px 12px 13px; margin-bottom: 10px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.14);
}

/* Direct children (+ the account trigger) only: the account MENU also lives
   inside the rail, and its own buttons must not inherit tile styling. */
.portal-rail > button, .rail-account-wrap > button {
  font-family: var(--sans); font-size: 14.5px; font-weight: 600;
  text-align: left; color: #BCCBDA; background: none; border: none;
  padding: 11px 12px; border-radius: 9px; cursor: pointer;
  display: flex; gap: 11px; align-items: center; width: 100%;
}
.portal-rail > button svg.ic, .rail-account-wrap > button svg.ic { width: 18px; height: 18px; }
.portal-rail > button:hover, .rail-account-wrap > button:hover { background: rgba(255, 255, 255, 0.08); color: #fff; }
.portal-rail > button.active { background: #fff; color: var(--navy); font-weight: 700; box-shadow: 0 3px 12px rgba(0, 0, 0, 0.22); }
.portal-rail hr { border: none; border-top: 1px solid rgba(255, 255, 255, 0.13); margin: 8px 4px; }

/* what is waiting in a section, visible without opening it */
.portal-rail .badge {
  margin-left: auto; flex: none;
  background: var(--gold); color: #3A2E05;
  font-size: 10.5px; font-weight: 800;
  border-radius: 999px; padding: 1px 7px; min-width: 19px; text-align: center;
}
.portal-rail .badge:empty { display: none; }
.portal-rail button.active .badge { background: var(--navy); color: #fff; }

/* the section header that replaces the old "Welcome, …" block */
.pane-head { display: flex; gap: 20px; align-items: flex-start; flex-wrap: wrap; margin-bottom: 22px; }
.pane-head .ph-text { flex: 1; min-width: 260px; }
.pane-head .crumb { font-size: 12.5px; letter-spacing: .16em; text-transform: uppercase; font-weight: 800; color: var(--gold-deep); margin-bottom: 5px; }
.pane-head h2 { font-size: clamp(26px, 3.2vw, 34px); }
.pane-head p { color: var(--muted); font-size: 16px; margin-top: 3px; }
/* the account — foot of the rail on desktop, last tile of the bottom bar on
   phones. The menu opens UPWARD from its button (the button sits at the bottom
   of both), holding who is signed in, appearance, density, and Sign Out. */
.rail-account-wrap { position: relative; display: block; }
.rail-account-wrap .ph-menu { top: auto; bottom: calc(100% + 8px); left: 8px; right: auto; z-index: 90; }
.acct-id { display: block; padding: 8px 12px 10px; border-bottom: 1px solid var(--hairline); margin-bottom: 6px; }
.acct-id b { display: block; color: var(--navy); font-size: 15px; }
.acct-id span { font-size: 13px; color: var(--muted); }

/* level 2 — a segmented control, never a tab strip */
.subnav {
  display: flex; gap: 5px; flex-wrap: wrap;
  background: var(--harbor-tint); padding: 5px; border-radius: 12px;
  margin: 0 0 24px;
}
.subnav button {
  font-family: var(--sans); font-size: 14.5px; font-weight: 700;
  color: var(--muted); background: none; border: none;
  padding: 9px 17px; border-radius: 9px; cursor: pointer; white-space: nowrap;
}
.subnav button:hover { color: var(--navy); }
.subnav button.active { background: #fff; color: var(--navy); box-shadow: 0 1px 4px rgba(15, 42, 67, 0.16); }
/* a view can carry a count — work waiting behind a tab must not be hidden by
   the fact that it is behind a tab */
.subnav .badge {
  display: inline-block; margin-left: 7px;
  background: var(--gold); color: #3A2E05;
  font-size: 10.5px; font-weight: 800; vertical-align: 1px;
  border-radius: 999px; padding: 1px 7px; min-width: 19px; text-align: center;
}
.subnav .badge:empty { display: none; }

/* ------------------------------- Today -----------------------------------
   The portal's front door: what is waiting on the board, each row a fact
   paired with the button that resolves it. Rows disappear when handled —
   the screen is self-emptying, and an empty Today reads like a good day. */

/* THE ANSWER BLOCK, above the list. A volunteer has ninety seconds before a
   meeting; this states the answer so the figures beneath become evidence for a
   sentence rather than a pile to be assembled.

   ONE HUE, DELIBERATELY. Everything here is white at four weights mixed from
   #fff — never from a surface token, which a dark block remaps and would
   invert the pair. The only colour is the accent, and it marks the two things
   that lead somewhere.

   There is no meter. A row of gold-and-grey segments with its meaning in a
   `title` is unreadable on a phone at any contrast; the watch rows say the
   same thing in words, and a wordless second statement of one fact is two
   statements waiting to disagree. */
.answer { background: linear-gradient(168deg, var(--navy-deep), var(--navy));
  color: #fff; border-radius: 14px; padding: 20px 22px 18px; margin-bottom: var(--s4);
  box-shadow: var(--shadow); }
.answer .vhead { font-family: var(--serif); font-size: 25px; line-height: 1.26; letter-spacing: -.01em;
  margin: 0; color: #fff; text-wrap: balance; }

/* ONE ROW, ONE STATEMENT. A label, the figure, and what qualifies it, so a
   spendable balance and a reserve nobody may touch can never be read as one
   sum the way they would be with a middle dot as the only thing between them.

   Held to a measure: the block is as wide as the pane, and at 1280 a label on
   the far left with its figure on the far right is a journey the eye makes
   twice per row. The sentence above still runs the full width — it is a
   sentence. */
.answer .vfigs, .answer .vwatch { max-width: 760px; }
.answer .vfigs { margin: 14px 0 0; border-top: 1px solid rgba(255,255,255,.16); }
.answer .vfig { display: flex; align-items: baseline; gap: 12px; padding: 9px 0;
  border-bottom: 1px solid rgba(255,255,255,.10); }
.answer .vfig .fl { flex: 1 1 auto; min-width: 0; font-size: 14.5px;
  color: color-mix(in srgb, #fff 78%, var(--navy)); }
.answer .vfig .fv { font-size: 17px; font-weight: 700; color: #fff;
  font-variant-numeric: tabular-nums; white-space: nowrap; }
.answer .vfig .fq { flex: 0 0 auto; min-width: 118px; text-align: right; font-size: 13px;
  white-space: nowrap; color: color-mix(in srgb, #fff 62%, var(--navy)); }

/* The things to watch. As many rows as the headline counts, each a button to
   the screen that owns it — a count a board member cannot reach is a count
   they learn to scroll past.

   THE ACCENT IS `--gold`, NOT A LIFTED VARIANT. This club's gold on its navy
   measures 6.06:1, which carries small text on its own; the lifted token
   exists in the other installation because ITS gold is 3.16:1 there. Importing
   the lift would be carrying somebody else's fix for a problem this palette
   does not have. */
.answer .vwatch { margin: 15px 0 0; }
.answer .wlab { margin: 0 0 6px; font-size: 11px; font-weight: 800; letter-spacing: .1em;
  text-transform: uppercase; color: var(--gold); }
/* Said ONCE beside the list, never inside a row: it is one constant for every
   line on the screen, and inside a row it is what makes the row wrap. */
.answer .wlab span { font-weight: 600; letter-spacing: .02em; text-transform: none;
  color: color-mix(in srgb, #fff 62%, var(--navy)); }
.answer .vrow { display: flex; align-items: center; gap: 12px; width: 100%; min-height: 44px;
  text-align: left; background: transparent; border: 0; border-top: 1px solid rgba(255,255,255,.10);
  padding: 10px 0; color: #fff; font: inherit; cursor: pointer; }
.answer .vrow:hover { background: rgba(255,255,255,.05); }
.answer .vrow:focus-visible { outline: 2px solid var(--gold); outline-offset: -2px; }
.answer .vrow .wn { flex: 0 0 auto; min-width: 152px; font-size: 14.5px; font-weight: 600; }
.answer .vrow .wd { flex: 1 1 auto; font-size: 14px;
  color: color-mix(in srgb, #fff 74%, var(--navy)); }
.answer .vrow .wg { flex: 0 0 auto; white-space: nowrap; font-size: 13px; font-weight: 600;
  color: var(--gold); }

/* At 390 a row is two lines BY DESIGN rather than by wrapping: the statement
   gets its own line at full width instead of being squeezed into whatever the
   label leaves, which is what turns one sentence into what looks like two. */
@media (max-width: 600px) {
  .answer { padding: 17px 17px 15px; }
  .answer .vhead { font-size: 20px; }
  .answer .vfig { flex-wrap: wrap; gap: 2px 10px; }
  .answer .vfig .fl { flex: 1 1 100%; order: 1; font-size: 13.5px; }
  .answer .vfig .fv { order: 2; }
  .answer .vfig .fq { order: 3; flex: 1 1 auto; min-width: 0; text-align: left; }
  .answer .vrow { flex-wrap: wrap; gap: 2px 10px; padding: 11px 0; }
  .answer .vrow .wn { flex: 1 1 100%; min-width: 0; }
  .answer .vrow .wd { flex: 1 1 100%; font-size: 13.5px; }
  .answer .vrow .wg { margin-top: 3px; }
}

.needs {
  background: var(--surface); border: 1px solid var(--hairline);
  border-radius: 14px; overflow: hidden; margin-bottom: var(--s4);
}
.needs .hd {
  padding: 13px 20px; border-bottom: 1px solid var(--hairline);
  display: flex; align-items: center; gap: 10px;
  font-size: 13px; font-weight: 800; letter-spacing: .05em;
  text-transform: uppercase; color: var(--muted);
}
.needs .hd .n {
  background: var(--gold); color: #3A2E05;
  border-radius: 999px; font-size: 12px; padding: 1px 9px;
}
.need {
  display: flex; gap: var(--s4); align-items: center;
  padding: 16px 20px; border-bottom: 1px solid var(--hairline);
  width: 100%; text-align: left; background: none; border-left: none;
  border-right: none; border-top: none; cursor: pointer; font-family: var(--sans);
}
.need:last-child { border-bottom: none; }
.need:hover { background: #FBFCFD; }
.need .nic {
  width: 38px; height: 38px; border-radius: 10px; flex: none;
  background: var(--harbor-tint); color: var(--navy);
  display: grid; place-items: center;
}
.need .nic svg.ic { width: 19px; height: 19px; }
.need .nic.gold { background: var(--gold-tint); color: var(--gold-deep); }
.need .nic.red { background: var(--danger-tint); color: #8E2B22; }
.need .nt { min-width: 0; }
.need .nt .t { display: block; font-size: 15.5px; font-weight: 700; color: var(--navy); }
.need .nt .m { display: block; font-size: 13.5px; color: var(--muted); margin-top: 1px; }
.need .go {
  margin-left: auto; flex: none;
  font-size: 13.5px; font-weight: 700; color: var(--harbor); white-space: nowrap;
}
.need:hover .go { color: var(--navy); }
.needs .allclear {
  display: flex; gap: 12px; align-items: center; padding: 22px 20px;
  color: var(--muted); font-size: 15.5px;
}
.needs .allclear svg.ic { width: 21px; height: 21px; color: var(--good); }

.glance { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--s3); }
@media (max-width: 860px) { .glance { grid-template-columns: repeat(2, 1fr); } }
.glance .g {
  background: var(--surface); border: 1px solid var(--hairline);
  border-radius: 12px; padding: 14px 16px;
  text-align: left; cursor: pointer; font-family: var(--sans);
}
.glance .g:hover { border-color: var(--harbor); }
.glance .g .l {
  display: block; font-size: 10.5px; letter-spacing: .13em; text-transform: uppercase;
  font-weight: 800; color: var(--muted);
}
.glance .g .v {
  display: block; font-family: var(--serif); font-size: 26px; color: var(--navy);
  margin-top: 3px; font-variant-numeric: tabular-nums;
}
.glance .g .d { display: block; font-size: 12.5px; color: var(--muted); }

/* --------------------- quiet prompts (one voice per page) -----------------
   What used to be a navy gradient panel when it wasn't the facts of record:
   a white card with a gold spine. Urgency comes from position and wording,
   not from colour — the dark panel stays reserved for the summary of record,
   at most one per pane. */
.prompt-row {
  display: flex; gap: var(--s4); align-items: center; flex-wrap: wrap;
  background: var(--surface); border: 1px solid var(--hairline);
  border-left: 4px solid var(--gold); border-radius: 12px;
  padding: 16px 20px; margin-bottom: var(--s4);
  box-shadow: var(--shadow);
}
.prompt-row .pt { flex: 1; min-width: 240px; }
.prompt-row h3 { font-size: 17.5px; font-family: var(--sans); font-weight: 700; margin-bottom: 2px; }
.prompt-row p { font-size: 14.5px; color: var(--muted); }
.prompt-row .pa { flex: none; margin-left: auto; display: flex; gap: 10px; align-items: center; }

/* the campaign composer, at the full width of the content area */
.compose-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; align-items: start; }
@media (max-width: 1000px) { .compose-grid { grid-template-columns: 1fr; } }
.compose-grid .preview-label { margin-top: 22px; }

.stat-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 18px; margin-bottom: 30px; }
@media (max-width: 860px) { .stat-row { grid-template-columns: repeat(2, 1fr); } }

.stat-tile { background: #fff; border: 1px solid rgba(15,42,67,0.07); border-radius: var(--radius); padding: 20px 22px; box-shadow: var(--shadow); }
.stat-tile .lbl { font-size: 13.5px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); }
.stat-tile .val { font-family: var(--serif); font-size: 33px; color: var(--navy); margin-top: 4px; font-variant-numeric: tabular-nums; }
.stat-tile .sub { font-size: 13.5px; color: var(--muted); margin-top: 2px; }
.stat-tile.gold { border-top: 4px solid var(--gold); }
/* Something did not arrive and the board has not seen why yet. */
.stat-tile.alert { border-top: 4px solid var(--danger); }
.stat-tile.alert .sub { color: var(--danger); font-weight: 600; }

.doc-row {
  display: flex; align-items: center; gap: 16px;
  padding: 17px 20px;
  background: #fff; border: 1px solid var(--line); border-radius: 10px;
  margin-bottom: 12px;
}
.doc-row .ic { font-size: 26px; flex: none; }
.doc-row .t { font-weight: 700; color: var(--navy); font-size: 17px; }
.doc-row .m { font-size: 14.5px; color: var(--muted); }
.doc-row .actions { margin-left: auto; flex: none; display: flex; gap: 10px; }

/* filing-cabinet drawers: filter chips + the category editor */
.doc-catbar { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 16px; }
.doc-catbar:empty { display: none; }
.doc-catbar .chip-year { font-size: 14px; padding: 8px 16px; min-height: 0; }

.linkish {
  background: none; border: none; padding: 0; cursor: pointer;
  font: inherit; font-size: 13.5px; font-weight: 700;
  color: var(--harbor); text-decoration: underline; text-underline-offset: 3px;
}
.linkish:hover { color: var(--navy); }

.doc-cat-row { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.doc-cat-row input { margin: 0; }
.doc-cat-row .dc-icon { width: 62px; flex: none; text-align: center; }
.doc-cat-row .dc-name { flex: 1; min-width: 0; }
.doc-cat-row .dc-use { flex: none; font-size: 13px; color: var(--muted); width: 62px; text-align: right; }
.doc-cat-row .dc-del {
  flex: none; width: 32px; height: 32px; border-radius: 8px; cursor: pointer;
  background: #fff; border: 1px solid var(--line); color: var(--danger);
  font-size: 19px; line-height: 1;
}
.doc-cat-row .dc-del:disabled { opacity: .3; cursor: not-allowed; }
.doc-cat-row .dc-del:not(:disabled):hover { background: #FDEDED; border-color: var(--danger); }

.minute-card { background: #fff; border: 1px solid var(--line); border-radius: var(--radius); margin-bottom: 16px; overflow: hidden; }
.minute-card summary {
  cursor: pointer; padding: 18px 22px;
  font-weight: 700; font-size: 17.5px; color: var(--navy);
  display: flex; gap: 14px; align-items: baseline; list-style: none;
}
.minute-card summary::-webkit-details-marker { display: none; }
.minute-card summary .dt { color: var(--gold-deep); font-family: var(--serif); flex: none; }
.minute-card summary::after { content: "▾"; margin-left: auto; color: var(--muted); }
.minute-card[open] summary { border-bottom: 1px solid var(--line); }
.minute-card .body { padding: 18px 22px; font-size: 16.5px; color: var(--ink); }
.minute-card .decisions { background: #FBF7E9; border-left: 4px solid var(--gold); border-radius: 0 8px 8px 0; padding: 13px 18px; margin-top: 14px; }
.minute-card .decisions .h { font-size: 13px; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: var(--gold-deep); margin-bottom: 6px; }
.minute-card .decisions li { margin-left: 20px; font-size: 15.5px; }

/* -------------------------------- charts -------------------------------- */
.chart-card { background: #fff; border: 1px solid rgba(15,42,67,0.07); border-radius: var(--radius); box-shadow: var(--shadow); padding: 22px 24px; }
.chart-card h3 { font-size: 18px; font-family: var(--sans); font-weight: 700; margin-bottom: 2px; }
.chart-card .sub { font-size: 14px; color: var(--muted); margin-bottom: 16px; }
/* ------------------------- when the mail goes wrong -------------------------
   The board reads this on a phone, a Kindle and a desktop, and the people
   reading it did not choose the mail provider whose error is being explained.
   So: bigger type than the surrounding cards rather than smaller, one idea per
   line rather than a run-on sentence, and the raw provider text folded away
   behind a summary that says who it is for. Nothing here is red unless
   somebody can actually do something — a warning colour on a problem with no
   action is just an alarm the club learns to ignore. */
.fix-card {
  background: #fff; border: 1px solid rgba(15,42,67,0.07); border-left: 5px solid var(--danger);
  border-radius: var(--radius); box-shadow: var(--shadow); padding: 22px 24px; margin-bottom: 22px;
}
.fix-card > h3 { font-size: 19px; font-family: var(--sans); font-weight: 700; margin-bottom: 2px; }
.fix-card > .sub { font-size: 14.5px; color: var(--muted); margin-bottom: 18px; }
.mail-fix { border-top: 1px solid var(--line); padding-top: 16px; margin-top: 16px; }
.mail-fix:first-child { border-top: 0; padding-top: 0; margin-top: 0; }
.mail-fix .hd { display: flex; gap: 12px; align-items: flex-start; }
.mail-fix .hd .ic { font-size: 22px; flex: none; line-height: 1.3; }
.mail-fix h4 { font-size: 17.5px; line-height: 1.35; color: var(--navy); margin: 0; }
.mail-fix .count { font-size: 14px; color: var(--muted); margin-top: 3px; }
.mail-fix p { font-size: 16px; line-height: 1.6; color: var(--ink); margin: 12px 0 0; }
.mail-fix p.todo {
  background: #FFF8E2; border: 1px solid #EBD9A4; border-radius: 9px; padding: 13px 15px;
}
/* How much of the club's mail this is actually affecting — the sentence that
   keeps the panel from overstating itself. Quieter than the cause above it,
   because it is a qualification, not news. */
.mail-fix p.scope { font-size: 15px; color: var(--muted); }
.mail-fix p.todo b { display: block; font-size: 13px; letter-spacing: .07em; text-transform: uppercase; color: var(--gold-deep); margin-bottom: 4px; }
/* Nothing anybody can change: said plainly, and in grey, so it reads as
   information rather than as one more thing left undone. */
.mail-fix.calm { }
.mail-fix.calm p.todo { background: var(--harbor-tint); border-color: var(--line); }
.mail-fix.calm p.todo b { color: var(--muted); }
.mail-fix .btn { margin-top: 14px; }
.mail-fix details { margin-top: 12px; }
.mail-fix summary { font-size: 13.5px; color: var(--muted); cursor: pointer; }
.mail-fix details pre {
  margin-top: 8px; padding: 10px 12px; background: var(--harbor-tint); border-radius: 8px;
  font-size: 12.5px; line-height: 1.5; white-space: pre-wrap; word-break: break-word; color: var(--muted);
}

/* One send in Latest Messages: the verdict on its own line, never buried at
   the end of a paragraph of metadata. */
.mail-row .t { font-weight: 700; color: var(--navy); font-size: 17px; line-height: 1.35; }
.mail-row .meta { font-size: 14px; color: var(--muted); margin-top: 3px; }
.mail-row .verdict { font-size: 15px; font-weight: 600; margin-top: 5px; }
.mail-row .said { font-size: 14.5px; color: var(--muted); margin-top: 6px; line-height: 1.55; }
.mail-row details { margin-top: 6px; }
.mail-row summary { font-size: 13px; color: var(--muted); cursor: pointer; }
.mail-row details pre {
  margin-top: 6px; padding: 9px 11px; background: var(--harbor-tint); border-radius: 8px;
  font-size: 12.5px; line-height: 1.5; white-space: pre-wrap; word-break: break-word; color: var(--muted);
}
/* The maintenance account's Remove. Deliberately quiet, and deliberately NOT
   the full-width block that doc rows give their primary action on a phone:
   this sits under every message including the ones that arrived, it destroys a
   record, and the loudest thing on a row should never be the one irreversible
   thing on it. */
.mail-row .actions { margin-left: auto; width: auto; align-self: center; }
.mail-row .actions .btn {
  flex: none; border: 0; background: none; padding: 4px 6px;
  font-size: 13px; font-weight: 600; color: var(--muted); text-decoration: underline;
}
.mail-row .actions .btn:hover { background: none; color: var(--danger); }
@media (max-width: 1000px) {
  .mail-row .actions { width: auto; }
  .mail-row .actions .btn { flex: none; }
}

.legend { display: flex; gap: 18px; font-size: 14px; color: var(--muted); margin-bottom: 12px; }
.legend .key { display: inline-block; width: 12px; height: 12px; border-radius: 3px; margin-right: 6px; vertical-align: -1px; }
.chart-tip {
  position: fixed; z-index: 300; pointer-events: none;
  max-width: min(320px, calc(100vw - 24px));
  background: var(--navy-deep); color: #fff;
  font-size: 13.5px; line-height: 1.5;
  padding: 8px 12px; border-radius: 8px;
  box-shadow: 0 6px 20px rgba(0,0,0,0.3);
  opacity: 0; transition: opacity 0.1s;
}
.chart-tip b { color: var(--gold); }
.hbar-row { display: grid; grid-template-columns: 170px 1fr 76px; align-items: center; gap: 12px; margin-bottom: 11px; font-size: 15px; }
.hbar-row .nm { color: var(--ink); font-weight: 600; text-align: right; }
.hbar-row .track { background: var(--harbor-tint); border-radius: 5px; height: 22px; position: relative; }
.hbar-row .fill { position: absolute; inset: 0 auto 0 0; border-radius: 5px 4px 4px 5px; background: var(--chart-income); min-width: 3px; }
.hbar-row .amt { font-variant-numeric: tabular-nums; color: var(--muted); }

/* misc */
.pill-note { display:inline-block; background:var(--harbor-tint); color:var(--navy); font-size:14px; padding:6px 14px; border-radius:18px; font-weight:600; }
.empty { color: var(--muted); font-size: 16px; padding: 30px; text-align: center; }
/* "we could not read this" — deliberately not styled as an error/alarm. It is
   a normal condition with a way out, so it gets the same weight as .empty plus
   a retry. See BASS.loadError(). */
.load-error { line-height: 1.7; }
.load-error .btn { margin-top: 12px; }
table.data { width: 100%; border-collapse: collapse; font-size: 15.5px; }
table.data th { text-align: left; font-size: 13px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); padding: 8px 10px; border-bottom: 2px solid var(--line); }
table.data td { padding: 9px 10px; border-bottom: 1px solid var(--line); font-variant-numeric: tabular-nums; }

/* ==========================================================================
   Finance Dashboard 2.0 + Campaign Dashboard + Gallery filters
   ========================================================================== */
.fin-hero {
  background: linear-gradient(135deg, var(--navy-deep), var(--navy-lift));
  border-radius: 18px; padding: 26px 30px 24px; color: #fff;
  box-shadow: 0 18px 44px rgba(15,42,67,.25); margin-bottom: 22px;
  position: relative; overflow: hidden;
}
.fin-hero::after {
  content: ""; position: absolute; right: -60px; top: -70px;
  width: 300px; height: 300px;
  background: url("/brand/mark.svg?role=watermark") no-repeat center / contain; opacity: .07;
  pointer-events: none;
}
.fin-hero .fh-head { display: flex; align-items: baseline; gap: 14px; margin-bottom: 18px; }
.fin-hero .fh-head h3 { color: #fff; font-family: var(--serif); font-size: 22px; }
.fin-hero .asof { color: #8FA5BC; font-size: 13.5px; margin-left: auto; }
.fh-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; position: relative; }
@media (max-width: 900px) { .fh-grid { grid-template-columns: repeat(2, 1fr); } }
.fh-tile { background: rgba(255,255,255,.06); border: 1px solid rgba(255,255,255,.13); border-radius: 14px; padding: 15px 18px 12px; }
.fh-tile .l { font-size: 11.5px; font-weight: 700; letter-spacing: .14em; text-transform: uppercase; color: #8FA5BC; }
.fh-tile .v { font-family: var(--serif); font-size: 32px; color: #fff; margin: 5px 0 2px; font-variant-numeric: tabular-nums; }
.fh-tile .v.gold { color: var(--gold); }
.fh-tile .d { font-size: 13px; color: #A9BBCE; }
.fh-tile .d .up { color: #7FD6A4; font-weight: 700; }
.fh-tile .d .down { color: #F4A79B; font-weight: 700; }
.fh-tile svg.spark { display: block; margin-top: 9px; width: 100%; height: 34px; overflow: visible; }
.fin-chips { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 24px; }
.fin-chip {
  display: flex; gap: 11px; align-items: center;
  background: #fff; border: 1px solid var(--line); border-left: 4px solid var(--gold);
  border-radius: 12px; padding: 11px 16px; font-size: 14.5px; color: var(--ink);
  box-shadow: var(--shadow);
}
.fin-chip .ic { font-size: 20px; }
.fin-chip b { color: var(--navy); }
.fin-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px; }
@media (max-width: 980px) { .fin-grid { grid-template-columns: 1fr; } }
.chart-card.span { grid-column: 1 / -1; }
.cat-row { display: grid; grid-template-columns: 40px minmax(110px, 150px) 1fr 110px; align-items: center; gap: 12px; margin-bottom: 13px; }
.cat-row .ico { width: 38px; height: 38px; border-radius: 10px; background: var(--harbor-tint); display: flex; align-items: center; justify-content: center; font-size: 19px; }
.cat-row .nm { font-weight: 600; color: var(--ink); font-size: 14.5px; line-height: 1.25; }
.cat-row .track { background: #EDF0F3; border-radius: 6px; height: 24px; position: relative; overflow: hidden; }
.cat-row .fill { position: absolute; inset: 0 auto 0 0; border-radius: 6px 5px 5px 6px; min-width: 3px; }
.cat-row .amt { text-align: right; font-variant-numeric: tabular-nums; color: var(--ink); font-weight: 700; font-size: 14.5px; }
.cat-row .amt small { display: block; color: var(--muted); font-weight: 400; font-size: 12px; }
.donut-wrap { display: flex; gap: 24px; align-items: center; flex-wrap: wrap; }
.donut-hole { font-family: var(--serif); }
.donut-legend { flex: 1; min-width: 150px; }
.dl-row { display: flex; align-items: center; gap: 10px; font-size: 14.5px; margin-bottom: 11px; color: var(--ink); }
.dl-row .sw { width: 13px; height: 13px; border-radius: 4px; flex: none; }
.dl-row b { margin-left: auto; font-variant-numeric: tabular-nums; }
.pace { display: inline-flex; align-items: center; gap: 5px; font-size: 11.5px; font-weight: 700; border-radius: 12px; padding: 3px 10px; white-space: nowrap; }
.pace.good { background: #E4F3EB; color: #16603C; }
.pace.warn { background: #FBEFD8; color: #8A6D0F; }
.pace.flat, .pace.none { background: #EDF0F3; color: #4E5A68; }
.pace.ok { background: #E4F3EB; color: #16603C; }
.pace.hot { background: #FBEFD8; color: #7A5E0B; }
.pace.critical { background: #FBEBE9; color: #8E2A21; }

/* ======================= budget vs. actual: the burn ========================
   Colour here is a *status scale*, not a series palette: on/under pace →
   running hot → over budget, in that order, with the red end always the end
   the board would not want. Both halves of the screen use the same three
   steps with the polarity handled server-side, so nobody has to remember
   which chart they are reading.

   The steps were chosen by running them through the palette validator against
   this app's own surfaces rather than by eye:

     light (#FFFFFF)  #1E7B4F · #A5851A · #B3362B   worst adjacent ΔE 17.0
                      normal vision, 9.5 under deuteranopia; all ≥ 3:1 contrast
     dark  (#132131)  #2E9464 · #B08F1E · #CC4436   15.3 / 7.8, all ≥ 3:1

   The dark pair sits in the 6–8 band, which is legal only alongside a second
   channel — so every bar ships with an icon and a worded chip beside it, and
   colour is never the only thing carrying the verdict. Do not "simplify" the
   chips away. */
:root {
  --burn-ok: #1E7B4F;
  --burn-hot: #A5851A;
  --burn-critical: #B3362B;
  --burn-none: #7E8C9C;
  --burn-track: #EDF0F3;
  --burn-over-zone: #F8E3E0;
  --burn-surplus-zone: #E7F1EA;
}
.burn-rows { margin-top: 4px; }
/* One layout at every width. The bar always gets the full width of the card,
   because these cards sit two-up in a grid and a bar sharing a 460px card with
   a name and a figure is back to the forty-pixel stub this replaced. Areas are
   named rather than left to auto-placement: the bar spans the row, and without
   explicit placement the figure beside the name would be pushed below it. */
.burn-row {
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr) auto;
  grid-template-areas: "ico who val" "bar bar bar";
  align-items: center; gap: 7px 11px; padding: 9px 0;
}
.burn-row .ico { grid-area: ico; }
.burn-row .who { grid-area: who; }
.burn-row .val { grid-area: val; }
.burn-row .bar { grid-area: bar; }
.burn-row + .burn-row { border-top: 1px solid var(--line); }
.burn-row .ico {
  width: 34px; height: 34px; border-radius: 9px; background: var(--harbor-tint);
  display: flex; align-items: center; justify-content: center; font-size: 17px;
}
.burn-row .who { min-width: 0; }
.burn-row .nm { display: block; font-weight: 600; color: var(--ink); font-size: 14.5px; line-height: 1.25; }
.burn-row .sub { display: block; color: var(--muted); font-size: 12.5px; font-variant-numeric: tabular-nums; }
.burn-row .bar { min-width: 0; }
/* `overflow: visible` so the pace marker can poke out top and bottom — inside
   the track it read as a gap chopping the bar in two rather than a mark on it.
   The track's own surface is a ::before instead of a background, so the strip
   past the budget can be washed in danger tint without touching the fill. */
.burn-row .track {
  display: block; position: relative; height: 22px; overflow: visible;
}
.burn-row .track::before {
  content: ""; position: absolute; inset: 0; border-radius: 6px;
  background: var(--burn-track);
}
/* Everything to the right of the budget is not "more track" — it is money the
   club did not plan for. Shading it makes a bar crossing the line legible
   before anybody reads the number. The tint follows the meaning, which is not
   the same on both halves of the screen: unplanned spending is a warning,
   unplanned income is a surplus, and a danger wash under a fundraiser that
   beat its target would be the chart telling the board off for succeeding. */
.burn-row .track.over-zone::before {
  background: linear-gradient(to right,
    var(--burn-track) 0 var(--bm, 100%), var(--burn-over-zone) var(--bm, 100%) 100%);
}
.burn-rows[data-kind="income"] .track.over-zone::before {
  background: linear-gradient(to right,
    var(--burn-track) 0 var(--bm, 100%), var(--burn-surplus-zone) var(--bm, 100%) 100%);
}
.burn-row .fill {
  position: absolute; inset: 0 auto 0 0; width: var(--w, 0%); min-width: 3px;
  border-radius: 6px 4px 4px 6px; background: var(--burn-none); z-index: 1;
  transition: width .65s cubic-bezier(.22, .8, .3, 1);
}
/* A line with no budget is measured against nothing, so its bar is hatched as
   well as grey — the reader should not have to reach the chip to know that the
   length means something different from every other length on the screen. */
.burn-row[data-tone="none"] .fill {
  background-image: repeating-linear-gradient(135deg,
    rgba(255, 255, 255, .3) 0 5px, rgba(255, 255, 255, 0) 5px 10px);
}
/* Until the rows are marked ready the fill has no width, so the first paint
   grows each bar out of the baseline. A length that moves reads as a quantity. */
.burn-rows:not(.ready) .fill { width: 0; }
.burn-row[data-tone="ok"] .fill { background: var(--burn-ok); }
.burn-row[data-tone="hot"] .fill { background: var(--burn-hot); }
.burn-row[data-tone="critical"] .fill { background: var(--burn-critical); }
/* Where the line would be if it were spent evenly across the year — the target
   marker. It stands above and below the bar so it never reads as a break in
   it, and carries a pale halo so it survives on the fill and on bare track
   alike. Taller than the budget mark, which is how the two are told apart. */
.burn-row .tick {
  position: absolute; top: -4px; bottom: -4px; width: 2px; margin-left: -1px; z-index: 3;
  background: var(--navy-deep); border-radius: 1px;
  box-shadow: 0 0 0 1.5px rgba(255, 255, 255, .92);
}
/* The budget itself, drawn only when something has run past it and widened the
   scale — otherwise it is the end of the track and needs no line. Dashed and
   contained within the bar, so it is never mistaken for the pace tick: two
   reference lines that looked the same would be worse than one. */
.burn-row .budget-mark {
  position: absolute; top: 0; bottom: 0; width: 2px; margin-left: -1px; z-index: 4;
  background: repeating-linear-gradient(to bottom,
    var(--navy-deep) 0 4px, rgba(255, 255, 255, .92) 4px 7px);
  box-shadow: 0 0 0 1.5px rgba(255, 255, 255, .92);
}
/* Past the section's scale cap: the bar is drawn to the edge and says so. */
.burn-row .track.clipped .fill::after {
  content: "›"; position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  color: #fff; font-weight: 800; font-size: 15px; line-height: 1;
}
/* The figure and the verdict read as one phrase — "98% · running hot" — on the
   same line as the name, so the row is scannable without the eye dropping. */
.burn-row .val {
  display: flex; align-items: center; justify-content: flex-end; gap: 9px;
  font-variant-numeric: tabular-nums; color: var(--ink); font-weight: 700; font-size: 14.5px;
}
.burn-rows.only-attention .burn-row:not([data-attention="true"]) { display: none; }

/* the sentence above the bars */
.burn-verdict {
  display: flex; gap: 11px; align-items: flex-start;
  border: 1px solid var(--line); border-left: 4px solid var(--burn-none);
  border-radius: 10px; padding: 11px 14px; margin: 10px 0 12px;
  background: var(--surface-2);
}
.burn-verdict[data-tone="ok"] { border-left-color: var(--burn-ok); background: var(--good-tint); }
.burn-verdict[data-tone="hot"] { border-left-color: var(--burn-hot); background: var(--gold-tint); }
.burn-verdict[data-tone="critical"] { border-left-color: var(--burn-critical); background: var(--danger-tint); }
.burn-verdict .bv-ico { font-size: 15px; line-height: 1.5; flex: none; }
.burn-verdict[data-tone="ok"] .bv-ico { color: #16603C; }
.burn-verdict[data-tone="hot"] .bv-ico { color: #7A5E0B; }
.burn-verdict[data-tone="critical"] .bv-ico { color: #8E2A21; }
.burn-verdict b { display: block; font-size: 15px; color: var(--ink); line-height: 1.35; }
.burn-verdict span { display: block; font-size: 13px; color: var(--muted); font-variant-numeric: tabular-nums; }

.burn-legend { display: flex; flex-wrap: wrap; gap: 6px 16px; margin-bottom: 10px; }
.burn-legend span { display: inline-flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--muted); }
.burn-legend i { width: 12px; height: 12px; border-radius: 3px; flex: none; }
.burn-legend i[data-tone="ok"] { background: var(--burn-ok); }
.burn-legend i[data-tone="hot"] { background: var(--burn-hot); }
.burn-legend i[data-tone="critical"] { background: var(--burn-critical); }
.burn-legend i.k-tick { width: 3px; height: 17px; border-radius: 1px; background: var(--navy-deep); }
.burn-legend i.k-budget {
  width: 3px; height: 12px; border-radius: 1px;
  background: repeating-linear-gradient(to bottom, var(--navy-deep) 0 4px, transparent 4px 7px);
}
.burn-legend i.k-none {
  background: var(--burn-none);
  background-image: repeating-linear-gradient(135deg, rgba(255, 255, 255, .3) 0 4px, rgba(255, 255, 255, 0) 4px 8px);
}

.burn-filter { display: flex; gap: 6px; margin-bottom: 12px; flex-wrap: wrap; }
.burn-filter button {
  font: inherit; font-size: 13px; font-weight: 600; cursor: pointer;
  background: var(--surface); color: var(--muted);
  border: 1px solid var(--line); border-radius: 999px; padding: 6px 14px; min-height: 34px;
}
.burn-filter button.on { background: var(--navy); border-color: var(--navy); color: var(--gold); }

.burn-empty {
  border: 1px solid var(--line); border-left: 4px solid var(--gold);
  border-radius: 10px; padding: 14px 16px; margin: 10px 0 16px; background: var(--gold-tint);
}
.burn-empty b { display: block; font-size: 15.5px; color: var(--ink); margin-bottom: 4px; }
.burn-empty p { font-size: 13.5px; color: var(--muted); line-height: 1.55; margin-bottom: 11px; }

/* Phones: the verdict drops under the figure rather than squeezing the name.
   (The old chart set its column widths inline, which no media query could
   reach, so it kept desktop proportions on a phone and collapsed to stubs.) */
@media (max-width: 560px) {
  .burn-row { grid-template-areas: "ico who val" "bar bar bar"; gap: 6px 9px; }
  .burn-row .val { flex-direction: column; align-items: flex-end; gap: 3px; }
  .burn-row .track { height: 20px; }
  .burn-legend { gap: 5px 13px; }
}
@media (prefers-reduced-motion: reduce) {
  .burn-row .fill { transition: none; }
  .burn-rows:not(.ready) .fill { width: var(--w, 0%); }
  /* The caret still turns over — it is the state, not decoration; it turns
     without being animated into place. */
  .cat-row .caret { transition: none; }
}

/* campaign dashboard */
.camp-agg { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin-bottom: 20px; }
@media (max-width: 900px) { .camp-agg { grid-template-columns: repeat(2, 1fr); } }
.camp-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; }
@media (max-width: 940px) { .camp-grid { grid-template-columns: 1fr; } }
.camp-card { background: #fff; border: 1px solid var(--line); border-radius: 14px; padding: 20px 22px 16px; box-shadow: var(--shadow); }
.camp-top { display: flex; gap: 18px; align-items: center; }
.ring { position: relative; width: 88px; height: 88px; flex: none; }
.ring svg { transform: rotate(-90deg); display: block; }
.ring .pct { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-family: var(--serif); font-size: 21px; color: var(--navy); font-weight: 600; }
.camp-kpis { display: flex; gap: 12px; margin: 14px 0 8px; flex-wrap: wrap; }
.ck { background: var(--harbor-tint); border-radius: 10px; padding: 8px 14px; font-size: 12.5px; color: var(--navy); }
.ck b { display: block; font-size: 18px; font-family: var(--serif); font-variant-numeric: tabular-nums; }
.provider-bar { display: flex; gap: 2px; height: 26px; border-radius: 8px; overflow: hidden; margin: 10px 0 10px; }
.provider-bar span { display: block; height: 100%; min-width: 3px; }

/* gallery filters + lightbox nav */
.filter-bar {
  position: sticky; top: 74px; z-index: 20;
  /* The page's own paper at 92%, so it follows the theme instead of being a
     pale strip pinned over a dark page. */
  background: color-mix(in srgb, var(--paper) 92%, transparent); backdrop-filter: blur(8px);
  padding: 12px 0; margin-bottom: 22px;
  display: flex; gap: 10px; flex-wrap: wrap; align-items: center;
}
.chip-year {
  font-size: 16px; font-weight: 700; font-family: var(--sans);
  padding: 11px 22px; border-radius: 26px; cursor: pointer;
  background: var(--surface); color: var(--ink); border: 1.6px solid var(--line);
  min-height: 46px;
}
.chip-year.active { background: var(--navy); color: var(--gold); border-color: var(--navy); }
.chip-year:hover { border-color: var(--navy); }
.photo-card { position: relative; }
.year-badge {
  position: absolute; top: 12px; left: 12px; max-width: calc(100% - 24px);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  background: rgba(10,29,48,0.72); color: var(--gold);
  font-size: 12.5px; font-weight: 800; letter-spacing: .08em;
  padding: 5px 12px; border-radius: 14px; backdrop-filter: blur(4px);
}
.lb-nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 58px; height: 58px; border-radius: 50%;
  background: rgba(255,255,255,0.14); border: none; color: #fff;
  font-size: 28px; cursor: pointer; backdrop-filter: blur(6px);
}
.lb-nav:hover { background: rgba(255,255,255,0.28); }
.lb-prev { left: 18px; } .lb-next { right: 18px; }
.lb-actions { display: flex; gap: 14px; }
@media (max-width: 600px) {
  .gallery-grid { grid-template-columns: 1fr 1fr; gap: 12px; }
  .photo-card .pad { padding: 10px 12px; }
  .photo-card .t { font-size: 14px; }
  .photo-card .c { font-size: 12px; }
  .filter-bar { top: 66px; }
  .lb-nav { width: 46px; height: 46px; font-size: 22px; }
}

/* mobile header: the nav wraps onto extra rows, same as desktop — it used to
   scroll sideways with the scrollbar hidden, which parked the gold "Become a
   Member" button off the right edge with only an edge-fade to say it existed */
/* THE PHONE-ONLY "MORE". Above 760px it does not exist and Other Clubs is an
   ordinary nav link; below it, the reverse. Both are in the markup and CSS
   picks, so there is no width at which the page has to be re-rendered to be
   right. The panel is styled here rather than inline because it is the only
   floating thing the public header has, and it has to sit over the page. */
.nav-more { display: none; position: relative; }
.nav-more > button {
  font: inherit; font-size: 15.5px; font-weight: 600; cursor: pointer;
  color: rgba(255, 255, 255, 0.86); background: none; border: 0;
  padding: 9px 14px; border-radius: 8px; line-height: 1;
}
.nav-more > button:hover { background: rgba(255, 255, 255, 0.1); color: #fff; }
.nav-more > button.active { background: rgba(255, 255, 255, 0.14); color: var(--gold); }
.nav-more > button:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.nav-more-menu {
  /* Hangs to the RIGHT of the button, not the left. Right-aligned it was wider
     than the distance from the button back to the edge of a 390px screen, so
     it clipped against the viewport and read as a panel belonging to the page
     rather than to the control that opened it. There is always room to the
     right: the button is the last text item in the nav. */
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 60;
  min-width: 168px; max-width: min(240px, 60vw); padding: 6px; border-radius: 10px;
  background: var(--navy); border: 1px solid rgba(255, 255, 255, 0.14);
  box-shadow: 0 10px 30px rgba(6, 15, 26, 0.42);
}
.nav-more-menu a { display: block; white-space: nowrap; }

@media (max-width: 760px) {
  .main-nav a.nav-wide { display: none; }
  .nav-more { display: block; }
  .site-header .bar { flex-wrap: wrap; gap: 8px; padding: 10px 16px; }
  .brand .name { font-size: 16px; }
  .brand .name small { font-size: 9px; letter-spacing: 0.2em; }
  .main-nav { width: 100%; row-gap: 2px; }
  .main-nav a { white-space: nowrap; font-size: 15px; padding: 8px 12px; }
  .main-nav a.nav-cta { margin-left: 4px; }
}

/* ---------------------- rich text editor (minutes) ---------------------- */
.editor-shell { border: 1.5px solid var(--line); border-radius: 10px; overflow: hidden; background: #fff; }
.editor-toolbar {
  display: flex; align-items: center; gap: 4px; flex-wrap: wrap;
  background: #F4F6F8; border-bottom: 1px solid var(--line); padding: 8px 10px;
}
.editor-toolbar button {
  min-width: 38px; height: 36px; padding: 0 10px;
  border: 1px solid transparent; border-radius: 7px;
  background: transparent; cursor: pointer;
  font-size: 14.5px; font-family: var(--sans); color: var(--navy); font-weight: 600;
}
.editor-toolbar button:hover { background: #fff; border-color: var(--line); }
.editor-toolbar button.on { background: var(--navy); color: #fff; }
.editor-toolbar .sep { width: 1px; height: 24px; background: var(--line); margin: 0 5px; }
.editor-toolbar .ed-count { margin-left: auto; font-size: 13px; color: var(--muted); padding-right: 4px; }
.editor-body {
  min-height: 460px; max-height: 62vh; overflow-y: auto;
  padding: 22px 26px; font-size: 17px; line-height: 1.75; color: var(--ink);
  font-family: var(--sans);
}
.editor-body:focus { outline: none; }
.editor-body:empty::before { content: attr(data-placeholder); color: #9AA4B0; }
.editor-body h3 { font-size: 19px; margin: 18px 0 8px; }
.editor-body ul, .editor-body ol { margin: 8px 0 8px 26px; }
.editor-body li { margin-bottom: 5px; }
.editor-body p { margin-bottom: 10px; }
.editor-body blockquote.motion {
  background: #FBF7E9; border-left: 4px solid var(--gold);
  border-radius: 0 8px 8px 0; padding: 12px 18px; margin: 14px 0;
}
.minutes-html { font-size: 16.5px; line-height: 1.7; }
.minutes-html ul, .minutes-html ol { margin: 8px 0 8px 24px; }
.minutes-html h3 { font-size: 18px; margin: 16px 0 7px; }
.minutes-html p { margin-bottom: 10px; }
.minutes-html blockquote.motion { background: #FBF7E9; border-left: 4px solid var(--gold); border-radius: 0 8px 8px 0; padding: 11px 16px; margin: 12px 0; }
.status-pill { font-size: 11.5px; font-weight: 800; letter-spacing: .09em; text-transform: uppercase; padding: 3px 10px; border-radius: 12px; }
.status-draft { background: #FBEFD8; color: #8A6D0F; }
.status-approved { background: #E4F3EB; color: #16603C; }
.dec-item { padding: 10px 0; border-bottom: 1px solid var(--line); font-size: 15px; }
.dec-item:last-child { border-bottom: none; }
.dec-item .d { color: var(--gold-deep); font-weight: 700; font-size: 13px; }

/* ====================== BOOKKEEPER LEDGER INTERFACE ======================= */
.bk-toolbar {
  background: #fff; border: 1px solid var(--line); border-radius: 14px;
  padding: 14px 18px; margin-bottom: 16px;
  display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap;
  box-shadow: var(--shadow);
}
.bk-field { display: flex; flex-direction: column; gap: 4px; }
.bk-field label { margin: 0; font-size: 11.5px; letter-spacing: .07em; text-transform: uppercase; color: var(--muted); }
.bk-field input, .bk-field select {
  font-size: 15px; padding: 8px 11px; border-radius: 8px; min-height: 40px;
  border: 1.4px solid var(--line);
}
.bk-field.grow { flex: 1; min-width: 170px; }
.bk-chipset { display: flex; gap: 6px; }
.bk-chip {
  font-size: 13.5px; font-weight: 700; padding: 8px 14px; border-radius: 20px;
  border: 1.4px solid var(--line); background: #fff; color: var(--navy); cursor: pointer; min-height: 40px;
}
.bk-chip.on { background: var(--navy); color: var(--gold); border-color: var(--navy); }

.bk-summary {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 1px;
  background: var(--line); border: 1px solid var(--line);
  border-radius: 12px; overflow: hidden; margin-bottom: 14px;
}
@media (max-width: 820px) { .bk-summary { grid-template-columns: repeat(2, 1fr); } }
.bk-cell { background: #fff; padding: 13px 18px; }
.bk-cell .l { font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); }
.bk-cell .v { font-family: var(--serif); font-size: 23px; font-variant-numeric: tabular-nums; margin-top: 2px; }
.bk-cell .v.in { color: var(--good); }
.bk-cell .v.out { color: var(--danger); }
.bk-cell .v.net { color: var(--navy); }
.bk-cell .v.bal { color: var(--gold-deep); }

.bk-wrap { border: 1px solid var(--line); border-radius: 12px; overflow: hidden; background: #fff; }
.bk-scroll { max-height: 620px; overflow: auto; }
table.bk { width: 100%; border-collapse: separate; border-spacing: 0; font-size: 14.5px; }
table.bk thead th {
  position: sticky; top: 0; z-index: 2;
  background: #F4F6F8; border-bottom: 2px solid var(--line);
  text-align: left; padding: 11px 12px; white-space: nowrap;
  font-size: 11.5px; letter-spacing: .08em; text-transform: uppercase; color: var(--muted);
  cursor: pointer; user-select: none;
}
table.bk thead th:hover { color: var(--navy); }
table.bk thead th .sort { color: var(--gold-deep); font-size: 10px; }
table.bk tbody td { padding: 9px 12px; border-bottom: 1px solid #EEF1F4; vertical-align: middle; }
table.bk tbody tr:hover { background: #FBFCFD; }
table.bk tbody tr.recon { background: #F6FBF8; }
table.bk tbody tr.recon:hover { background: #EFF8F3; }
table.bk .num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
table.bk .amt-in { color: var(--good); font-weight: 700; }
table.bk .amt-out { color: var(--danger); font-weight: 700; }
table.bk .run { color: var(--muted); font-variant-numeric: tabular-nums; }
table.bk .desc-cell { min-width: 190px; }
table.bk .catpill {
  display: inline-block; font-size: 12px; padding: 2px 9px; border-radius: 11px;
  background: var(--harbor-tint); color: var(--navy); white-space: nowrap;
}
table.bk .acctpill { font-size: 12px; color: var(--muted); white-space: nowrap; }
table.bk .acctpill.restricted { color: var(--gold-deep); font-weight: 700; }
.bk-actions { display: flex; gap: 5px; }
.bk-icon {
  width: 32px; height: 32px; border-radius: 8px; border: 1px solid var(--line);
  background: #fff; cursor: pointer; font-size: 14px; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
}
.bk-icon:hover { border-color: var(--navy); background: #F4F6F8; }
.bk-icon.danger:hover { border-color: var(--danger); color: var(--danger); }
.bk-icon.on { background: var(--good); border-color: var(--good); color: #fff; }
.bk-foot {
  display: flex; gap: 14px; align-items: center; flex-wrap: wrap;
  padding: 11px 16px; background: #F9FAFB; border-top: 1px solid var(--line);
  font-size: 13.5px; color: var(--muted);
}

/* quick-entry row */
table.bk tfoot { position: sticky; bottom: 0; z-index: 3; }
.bk-quick { background: #FFFDF5; border-top: 2px solid var(--gold); }
.bk-quick td { background: #FFFDF5; }
.bk-quick td { padding: 8px 10px !important; }
.bk-quick input, .bk-quick select {
  width: 100%; font-size: 14px; padding: 7px 9px; min-height: 38px;
  border: 1.3px solid var(--line); border-radius: 7px;
}

/* category manager */
.cat-manage { display: grid; grid-template-columns: 1fr 1fr; gap: 22px; }
@media (max-width: 880px) { .cat-manage { grid-template-columns: 1fr; } }
/* A budget line: icon, name, tax line, budget, remove. The name carries an
   entry count underneath it, which makes its cell the tallest — so centring
   the row put the name box a line above every other control on it. Aligned to
   the top, with one height shared by every control, the boxes line up and the
   caption hangs below where it belongs. */
.cat-item {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 10px 14px; border: 1px solid var(--line); border-radius: 10px;
  margin-bottom: 8px; background: #fff; font-size: 15px;
}
.cat-item .ic { line-height: 34px; flex: none; }
.cat-item .nm { font-weight: 600; color: var(--ink); flex: 1; min-width: 110px; }
.cat-item .use { display: block; font-size: 12.5px; color: var(--muted); margin-top: 2px; }
/* One height for every control on the row, set rather than inherited: the name
   box carried its own padding inline and the budget box took the global input
   padding, so they stood ten pixels apart in height on the same line. */
.cat-item input, .cat-item select {
  margin: 0; height: 34px; min-height: 34px; font-size: 14.5px; padding: 4px 8px;
}
.cat-item .cat-name { font-weight: 600; width: 100%; }
.cat-item .bud-input { width: 92px; flex: none; text-align: right; }
.cat-item .btns { line-height: 34px; }
.cat-item .bud { margin-left: auto; font-size: 13px; color: var(--muted); font-variant-numeric: tabular-nums; }
.cat-item .btns { display: flex; gap: 5px; }

/* audit trail */
.audit-row { display: flex; gap: 12px; padding: 9px 0; border-bottom: 1px solid var(--line); font-size: 13.5px; }
.audit-row:last-child { border-bottom: none; }
.audit-row .when { color: var(--muted); white-space: nowrap; font-variant-numeric: tabular-nums; min-width: 118px; }
.audit-row .act { font-weight: 700; white-space: nowrap; }
.audit-row .act.add { color: var(--good); }
.audit-row .act.edit { color: var(--gold-deep); }
.audit-row .act.delete { color: var(--danger); }
.audit-row .act.login { color: var(--harbor); }
.audit-row .who { color: var(--muted); }

/* -------------------- budget line editing (in-place) --------------------- */
.bud-row {
  display: grid; grid-template-columns: 38px 1fr 130px 96px 76px;
  align-items: center; gap: 11px;
  padding: 9px 13px; border: 1px solid var(--line); border-radius: 10px;
  margin-bottom: 8px; background: #fff;
}
@media (max-width: 620px) { .bud-row { grid-template-columns: 32px 1fr 110px; } .bud-row .bud-actual, .bud-row .btns { display: none; } }
.bud-row .ic { font-size: 19px; }
.bud-row .nm { font-weight: 600; font-size: 15px; }
.bud-row .nm small { display: block; font-weight: 400; font-size: 12.5px; color: var(--muted); }
.bud-row input.bud-input {
  text-align: right; font-variant-numeric: tabular-nums; font-weight: 700;
  padding: 8px 11px; min-height: 40px; border: 1.4px solid var(--line); border-radius: 8px; font-size: 15px;
}
.bud-row input.bud-input:focus { border-color: var(--gold); outline: 3px solid rgba(201,162,39,.25); }
.bud-row .bud-actual { font-size: 13px; color: var(--muted); text-align: right; font-variant-numeric: tabular-nums; }
.bud-row .bud-actual b { display: block; color: var(--ink); font-size: 14.5px; }
.bud-row .btns { display: flex; gap: 5px; justify-content: flex-end; }
.bud-total {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 15px; border-radius: 10px; background: var(--harbor-tint);
  font-size: 15px; font-weight: 700; color: var(--navy); margin-top: 6px;
}
.bud-total .v { margin-left: auto; font-variant-numeric: tabular-nums; font-size: 18px; }
.bud-dirty { position: sticky; bottom: 14px; z-index: 5; }

/* ------------------------- audit trail: drawer --------------------------- */
.audit-bar {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  background: #fff; border: 1px solid var(--line); border-left: 4px solid var(--navy);
  border-radius: 12px; padding: 13px 18px; box-shadow: var(--shadow);
}
.audit-bar .lock { font-size: 20px; }
.audit-bar .t { font-weight: 700; color: var(--navy); font-size: 15.5px; }
.audit-bar .s { font-size: 13.5px; color: var(--muted); }
.audit-bar .spacer { margin-left: auto; }

.drawer-scrim {
  position: fixed; inset: 0; z-index: 300;
  background: rgba(10,29,48,.55); backdrop-filter: blur(3px);
  opacity: 0; transition: opacity .22s;
}
.drawer-scrim.open { opacity: 1; }
.drawer {
  position: fixed; top: 0; right: 0; bottom: 0; z-index: 301;
  width: min(660px, 96vw); background: var(--paper);
  box-shadow: -18px 0 50px rgba(10,29,48,.34);
  display: flex; flex-direction: column;
  transform: translateX(100%); transition: transform .26s cubic-bezier(.2,.7,.25,1);
}
.drawer.open { transform: none; }
.drawer-head {
  background: linear-gradient(135deg,var(--navy-deep),var(--navy-lift)); color: #fff;
  padding: 20px 24px 16px; flex: none;
}
.drawer-head h3 { color: #fff; font-size: 21px; }
.drawer-head p { color: #A9BBCE; font-size: 13.5px; margin-top: 3px; }
.drawer-close {
  position: absolute; top: 16px; right: 18px;
  width: 38px; height: 38px; border-radius: 50%; border: 1px solid rgba(255,255,255,.28);
  background: rgba(255,255,255,.1); color: #fff; font-size: 20px; cursor: pointer;
}
.drawer-close:hover { background: rgba(255,255,255,.2); }
.drawer-filters {
  display: flex; gap: 8px; padding: 12px 20px; flex-wrap: wrap;
  background: #fff; border-bottom: 1px solid var(--line); flex: none;
}
.drawer-filters input { flex: 1; min-width: 150px; min-height: 38px; font-size: 14.5px; padding: 7px 11px; }
.drawer-filters .bk-chip { font-size: 12.5px; padding: 7px 13px; min-height: 38px; }
.drawer-body { flex: 1; overflow-y: auto; padding: 6px 20px 20px; }
.drawer-foot { flex: none; padding: 12px 20px; background: #fff; border-top: 1px solid var(--line); display: flex; gap: 12px; align-items: center; }
/* The member drawer's status. It shares the footer with Done, so the notice is
   compacted and allowed to wrap rather than pushing the button off a phone. */
.ms-foot-msg { flex: 1 1 auto; min-width: 0; font-size: 13px; }
.ms-foot-msg .hint { color: var(--muted); }
.ms-foot-msg .notice { margin: 0; padding: 7px 10px; font-size: 13px; line-height: 1.35; }

.aud-day { font-size: 11.5px; font-weight: 800; letter-spacing: .12em; text-transform: uppercase;
  color: var(--muted); margin: 18px 0 8px; position: sticky; top: 0; background: var(--paper); padding: 4px 0; }
.aud-item {
  display: flex; gap: 11px; align-items: flex-start;
  background: #fff; border: 1px solid var(--line); border-radius: 10px;
  padding: 11px 14px; margin-bottom: 7px; font-size: 14.5px;
}
.aud-item .dot {
  width: 30px; height: 30px; border-radius: 50%; flex: none;
  display: flex; align-items: center; justify-content: center; font-size: 14px;
}
.aud-item.add .dot { background: #E4F3EB; }
.aud-item.edit .dot { background: #FBEFD8; }
.aud-item.delete .dot { background: #FBEAE8; }
.aud-item.access .dot { background: #DCE8F5; }
.aud-item .who { font-size: 12.5px; color: var(--muted); margin-top: 2px; }
.aud-item .time { margin-left: auto; font-size: 12.5px; color: var(--muted); white-space: nowrap; }

/* ------------------------- recurring event UI ---------------------------- */
.series-badge {
  display: inline-block; font-size: 11.5px; font-weight: 800; letter-spacing: .06em;
  background: #EFE7F5; color: #6B3E86; padding: 2px 9px; border-radius: 11px; margin-left: 7px;
}
.moved-badge { background: #FBEFD8; color: #8A6D0F; }
.scope-modal {
  background: #fff; border-radius: 16px; max-width: 460px; width: 92%;
  padding: 28px 30px; box-shadow: 0 24px 60px rgba(10,29,48,.4);
}
.scope-modal h3 { font-size: 21px; margin-bottom: 6px; }
.scope-modal p { font-size: 15px; color: var(--muted); margin-bottom: 18px; }
.scope-opt {
  display: flex; gap: 13px; align-items: flex-start; width: 100%; text-align: left;
  padding: 14px 16px; border: 1.6px solid var(--line); border-radius: 12px;
  background: #fff; cursor: pointer; margin-bottom: 10px; font-family: var(--sans);
}
.scope-opt:hover { border-color: var(--navy); background: #F7F9FB; }
.scope-opt .ic { font-size: 20px; }
.scope-opt .t { font-weight: 700; font-size: 15.5px; color: var(--navy); }
.scope-opt .s { font-size: 13.5px; color: var(--muted); margin-top: 2px; }
.scope-opt.danger:hover { border-color: var(--danger); background: #FDF7F6; }
.scope-opt.danger .t { color: var(--danger); }

/* ----------------------- resources: the three folders ---------------------
   One shelf, three drawers. The open folder is the gold one; the others sit
   flat until hovered, so the row reads as places to go rather than buttons
   competing to be pressed. */
.res-folders { display: flex; gap: 12px; flex-wrap: wrap; margin: 18px 0 22px; }
.res-folder {
  flex: 1 1 200px; min-height: 44px; text-align: left; cursor: pointer;
  display: flex; flex-direction: column; gap: 3px;
  padding: 14px 16px; border: 1px solid var(--line); border-radius: 12px;
  background: #fff; font: inherit; color: inherit;
  transition: border-color .15s, box-shadow .15s, background .15s;
}
.res-folder:hover { border-color: var(--gold); box-shadow: var(--shadow); }
.res-folder.on { border-color: var(--gold); background: #FCF9EF; box-shadow: var(--shadow); }
/* Was an emoji at 21px; it is a drawn icon now, so it takes a size and a
   colour rather than whichever picture the operating system supplies. */
.res-folder .rf-ic { display: inline-flex; align-items: center; }
.res-folder .rf-ic .ic { width: 21px; height: 21px; color: var(--harbor); }
.res-folder b { font-size: 16px; color: var(--navy); }
.res-folder .rf-sub { font-size: 13.5px; color: var(--muted); line-height: 1.4; }

/* the template folder: one specimen per kind of email the portal sends */
.tpl-card {
  border: 1px solid var(--line); border-radius: 12px; background: #fff;
  overflow: hidden; margin-bottom: 18px;
}
.tpl-card .tpl-head { padding: 15px 18px 13px; border-bottom: 1px solid var(--line); }
.tpl-card .tpl-head b { font-size: 16.5px; color: var(--navy); }
.tpl-card .tpl-head .m { font-size: 14px; color: var(--muted); line-height: 1.5; margin-top: 4px; max-width: 78ch; }
.tpl-card iframe { display: block; width: 100%; height: 540px; border: 0; background: #F0EFE9; }

/* ---------------------- resources: the logo shelf ------------------------- */
.logo-shelf {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 16px; margin-top: 18px;
}
.logo-tile {
  display: flex; gap: 15px; align-items: center;
  padding: 15px; border: 1px solid var(--line); border-radius: 12px;
  background: #fff; text-decoration: none; color: inherit;
  transition: border-color .15s, box-shadow .15s;
}
.logo-tile:hover { border-color: var(--gold); box-shadow: var(--shadow-lg); }
.logo-tile img { width: 76px; height: 76px; flex: none; object-fit: contain; }
.logo-tile .lt-body { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.logo-tile .lt-body b { font-size: 16.5px; color: var(--navy); }
.logo-tile .lt-body span { font-size: 14px; color: var(--muted); line-height: 1.45; }
.logo-tile .lt-dl { font-weight: 700; color: var(--harbor); font-size: 13.5px; }

/* The fold below the approved mark. Muted rather than styled like a second
   offer: it should read as an archive drawer, not a choice being presented. */
.alt-logos { margin-top: 26px; border-top: 1px solid var(--line); padding-top: 18px; }
.alt-logos > summary {
  cursor: pointer; font-weight: 700; color: var(--navy); font-size: 15.5px;
  list-style: none; display: flex; align-items: center; gap: 8px; min-height: 44px;
}
.alt-logos > summary::-webkit-details-marker { display: none; }
.alt-logos > summary::before { content: "▸"; color: var(--muted); font-size: 13px; }
.alt-logos[open] > summary::before { content: "▾"; }
.alt-logos > summary span { font-weight: 400; color: var(--muted); font-size: 14.5px; }
.alt-logos .logo-tile { background: var(--paper); }   /* off-white: an archive, not an offer */

/* ======================= PHONE: shared components ======================== */
@media (max-width: 760px) {
  body { font-size: 17px; }
  .wrap { padding: 0 18px; }
  .section { padding: 46px 0; }
  h2 { font-size: clamp(25px, 6.4vw, 32px); }

  /* brand lockup must stay legible when the nav wraps beneath it */
  .site-header .bar { padding: 11px 16px 8px; }
  .brand { gap: 10px; }
  .brand .mark { width: 38px; height: 38px; }
  .brand .name { font-size: 15.5px; line-height: 1.1; }
  .brand .name small { font-size: 8.5px; letter-spacing: .16em; margin-top: 2px; }

  /* events */
  .cal-toolbar { gap: 10px; }
  .event-card { flex-direction: row; gap: 14px; padding: 18px; }
  .date-block { min-width: 56px; padding: 8px 10px; }
  .date-block .da { font-size: 23px; }
  .event-card h3 { font-size: 18.5px; }
  .event-card .meta, .event-card .desc { font-size: 15px; }

  /* gallery */
  .filter-bar { gap: 8px; padding: 10px 0; }
  .chip-year { font-size: 15px; padding: 10px 18px; }
  .lightbox { padding: 12px; }
  .lightbox img { max-height: 68vh; }
  .lb-actions { flex-direction: column; align-items: center; gap: 8px; }

  /* newsletters */
  .grid-3 > .card { margin-bottom: 0; }

  /* forms and buttons get thumb-sized */
  .btn { padding: 14px 24px; font-size: 16.5px; }
  .btn-sm { padding: 11px 18px; font-size: 15px; }
  input:where(:not([type="checkbox"], [type="radio"], [type="range"], [type="hidden"], [type="submit"], [type="button"])),
  select, textarea {
    min-height: 48px;
  }
  /* 16px is the threshold under which iOS Safari zooms the page in on focus —
     and it does not zoom back out. The typed selectors above miss two whole
     families of field: <input> written with no type attribute (the roster's
     .mem-edit cells are the big one), and controls sized by a class that
     outranks a bare `select`. Sweeping every text-entry control is the only
     version of this rule that a new field can't quietly slip past, and it has
     to outrank the component classes to do its job — hence !important. */
  input:where(:not([type="checkbox"], [type="radio"], [type="range"], [type="hidden"])),
  select,
  textarea {
    font-size: 16px !important;
  }
  label { font-size: 15px; }

  /* Thumb-sized hit areas. The visual size of these stays as designed — the
     padding grows the target, it doesn't grow the type. */
  .main-nav a { min-height: 44px; display: inline-flex; align-items: center; }
  .go { min-height: 44px; display: inline-flex; align-items: center; }
  .brand { min-height: 44px; }
  .subnav button { min-height: 44px; }
  #wn-price a,
  #j-next a { min-height: 44px; display: inline-flex; align-items: center; }
  .gal-modes button { min-height: 44px; }
  .site-footer li { margin-bottom: 4px; }
  .site-footer li a,
  .site-footer .legal a { display: inline-flex; align-items: center; min-height: 44px; }
  summary { min-height: 44px; display: flex; align-items: center; }

  .section-head p { font-size: 17px; }
  .site-footer .cols { padding: 40px 0 28px; gap: 26px; }
  .site-footer .legal { flex-direction: column; gap: 8px; font-size: 13px; }
}

/* ---- phone: events page leads with the list, not an empty calendar ---- */
@media (max-width: 760px) {
  .cal-toolbar, .cal-grid, #cal-dows { display: none !important; }
  .mobile-cal-note { display: block !important; }
}
.mobile-cal-note { display: none; }

/* ============= PHONE & TABLET: the board portal, app-style =============
   Same markup, same code — the portal reorganises below 1024px: a fixed
   bottom tab bar for thumb reach, and every wide table becomes a stack of
   cards so nothing needs horizontal scrolling.

   The cutoff is 1024px, not 760px, on purpose. Between those two widths sits
   the whole iPad / Kindle band, where the 236px side rail plus seven table
   columns left the roster's Full name and Email inputs squeezed to two or
   three characters — unreadable exactly where the board edits names. Folding
   the rail and carding the tables reclaims that width for the fields that
   matter. Phones (<=760) are unchanged; roomy laptops (>=1025) keep the table. */
@media (max-width: 1024px) {
  /* Room for a two-row tab bar. Eight sections will not fit across a phone at
     a legible size, and the alternative — a bar that scrolls sideways — hides
     whichever section falls off the end from the people least likely to go
     looking for it.

     THIS IS A FALLBACK NOW, NOT THE ANSWER. It was written for two rows of
     five and is 132px; the bar is four tiles and a More, which measures 69.
     Left as the answer it would reserve 63px of empty screen at the foot of
     every pane — reclaiming the second row and then wasting it. syncRailClearance()
     measures the bar and writes the real figure inline; this stands until it
     has, and on the one phone in a thousand where script never runs.

     The safe-area inset is added rather than assumed away: the rail's own
     bottom padding grows by it, so on a phone with a home indicator the bar is
     taller than its own box, and the last card on a long screen — the Add
     Account form on Setup — sat under it. */
  body[data-page="board"] { padding-bottom: calc(69px + env(safe-area-inset-bottom)); }

  /* Tablet taps must not trigger iOS/iPadOS focus-zoom (it never zooms back
     out). The roster's inline inputs are 15px, under Safari's 16px threshold;
     this mirrors the site-wide phone rule but reaches up through the iPad band.
     Scoped to the portal so the public site is untouched. */
  body[data-page="board"] input:where(:not([type="checkbox"], [type="radio"], [type="range"], [type="hidden"])),
  body[data-page="board"] select,
  body[data-page="board"] textarea { font-size: 16px !important; }

  /* one column — and the rail unrolls into a fixed bottom bar, thumb-height */
  .portal-shell { display: block; }
  .rail-brand { display: none; }

  /* No backdrop-filter here, deliberately: Safari mis-composites fixed
     elements that carry one, and the bar was seen parked halfway up the
     attendance screen mid-scroll. The background is 97% opaque, so the blur
     bought nothing; translateZ pins the bar to its own layer. */
  body[data-page="board"] .portal-rail {
    position: fixed; left: 0; right: 0; bottom: 0; top: auto; z-index: 60;
    margin: 0; border: none; border-radius: 0; box-shadow: none;
    border-top: 1px solid rgba(255,255,255,.12);
    background: rgba(10, 29, 48, .97);
    transform: translateZ(0);
    display: grid; grid-template-columns: repeat(5, 1fr); gap: 0;
    padding: 6px 2px calc(6px + env(safe-area-inset-bottom));
  }
  body[data-page="board"] .portal-rail hr { display: none; }
  body[data-page="board"] .portal-rail > button,
  body[data-page="board"] .rail-account-wrap > button {
    font-size: 9.5px; font-weight: 700; letter-spacing: .01em;
    padding: 7px 2px 5px; margin: 0; border: none; border-radius: 0;
    color: #8FA5BC; background: none; box-shadow: none;
    display: flex; flex-direction: column; align-items: center; gap: 3px;
    line-height: 1.15; text-align: center; min-height: 52px;
    position: relative; height: 100%;
  }
  body[data-page="board"] .portal-rail > button svg.ic,
  body[data-page="board"] .rail-account-wrap > button svg.ic { width: 19px; height: 19px; }
  body[data-page="board"] .portal-rail > button.active { color: var(--gold); background: none; box-shadow: none; }
  /* the account menu opens upward from the bar's last tile, hugging the right edge */
  body[data-page="board"] .rail-account-wrap .ph-menu { left: auto; right: 4px; }
  /* the badge rides the icon's shoulder rather than taking a column */
  body[data-page="board"] .portal-rail .badge {
    position: absolute; top: 1px; right: 14%; margin: 0;
    font-size: 9px; padding: 0 5px; min-width: 16px;
  }
  body[data-page="board"] .portal-rail button.active .badge { background: var(--gold); color: #3A2E05; }

  /* ---- FOUR SECTIONS AND A More ------------------------------------------
     Nine sections and an account menu in a five-wide grid is two rows, on
     every screen, permanently. The overflow is HIDDEN here rather than moved,
     because a dozen selectors in board.js ask whether
     `.portal-rail button[data-tab=x]` exists to decide whether a section
     exists at all. `layoutRail()` marks the four that show; `#rail-more`
     opens the rest in a sheet.

     `.bar-laid` is set by layoutRail() ONCE IT HAS CHOSEN. Without it nothing
     is hidden, so a bar that has not been laid out yet is the old crowded two
     rows — which is the defect being fixed, and never a dead end. */
  body[data-page="board"] .portal-rail.bar-laid > button[data-tab]:not(.in-bar) { display: none; }
  /* The account menu lives in the sheet at this width — its popover opened
     upward out of a 52px tile at the very bottom of a phone. It is MOVED
     there by openMoreSheet(), so it keeps its ids and its listeners. */
  body[data-page="board"] .portal-rail.bar-laid .rail-account-wrap { display: none; }
  /* "Members & Email" is the longest label in the bar and the only one that
     wraps, so it alone was setting the bar's height — every other tile sat in
     13px of space it did not need. The tile says "Members"; the full name
     stays in the markup, so the rail at desktop width, the More sheet's row
     (railName reads textContent, hidden spans included) and every screen
     reader still get all of it. */
  body[data-page="board"] .portal-rail.bar-laid .lbl-rest { display: none; }

  /* segmented sub-nav wraps onto extra rows, same as desktop — it used to
     scroll sideways with the scrollbar hidden, which left the off-screen
     views (Reports, Setup, Night Recap…) invisible with no hint they exist */

  .compose-grid { grid-template-columns: 1fr; }

  /* wide tables become cards — no pinch-zoom, no sideways scrolling */
  table.data, table.bk { display: block; }
  table.data thead, table.bk thead { display: none; }
  table.data tbody, table.bk tbody { display: block; }
  table.data tr, table.bk tbody tr {
    display: block; background: #fff; border: 1px solid var(--line);
    border-radius: 12px; padding: 12px 14px; margin-bottom: 10px;
  }
  table.data td, table.bk tbody td {
    display: flex; justify-content: space-between; gap: 12px;
    border: none; padding: 5px 0; font-size: 15px; text-align: right;
    /* AN EMAIL ADDRESS IS ONE WORD. This row is a flex line with the label
       pinned beside the value, so without the rule below the row cannot be
       drawn narrower than LABEL + the longest address on the roster. On
       Members We Haven't Seen that came to 365px inside a 339px card, which
       pushed the page 8px wider than the phone — and iOS answers a page wider
       than the device by zooming the whole thing out, at which point every
       `position: fixed` thing on it is drawn against a viewport the screen is
       no longer showing, and the bottom bar floats a third of the way up the
       page. That is the bar the board photographed on 13 August 2026: not a
       bug in the bar, one long address in a table nobody thought of as wide.

       `anywhere` and not `break-word`, deliberately — only `anywhere` shrinks
       the MIN-CONTENT, and min-content is the measurement doing the damage.
       tests/viewport.test.mjs fails now if any pane is wider than the screen
       it is drawn on, so this cannot come back quietly. */
    min-width: 0; overflow-wrap: anywhere;
  }
  table.data td::before, table.bk tbody td::before {
    content: attr(data-label); font-weight: 700; color: var(--muted);
    font-size: 12.5px; letter-spacing: .06em; text-transform: uppercase;
    text-align: left; flex: none;
  }
  /* The attendance month grid shares class="data" for base cell styling, but it
     is a dense spreadsheet with a sticky first column that is meant to SCROLL
     sideways inside .grid-scroll, not collapse into cards. Opt it back out of
     the card treatment at every width the card rules apply. */
  table.data.grid-sheet { display: table; }
  table.data.grid-sheet thead { display: table-header-group; }
  table.data.grid-sheet tbody { display: table-row-group; }
  table.data.grid-sheet tfoot { display: table-footer-group; }
  table.data.grid-sheet tr {
    display: table-row; background: none; border: none;
    border-radius: 0; padding: 0; margin: 0;
  }
  table.data.grid-sheet td, table.data.grid-sheet th {
    display: table-cell; text-align: center;
  }
  table.data.grid-sheet td.mname, table.data.grid-sheet th.mname { text-align: left; }
  table.data.grid-sheet td::before, table.data.grid-sheet th::before { content: none; }

  table.bk tbody td.num { text-align: right; }
  table.bk .bk-actions { justify-content: flex-end; width: 100%; }
  table.bk .bk-icon { width: 42px; height: 42px; font-size: 16px; }
  .bk-scroll { max-height: none; overflow: visible; }
  .bk-quick { display: none; }          /* quick-entry row is desktop-only */
  #add-tx { width: 100%; }

  .fh-grid { grid-template-columns: repeat(2, 1fr); }
  .fh-tile .v { font-size: 26px; }
  .fin-grid, .camp-grid, .cat-manage, .bk-summary { grid-template-columns: 1fr !important; }
  .bk-summary { grid-template-columns: repeat(2, 1fr) !important; }
  .stat-row, .camp-agg { grid-template-columns: repeat(2, 1fr); }
  .bk-toolbar { padding: 12px 14px; }
  .bk-field.grow { min-width: 100%; }
  .cat-row { grid-template-columns: 32px 1fr; gap: 8px; }
  .cat-row .track, .cat-row .amt { grid-column: 1 / -1; }
  .cat-row .amt { text-align: left; }
  .bud-row { grid-template-columns: 32px 1fr 100px; }
  .doc-row { flex-wrap: wrap; }
  .doc-row .actions { margin-left: 0; width: 100%; }
  .doc-row .actions .btn { flex: 1; text-align: center; }
  .drawer { width: 100%; }
  .grid-2, .grid-3 { grid-template-columns: 1fr !important; }
  .editor-body { min-height: 300px; }
  .minute-card summary { flex-wrap: wrap; font-size: 16px; }
}

/* phone & tablet: give each ledger card a clear header line */
@media (max-width: 1024px) {
  table.bk tbody td[data-label="Date"],
  table.bk tbody td[data-label="Description"] {
    display: block; text-align: left; padding: 0 0 2px;
  }
  table.bk tbody td[data-label="Date"] {
    font-size: 12.5px; font-weight: 700; letter-spacing: .06em;
    text-transform: uppercase; color: var(--gold-deep);
  }
  table.bk tbody td[data-label="Description"] {
    font-size: 16.5px; font-weight: 600; color: var(--navy);
    padding-bottom: 8px; border-bottom: 1px solid #EEF1F4; margin-bottom: 6px;
  }
  table.bk tbody td[data-label="Date"]::before,
  table.bk tbody td[data-label="Description"]::before { content: none; }
}

/* Tablet band (iPad / Kindle, roughly 700–1024px): the roster's cards sit
   two-up so a roomy screen stays dense and scannable while every name and
   email input keeps its full width. Below 700px they stack one per row (the
   phone view); the #id selector outranks the `table.data tbody { display:block }`
   card rule regardless of source order. The header lives in <thead>, already
   hidden by the card rules, so only member rows fall into the grid. */
@media (min-width: 700px) and (max-width: 1024px) {
  #mem-table tbody {
    display: grid; grid-template-columns: 1fr 1fr;
    gap: 12px; align-items: start;
  }
  #mem-table tbody tr { margin-bottom: 0; height: 100%; }
}
/* Comfortable touch targets for the inline inputs on any card-layout width. */
@media (max-width: 1024px) {
  #mem-table .mem-edit { min-height: 44px; }
}

/* ---- the roster card reads as a form, so its values line up in one column --
   The shared card rules push each label and its value to opposite edges, which
   works for a ledger of short figures and reads as a mess on a member's card:
   the label sits a line high, and the value starts wherever that label happened
   to end — the email under NAME, "July 13" a word further in under BIRTHDAY,
   none of them under the name box's own text. A fixed label column puts every
   value on one left edge, and the inputs pull back by their own border and
   padding so the TYPED text — not the invisible box around it — is what lands
   on that edge. Pills do the same, so a status reads in the same column as a
   name. Widest label is BIRTHDAY (~68px at this size); 92px leaves it room. */
@media (max-width: 1024px) {
  #mem-table tbody td {
    justify-content: flex-start; align-items: center;
    gap: 0; text-align: left;
  }
  #mem-table tbody td::before {
    flex: 0 0 92px; padding-right: 8px;
  }
  #mem-table tbody td .mem-edit { margin-left: -10.4px; }   /* 1.4px border + 9px padding */
  #mem-table tbody td .pill { margin-left: -10px; }         /* the pill's own padding */
  #mem-table tbody td[data-label="Status"] { flex-wrap: wrap; }
  /* "recorded by Pat Morgan · 12 Aug" drops under the pill and starts on the
     same edge — the basis leaves room for the indent, or the line runs past
     the card by exactly the 92px it was moved over. */
  #mem-table tbody td[data-label="Status"] .mem-prov {
    flex: 1 0 calc(100% - 92px); margin-left: 92px; margin-top: 3px; max-width: none;
  }
  /* the ⋯ button labels nothing, so it keeps the card's right edge */
  #mem-table tbody td[data-label=""] { justify-content: flex-end; }
  #mem-table tbody td[data-label=""]::before { display: none; }
}
/* Five columns fit a portal-width pane without scrolling — the rest of a
   member's record lives in their sheet. */

/* ---------------- inline editing in the member roster ------------------- */
.mem-edit {
  width: 100%; min-height: 0; padding: 7px 9px; font-size: 15px;
  border: 1.4px solid transparent; border-radius: 7px; background: transparent;
  font-family: var(--sans); color: var(--ink);
}
.mem-edit:hover { border-color: var(--line); background: #FBFCFD; }
.mem-edit:focus {
  border-color: var(--gold); background: #fff;
  outline: 3px solid rgba(201,162,39,.22);
}
.mem-edit.saved { border-color: var(--good); background: #F2FAF5; }
.mem-edit.err { border-color: var(--danger); background: #FDF6F5; }
.mem-edit::placeholder { color: #A8B0BA; font-style: italic; }
.mem-hint {
  font-size: 14px; color: var(--muted); background: #F4F6F8;
  border-left: 4px solid var(--gold); border-radius: 0 8px 8px 0;
  padding: 10px 14px; margin-bottom: 12px;
}
.mem-guessed { font-size: 11px; color: var(--gold-deep); font-weight: 700; letter-spacing: .04em; }
/* How somebody came off the mailing list — themselves, or recorded for them. */
.mem-prov { font-size: 11.5px; color: var(--muted); line-height: 1.35; margin-top: 4px; max-width: 190px; }

/* ------------------------- photo album manager -------------------------- */
.pm-album { margin-bottom: 22px; }
.pm-album h4 {
  font-size: 16.5px; color: var(--navy); margin-bottom: 3px;
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
}
.pm-album h4 .n { font-size: 13px; color: var(--muted); font-weight: 400; }
.pm-album h4 .ev { font-size: 11.5px; font-weight: 800; letter-spacing: .06em; background: #DCE8F5; color: #29517A; padding: 2px 9px; border-radius: 11px; }
.pm-album h4 .loose { background: #F0EDE4; color: #7A6110; }
.pm-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); gap: 10px; margin-top: 10px; }
.pm-thumb {
  position: relative; border: 2px solid var(--line); border-radius: 10px;
  overflow: hidden; cursor: pointer; background: #fff;
}
.pm-thumb img { width: 100%; aspect-ratio: 4/3; object-fit: cover; display: block; }
.pm-thumb .cap { font-size: 11.5px; color: var(--muted); padding: 5px 7px; line-height: 1.3;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.pm-thumb.sel { border-color: var(--gold); box-shadow: 0 0 0 3px rgba(201,162,39,.25); }
.pm-thumb .tick {
  position: absolute; top: 6px; left: 6px; width: 24px; height: 24px; border-radius: 50%;
  background: rgba(255,255,255,.9); border: 1.5px solid var(--line);
  display: flex; align-items: center; justify-content: center; font-size: 13px; color: transparent;
}
.pm-thumb.sel .tick { background: var(--gold); border-color: var(--gold); color: var(--navy-deep); }

/* ------------------------- event flyers & rich copy ---------------------- */
.event-card.has-flyer { align-items: stretch; }
.event-card .ev-body { flex: 1; min-width: 0; }
.ev-flyer {
  flex: none; width: 190px; position: relative;
  border-radius: 12px; overflow: hidden; text-decoration: none;
  border: 1px solid var(--line); background: #fff;
  box-shadow: 0 6px 20px rgba(15,42,67,.14);
  transition: transform .2s, box-shadow .2s;
  align-self: flex-start;
}
.ev-flyer:hover { transform: translateY(-3px); box-shadow: 0 14px 32px rgba(15,42,67,.24); }
.ev-flyer img { width: 100%; display: block; }
.ev-flyer-hint {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 16px 8px 7px; text-align: center;
  font-size: 11.5px; font-weight: 700; letter-spacing: .04em; color: #fff;
  background: linear-gradient(to top, rgba(10,29,48,.86), transparent);
}
.desc.rich p { margin-bottom: 9px; }
.desc.rich p:last-child { margin-bottom: 0; }
.desc.rich ul, .desc.rich ol { margin: 8px 0 8px 22px; }
.desc.rich li { margin-bottom: 4px; }
.desc.rich h3, .desc.rich h4 { font-size: 17px; margin: 12px 0 5px; }
.desc.rich a { font-weight: 600; }
@media (max-width: 760px) {
  .event-card.has-flyer { flex-wrap: wrap; }
  .ev-flyer { width: 100%; margin-top: 12px; }
  .ev-flyer img { max-height: 460px; object-fit: contain; background: #F4F6F8; }
}

/* --------- manage-events rows mirror what the public page shows --------- */
.ev-group {
  font-size: 12px; font-weight: 800; letter-spacing: .12em; text-transform: uppercase;
  color: var(--muted); margin: 18px 0 8px;
}
.ev-group:first-child { margin-top: 0; }
.ev-row .ev-snip {
  font-size: 14px; color: var(--muted); margin-top: 6px; line-height: 1.5;
  max-width: 620px;
}
.ev-row .ev-thumb {
  flex: none; width: 54px; border-radius: 7px; border: 1px solid var(--line);
  align-self: center; margin-left: 4px;
}
.ev-row .actions { flex-wrap: wrap; justify-content: flex-end; }
/* An event row must never starve its own title to keep three buttons on one
   line. "First Friday Social Dance" was being set one word per line so that
   View / Edit / Cancel could stay beside it. The text keeps a 260px floor and
   the buttons drop to their own line when they no longer fit next to it —
   driven by the width of the row, not by a window breakpoint, so a narrow
   column, a tablet and a phone all behave the same way. */
.ev-row { flex-wrap: wrap; }
.ev-row .evt { flex: 1 1 260px; min-width: 0; }
.ev-row .actions { margin-left: auto; }
/* On the narrow end the buttons share the line by how long their words are,
   rather than taking a third each — equal thirds broke "View on site" across
   two lines while "Edit" sat in a box twice the size it needed. */
@media (max-width: 1024px) {
  .ev-row .actions .btn { flex: 1 1 auto; white-space: nowrap; }
}
/* the events page date block shouldn't stretch down a tall card */
.event-card .date-block { align-self: flex-start; }
.ev-flyer-hint { padding-top: 26px; }

/* ------------- live preview of the branded email being written ----------- */
.compose-split { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; align-items: start; }
/* 1024, matching the campaign composer (.compose-grid), so the one-off email
   composer and its preview also stack instead of squeezing on a tablet. */
@media (max-width: 1024px) { .compose-split { grid-template-columns: 1fr; } }
.preview-pane { position: sticky; top: 10px; }
.preview-label {
  font-size: 11.5px; font-weight: 800; letter-spacing: .12em; text-transform: uppercase;
  color: var(--muted); margin-bottom: 8px; display: flex; align-items: center; gap: 8px;
}
.preview-label .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--good); }
.preview-frame {
  background: #F0EFE9; border: 1px solid var(--line); border-radius: 12px;
  padding: 14px; max-height: 520px; overflow-y: auto;
}
/* ---- THE E-MAIL PREVIEW IS DELIBERATELY NOT TOKENISED --------------------
   Everything from here to the end of the .mail- block is a PICTURE of an
   e-mail, drawn to match what lib/mailer.js actually sends — and the message
   itself is built from these same values as literals, because an e-mail
   cannot read a stylesheet. A client strips <style>, and a custom property
   reaches nothing in Outlook at all.

   So tokenising the picture alone would make the picture LIE: a club that
   chose a palette would see a preview in its own colours and members would
   receive navy and gold. `mailtrouble`'s rule covers this — never show
   something a screen cannot honour — and the honest fix is that the composer
   and this block move together, which is a club-two job and not this one.
   ------------------------------------------------------------------------ */
.mail-shell {
  max-width: 460px; margin: 0 auto; background: #fff; border: 1px solid #E2E0D6;
  border-radius: 12px; overflow: hidden; font-family: Georgia, serif;
  box-shadow: 0 4px 16px rgba(20,30,45,.1);
}
.mail-mast { background: #0F2A43; padding: 22px 18px 16px; text-align: center; }
.mail-mast img { width: 84px; display: inline-block; }
.mail-mast .nm {
  color: #C9A227; font-size: 9px; letter-spacing: 2.6px; margin-top: 9px;
  font-family: Helvetica, Arial, sans-serif;
}
.mail-rule { height: 4px; background: linear-gradient(90deg, #C9A227, #E5C963, #C9A227); }
.mail-body { padding: 20px 22px 14px; color: #22303f; line-height: 1.6; font-size: 14px; }
.mail-body p { margin-bottom: 10px; }
.mail-body ul, .mail-body ol { margin: 8px 0 10px 20px; }
.mail-body li { margin-bottom: 5px; }
.mail-body blockquote { border-left: 3px solid #C9A227; padding-left: 11px; color: #4E5A68; margin: 10px 0; }
.mail-body h3, .mail-body h4 { color: #0F2A43; font-size: 16px; margin: 12px 0 6px; }
.mail-sign { font-size: 13.5px; color: #22303f; margin-top: 14px; }
.mail-sign small { display: block; color: #66788c; font-size: 12px; }
.mail-ticket { margin: 4px 18px 16px; background: #0F2A43; border-radius: 10px; padding: 14px 12px; }
.mail-ticket .inner {
  border: 2px dashed rgba(201,162,39,.6); border-radius: 9px; padding: 14px 12px; text-align: center;
}
.mail-ticket .k { color: #C9A227; font-size: 8.5px; letter-spacing: 3px; font-family: Helvetica, Arial, sans-serif; }
.mail-ticket .v { color: #fff; font-size: 16px; margin: 7px 0 4px; }
.mail-ticket .d { color: #A9BBCE; font-size: 11.5px; }
.mail-ticket .p { color: #C9A227; font-size: 11px; margin-top: 8px; }
.mail-foot {
  background: #0F2A43; color: #8FA5BC; font-size: 9.5px; line-height: 1.55;
  padding: 11px 18px; text-align: center; font-family: Helvetica, Arial, sans-serif;
}

/* ==========================================================================
   THE CHECKBOOK REGISTER
   A treasurer's register, not a report table: dense rows, monospaced figures,
   Payment and Deposit in separate columns, and the running balance bold on the
   right. Every cell is editable in place — the layout has to stay readable
   while it is being typed into, which is why the edit state changes the cell's
   border and background rather than its size.
   ========================================================================== */

/* account tabs — one per account in the chart of accounts */
.ck-accounts { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 14px; }
.ck-acct {
  appearance: none; cursor: pointer; text-align: left;
  border: 1px solid var(--line); background: #fff; border-radius: 11px;
  padding: 9px 15px; min-width: 128px; font-family: var(--sans);
  transition: border-color .12s, box-shadow .12s;
}
.ck-acct:hover { border-color: var(--harbor); }
.ck-acct .nm { display: block; font-size: 12.5px; font-weight: 700; color: var(--muted); }
.ck-acct .bl {
  display: block; font-family: var(--serif); font-size: 19px; color: var(--navy);
  font-variant-numeric: tabular-nums; margin-top: 1px;
}
.ck-acct .rs { display: block; font-size: 10.5px; letter-spacing: .06em; text-transform: uppercase; color: var(--gold-deep); font-weight: 700; }
.ck-acct.on { border-color: var(--navy); box-shadow: inset 0 0 0 1px var(--navy); background: #FBFCFD; }
.ck-acct.on .nm { color: var(--navy); }

.ck-wrap { border: 1px solid var(--line); border-radius: 12px; overflow: hidden; background: #fff; }
.ck-scroll { max-height: 640px; overflow: auto; }

table.ck { width: 100%; border-collapse: collapse; font-size: 13.5px; }
table.ck thead th {
  position: sticky; top: 0; z-index: 20; background: var(--navy); color: #C9D6E4;
  text-align: left; font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase;
  font-weight: 700; padding: 9px 10px; white-space: nowrap;
}
table.ck thead th.n { text-align: right; }
table.ck thead th.c { width: 34px; text-align: center; }
table.ck thead th.dt { width: 104px; }
table.ck thead th.rf { width: 76px; }
table.ck thead th.cat { width: 178px; }
table.ck thead th.n { width: 110px; }
table.ck thead th.bal { width: 124px; }
table.ck thead th.ex { width: 34px; }

table.ck tbody tr.row { border-bottom: 1px solid #EDF0F3; }
/* Alternating pairs, not single rows: the eye tracks a two-row band across a
   wide table far better than a one-row stripe. */
table.ck tbody tr.row:nth-child(4n-1), table.ck tbody tr.row:nth-child(4n) { background: #FAFBFC; }
table.ck tbody tr.row:hover { background: #F2F7FC; }
table.ck tbody tr.row.open { background: #EAF2FA; }
table.ck tbody tr.row.future { background: #FFFBF0; }
table.ck td { padding: 0; height: 30px; vertical-align: middle; }

.ck-cell { display: block; padding: 5px 10px; min-height: 29px; line-height: 19px; cursor: text; }
.ck-cell.n {
  text-align: right; font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
  font-size: 12.5px; font-variant-numeric: tabular-nums; white-space: nowrap;
}
td.ck-ed:hover .ck-cell { background: #EEF4FA; box-shadow: inset 0 0 0 1px #C6D6E4; border-radius: 4px; }
td.ck-ed.active { box-shadow: inset 0 0 0 2px var(--harbor); background: #fff; position: relative; z-index: 6; }
td.ck-ed input, td.ck-ed select {
  width: 100%; border: none; outline: none; background: #fff; color: var(--ink);
  font-family: var(--sans); font-size: 13.5px; padding: 5px 8px; min-height: 29px;
}
td.ck-ed input.n {
  text-align: right; font-variant-numeric: tabular-nums;
  font-family: var(--mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
}
.ck-saved {
  position: absolute; right: 3px; top: 50%; transform: translateY(-50%);
  color: var(--good); font-size: 11px; opacity: 0; transition: opacity .2s; pointer-events: none;
}
.ck-saved.show { opacity: 1; }

table.ck .pay { color: var(--danger); }
table.ck .dep { color: var(--good); }
table.ck .bal { font-weight: 700; color: var(--navy); }
table.ck .ref { font-family: var(--mono, ui-monospace, Menlo, monospace); font-size: 11.5px; color: var(--muted); letter-spacing: .03em; }
table.ck .cat-txt { color: var(--muted); font-size: 12.5px; }
table.ck .restricted-dot { color: var(--gold-deep); font-weight: 700; }

/* the cleared column — a checkbook's "C" */
.ck-c {
  width: 34px; text-align: center; cursor: pointer; user-select: none;
  color: #CBD3DB; font-weight: 700; font-size: 13px;
}
.ck-c.on { color: var(--good); }
.ck-c:hover { background: #E8F3EC; }

.ck-ex { text-align: center; color: #A9B4BF; cursor: pointer; user-select: none; font-size: 12px; }
.ck-ex:hover { color: var(--navy); }

/* the always-blank entry row */
tr.ck-new { background: #FFFCF2 !important; border-top: 2px solid var(--gold); }
tr.ck-new .ck-cell { color: #A6AFB9; }
tr.ck-new td.ck-ed:hover .ck-cell { background: #FBF4DE; box-shadow: inset 0 0 0 1px #E3D096; }

/* inline detail — opens in place so you never lose your spot in the register */
tr.ck-detail > td { padding: 0; background: #EAF2FA; }
.ck-detail-in {
  padding: 14px 18px 16px 44px; display: flex; gap: 26px; flex-wrap: wrap;
  align-items: flex-start; border-bottom: 2px solid #CFE0EF;
}
.ck-detail-in .f { font-size: 12.5px; }
.ck-detail-in .f b {
  display: block; font-size: 10.5px; letter-spacing: .09em; text-transform: uppercase;
  color: var(--muted); margin-bottom: 3px; font-weight: 800;
}
.ck-detail-in .acts { margin-left: auto; display: flex; gap: 8px; flex-wrap: wrap; }

.ck-foot {
  display: flex; gap: 14px; align-items: center; flex-wrap: wrap;
  padding: 11px 16px; background: #F9FAFB; border-top: 1px solid var(--line);
  font-size: 13.5px; color: var(--muted);
}
.ck-hint { font-size: 12.5px; color: var(--muted); }
.kbd {
  font-family: var(--mono, ui-monospace, Menlo, monospace); font-size: 11px;
  background: #fff; border: 1px solid var(--line); border-bottom-width: 2px;
  border-radius: 5px; padding: 1px 5px; color: var(--navy);
}
.ck-empty { padding: 40px; text-align: center; color: var(--muted); }

@media (max-width: 860px) {
  table.ck thead th.rf, table.ck tbody td.rf,
  table.ck thead th.cat, table.ck tbody td.cat-cell { display: none; }
  .ck-scroll { max-height: 460px; }
}

/* ----- add / edit transaction: a touch-friendly form ----------------------
   The inline register needs a mouse; this modal is how a phone, tablet or
   Kindle adds and edits entries. Posts to the same endpoint — same books. */
.bk-addbar { display: flex; justify-content: flex-end; margin-bottom: 12px; }
.bk-addbar .btn { min-height: 44px; }

.tx-modal {
  position: fixed; inset: 0; z-index: 120;
  background: rgba(11, 26, 42, .55);
  display: flex; align-items: flex-end; justify-content: center;
}
.tx-sheet {
  background: #fff; width: 100%; max-width: 520px; max-height: 92vh;
  border-radius: 16px 16px 0 0; overflow-y: auto;
  box-shadow: 0 -8px 40px rgba(0, 0, 0, .32);
  display: flex; flex-direction: column;
  -webkit-overflow-scrolling: touch;
}
.tx-head {
  position: sticky; top: 0; z-index: 2; background: var(--navy); color: #fff;
  padding: 15px 18px; display: flex; align-items: center; gap: 12px;
}
.tx-head h4 { margin: 0; font-size: 17px; flex: 1; color: #fff; }
.tx-head .x {
  background: none; border: none; color: #C9D6E4; font-size: 26px;
  line-height: 1; cursor: pointer; padding: 2px 8px; min-height: 40px;
}
.tx-head .x:hover { color: #fff; }
.tx-body { padding: 16px 18px; display: flex; flex-direction: column; gap: 14px; }
.tx-f { display: flex; flex-direction: column; gap: 6px; }
.tx-f > label {
  font-size: 12px; font-weight: 800; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted);
}
.tx-f .tx-opt { font-weight: 400; letter-spacing: 0; text-transform: none; color: #9AA5B1; }
.tx-f input, .tx-f select {
  /* 16px keeps iOS Safari from zooming the page in when a field is focused. */
  font-size: 16px; font-family: var(--sans); color: var(--ink);
  padding: 12px; min-height: 48px; width: 100%;
  border: 1.5px solid var(--line); border-radius: 10px; background: #fff;
}
.tx-f input:focus, .tx-f select:focus {
  outline: none; border-color: var(--harbor); box-shadow: 0 0 0 3px rgba(31, 74, 120, .16);
}
/* The refund tick on the transaction sheet. A real row on its own line rather
   than a cramped checkbox beside the amount: it changes what the category list
   MEANS, so it has to be read before the category is chosen, not noticed
   afterwards. 44px of target because it is pressed with a thumb. */
.tx-contra{display:flex;align-items:center;gap:10px;min-height:44px;
  margin:2px 0 2px;padding:8px 10px;border:1px solid var(--line);
  border-radius:10px;background:var(--surface-2);cursor:pointer;}
.tx-contra input{width:18px;height:18px;flex:none;accent-color:var(--gold);cursor:pointer;}
.tx-contra span{font-size:14px;color:var(--muted);line-height:1.3;}
.tx-contra:has(input:checked){border-color:var(--gold);background:var(--gold-tint);}
.tx-contra:has(input:checked) span{color:var(--ink);font-weight:600;}
.tx-two { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.tx-kind { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.tx-kind button {
  min-height: 50px; border: 1.5px solid var(--line); border-radius: 10px;
  background: #fff; color: var(--muted); font-size: 15.5px; font-weight: 700;
  cursor: pointer; font-family: var(--sans);
}
.tx-kind button.on[data-k="income"]  { border-color: var(--good);   background: #E4F3EB; color: #16603C; }
.tx-kind button.on[data-k="expense"] { border-color: var(--danger); background: #FBEAE8; color: #8C2B22; }
.tx-msg:empty { display: none; }
.tx-foot {
  position: sticky; bottom: 0; background: #fff; border-top: 1px solid var(--line);
  padding: 14px 18px; display: flex; gap: 10px; align-items: center;
}
.tx-foot .grow { flex: 1; }
.tx-foot .btn { min-height: 48px; }

@media (min-width: 620px) {
  .tx-modal { align-items: center; padding: 24px; }
  .tx-sheet { border-radius: 16px; }
  .tx-head { border-radius: 16px 16px 0 0; }
}

/* ==========================================================================
   REPORTS
   ========================================================================== */
.rep-controls {
  display: flex; gap: 14px; flex-wrap: wrap; align-items: flex-end;
  background: #fff; border: 1px solid var(--line); border-radius: 12px;
  padding: 14px 16px; margin-bottom: 18px;
}
.rep-controls .bk-field select, .rep-controls .bk-field input { margin: 0; }

.rep-preview {
  background: #fff; border: 1px solid var(--line); border-radius: 12px;
  padding: 26px 30px 30px; box-shadow: var(--shadow); margin-bottom: 22px;
  overflow-x: auto;   /* a wide report scrolls within the card, never the page */
}
.rep-preview h4 {
  font-family: var(--serif); font-weight: 400; font-size: 21px; margin: 0 0 2px;
}
.rep-preview .per { color: var(--muted); font-size: 14.5px; margin-bottom: 18px; }
.rep-preview table { width: 100%; border-collapse: collapse; font-size: 14px; }
.rep-preview th {
  text-align: left; font-size: 10.5px; letter-spacing: .09em; text-transform: uppercase;
  color: var(--muted); font-weight: 800; padding: 8px 9px; border-bottom: 1px solid var(--line);
}
.rep-preview th.n, .rep-preview td.n { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.rep-preview td { padding: 6px 9px; border-bottom: 1px solid #F1F3F5; }
.rep-preview tr.sec td {
  font-family: var(--serif); font-size: 16px; padding-top: 20px; border-bottom: 1px solid var(--line);
}
.rep-preview tr.tot td { border-top: 2px solid var(--navy); border-bottom: none; font-weight: 800; padding-top: 9px; }
.rep-preview tr.grand td {
  border-top: 3px double var(--navy); border-bottom: none; font-weight: 800;
  font-size: 15.5px; padding-top: 11px;
}
.rep-preview .pos { color: var(--good); } .rep-preview .neg { color: var(--danger); }
.rep-preview .muted { color: var(--muted); }
.rep-bar { display: inline-block; vertical-align: middle; width: 56px; height: 6px; border-radius: 3px; background: #E7EBEF; overflow: hidden; }
.rep-bar i { display: block; height: 100%; background: var(--harbor); }
.rep-bar i.over { background: var(--danger); }

.rep-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(288px, 1fr)); gap: 18px; }
.rep-mini { margin-top: 12px; border-top: 1px solid var(--line); padding-top: 12px; font-size: 13.5px; }
.rep-mini .r { display: flex; gap: 10px; padding: 3px 0; }
.rep-mini .r span:last-child { margin-left: auto; font-variant-numeric: tabular-nums; font-weight: 700; color: var(--ink); }
.rep-mini .r span:first-child { color: var(--muted); }
.rep-verdict { border-radius: 9px; padding: 10px 13px; font-size: 13px; margin-top: 10px; line-height: 1.45; }
.rep-verdict.ok { background: #F1F9F4; border-left: 3px solid var(--good); color: #17603D; }
.rep-verdict.warn { background: #FBF7E9; border-left: 3px solid var(--gold-deep); color: #6E5809; }
.rep-verdict.bad { background: #FCF3F1; border-left: 3px solid var(--danger); color: #8E2B22; }

/* An exception on the tax card: what is wrong, why it matters at filing time,
   and the way to the screen it is fixed on. A count with no noun and no door
   is what this replaced. */
.rep-fix { margin-top: 9px; padding-top: 9px; border-top: 1px solid rgba(110, 88, 9, 0.18); }
.rep-fix .what { font-weight: 600; }
.rep-fix .why { margin-top: 3px; font-size: 12.5px; opacity: 0.82; }
.rep-fix .btn { margin-top: 8px; font-size: 13.5px; padding: 7px 15px; }
/* The verdict panel keeps its light amber in dark mode, so the button inside
   it has to keep its light-mode ink too — the portal's dark ghost button is
   pale grey, which on this background is a button you cannot read. */
:root.theme-dark .rep-fix .btn-ghost { color: var(--navy); border-color: var(--navy); }
:root.theme-dark .rep-fix .btn-ghost:hover { background: var(--navy); color: #fff; }

/* ==========================================================================
   ACCOUNTS (setup)
   ========================================================================== */
.acct-list { display: grid; gap: 10px; margin-bottom: 16px; }
.acct-row {
  /* Type carries the longest option text ("Checking / bank"); Restriction now
     shows the short form, so the width moves from the one to the other. */
  display: grid; grid-template-columns: 1.6fr 1.2fr 1fr 1fr 1fr auto;
  /* Top-aligned, never centred: the name cell carries a note underneath and is
     the tallest, so centring pushed the other five labels half a note down the
     row and no two of them shared a line. */
  gap: 12px; align-items: start; background: #fff; border: 1px solid var(--line);
  border-radius: 11px; padding: 12px 15px;
}
.acct-row.restricted { border-left: 4px solid var(--gold); }
.acct-row .nm { font-weight: 700; color: var(--ink); font-size: 15px; }
.acct-row .nm small { display: block; font-weight: 400; font-size: 12px; color: var(--muted); }
.acct-row label {
  margin: 0 0 3px; font-size: 10.5px; letter-spacing: .07em; text-transform: uppercase;
  color: var(--muted); font-weight: 700; display: block;
}
/* Safari centres a date input's text; every other field on this card is left
   aligned, so the two date columns read as a different kind of control. */
.acct-row input[type="date"], .acct-add input[type="date"] { text-align: left; }
.acct-row input, .acct-row select { margin: 0; font-size: 14px; padding: 7px 9px; min-height: 36px; }
/* The balance cell wears a label like every other cell on the row. It used to
   carry its caption underneath as a <small>, which left it the one cell with
   nothing on the label line — so it floated against its neighbour instead of
   lining up with it. */
.acct-row .bal-line { display: flex; align-items: center; gap: 8px; }
.acct-row .bal {
  font-family: var(--serif); font-size: 18px; color: var(--navy);
  font-variant-numeric: tabular-nums; line-height: 36px;
}
.acct-row .bal-cell { text-align: right; }
.acct-row .bal-cell .bal-line { justify-content: flex-end; }
.acct-add {
  /* Same column ratio as the rows above it, so the add form reads as another
     line of the same table rather than a form that happens to sit under one. */
  display: grid; grid-template-columns: 1.6fr 1.2fr 1fr 1fr 1fr auto;
  gap: 12px; align-items: end; background: #FFFCF2; border: 1px dashed var(--gold);
  border-radius: 11px; padding: 14px 15px;
}
.acct-add label { margin: 0 0 3px; font-size: 10.5px; letter-spacing: .07em; text-transform: uppercase; color: var(--muted); font-weight: 700; }
.acct-add input, .acct-add select { margin: 0; font-size: 14px; padding: 8px 9px; min-height: 38px; }
/* Collapse at 1024, not 940: iPad/Kindle landscape sat in the 941–1024 band
   with all six columns squeezed to ~8 characters each — the same crush the
   member roster had. Two columns is comfortable across the whole tablet band. */
@media (max-width: 1024px) {
  /* `minmax(0, 1fr)`, not `1fr`: a date input carries an intrinsic minimum
     width, and under plain `1fr` it pushed the left column eight pixels wider
     than the right — enough that no two fields on the card shared an edge. */
  .acct-row, .acct-add {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    /* Top-aligned, so the labels of a pair sit on one line. Centred, the name
       cell's note made it the tallest and its neighbour was pushed half a
       note down the card — the Type field sitting lower than Account name. */
    align-items: start;
  }
  /* The name owns a full row: it is the account's identity, its note reads as
     a sentence rather than a column, and pairing it with Type was what caused
     the drop in the first place. */
  .acct-row > div:first-child, .acct-add > div:first-child { grid-column: 1 / -1; }
  /* Balance and remove take a row of their own, money left, ✕ right. */
  .acct-row .bal-cell { grid-column: 1 / -1; text-align: left; }
  .acct-row .bal-cell .bal-line { justify-content: space-between; }
  .acct-row .bal { line-height: 1.2; }
  /* Full width rather than sharing a line with the date field, which on iOS
     is wide enough to collide with it. */
  .acct-add button[type="submit"] { grid-column: 1 / -1; width: 100%; margin-top: 2px; }
}

/* tax-line selector on a category line */
.cat-item .tax { font-size: 12px; flex: 0 1 auto; min-width: 0; }
.cat-item .tax select { font-size: 12.5px; padding: 4px 7px; max-width: 190px; width: 100%; }

/* Five controls will not fit across a phone. Squeezed onto one line the tax
   select collapsed to nothing but its arrow and the remove button ran off the
   card, so below this width the line breaks in two: what the line is called on
   top, what it rolls into and what it is budgeted underneath. */
@media (max-width: 640px) {
  .cat-item {
    display: grid; align-items: center; gap: 9px 10px;
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-areas: "ic nm del" "tax tax bud";
  }
  .cat-item .ic { grid-area: ic; line-height: 1; }
  .cat-item .nm { grid-area: nm; }
  /* The remove button shares a column with the budget box below it, so it is
     pushed to that column's right edge — otherwise the two ends of the card
     disagree by the difference in their widths. */
  .cat-item .btns { grid-area: del; line-height: 1; justify-self: end; }
  .cat-item .btns button { width: 34px; height: 34px; }
  .cat-item .tax { grid-area: tax; }
  .cat-item .tax select { max-width: none; }
  .cat-item .bud-input { grid-area: bud; }
}

@media print {
  .portal-rail, .subnav, .bk-toolbar, .ck-accounts, .ck-foot, .audit-bar, .rep-controls,
  .rep-cards, .site-header, .site-footer { display: none !important; }
  .portal-shell { display: block !important; }
  /* The menu is not printed, so the room reserved for it must not be either —
     a 236px empty gutter down the left of every printed report. */
  body[data-page="board"] #portal-view { padding-left: 0 !important; }
  body[data-page="board"] #portal-view > .wrap { padding: 0 !important; }
  .ck-scroll { max-height: none; overflow: visible; }
  tr.ck-new { display: none; }
}

/* ==========================================================================
   SAMPLE-DATA MARKING
   The application ships with demonstration data. The failure this styling
   exists to prevent is a demo figure being mistaken for a real one, so the
   marking is deliberately loud and deliberately everywhere — and it survives
   printing, because a printed page outlives the screen it came from.
   ========================================================================== */
.demo-banner {
  display: flex; gap: 14px; align-items: center; flex-wrap: wrap;
  background: linear-gradient(135deg, #8E2B22, #B3362B);
  color: #fff; border-radius: 12px; padding: 15px 20px; margin: 20px 0 4px;
  box-shadow: 0 8px 22px rgba(179, 54, 43, .26);
}
.demo-banner .ic { font-size: 24px; line-height: 1; }
.demo-banner .t { font-weight: 800; font-size: 15.5px; letter-spacing: .02em; }
.demo-banner .s { font-size: 13.5px; color: #FBE4E1; margin-top: 2px; max-width: 760px; }
.demo-banner .go {
  margin-left: auto; appearance: none; cursor: pointer; white-space: nowrap;
  background: #fff; color: #8E2B22; border: none; border-radius: 8px;
  padding: 10px 16px; font: 700 13.5px/1 var(--sans);
}
.demo-banner .go:hover { background: #FFF3F1; }
.demo-banner.mixed { background: linear-gradient(135deg, #7A1F17, #A32C22); }

/* a sample row in the register */
.ck-demo {
  display: inline-block; font-size: 9.5px; font-weight: 800; letter-spacing: .09em;
  text-transform: uppercase; color: #B3362B; background: #FBEAE8;
  border: 1px solid #EFC9C4; border-radius: 4px; padding: 0 4px; margin-left: 6px;
  vertical-align: 1px;
}
table.ck tbody tr.row.demo-row { background: #FDF6F5; }
table.ck tbody tr.row.demo-row:hover { background: #FBEDEB; }
table.ck tbody tr.row.demo-row td:first-child { box-shadow: inset 3px 0 0 #E7B9B2; }

/* going live */
.card.golive { border: 2px solid var(--danger); }
.golive-sum { display: grid; gap: 7px; margin: 16px 0 6px; }
.golive-sum .r {
  display: flex; gap: 12px; align-items: center; font-size: 14px;
  padding: 8px 13px; border: 1px solid var(--line); border-radius: 9px; background: #fff;
}
.golive-sum .r .n { margin-left: auto; font-variant-numeric: tabular-nums; }
.golive-sum .r .n b { color: var(--danger); }
.golive-sum .r .n small { color: var(--muted); font-weight: 400; }
.golive-sum .r.keep { background: #F4FAF6; border-color: #C4E3D1; }
.golive-sum .r.cfg { background: #FFFCF2; border-color: #E7DCB0; }
.golive-act { display: flex; gap: 11px; align-items: center; flex-wrap: wrap; margin-top: 16px; }
.btn-danger { background: var(--danger); border-color: var(--danger); color: #fff; }
.btn-danger:hover { background: #8E2B22; border-color: #8E2B22; }
.golive-live {
  background: #F1F9F4; border: 1px solid #BFE0CE; border-radius: 10px;
  padding: 14px 18px; font-size: 14.5px; color: #17603D;
}

@media print {
  .demo-banner { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
  .demo-banner .go { display: none; }
}

/* ==========================================================================
   BOARD ACCESS & DELEGATION
   The screen a board looks at to answer "who can move our money?". It is
   built to make one answer obvious at a glance and everything else secondary.
   ========================================================================== */
.acc-review { border-radius: 12px; padding: 16px 20px; margin-bottom: 4px; display: flex; gap: 14px; align-items: flex-start; }
.acc-review .ic { font-size: 22px; line-height: 1.2; }
.acc-review .t { font-weight: 800; font-size: 15.5px; margin-bottom: 3px; }
.acc-review .m { font-size: 14px; line-height: 1.5; max-width: 820px; }
.acc-review .r { font-size: 13.5px; margin-top: 7px; font-style: italic; opacity: .9; }
.acc-review.ok { background: #F1F9F4; border: 1px solid #BFE0CE; color: #17603D; }
.acc-review.warn { background: #FBF7E9; border: 1px solid #E7DCB0; color: #6E5809; }
.acc-review.error { background: #FCF3F1; border: 1px solid #E8C6C1; color: #8E2B22; }
.acc-review + .acc-review { margin-top: 8px; }

.acc-grid { display: grid; gap: 10px; }
.acc-person {
  background: #fff; border: 1px solid var(--line); border-radius: 12px; padding: 15px 18px;
  display: grid; grid-template-columns: minmax(190px, 1.1fr) 2.4fr auto; gap: 18px; align-items: center;
}
.acc-person.keeper { border-left: 4px solid var(--gold); }
.acc-person.concentrated { border-left: 4px solid var(--danger); background: #FFFBFA; }
.acc-person .who .n { font-weight: 700; font-size: 15.5px; color: var(--ink); }
.acc-person .who .e { font-size: 12.5px; color: var(--muted); }
/* "correct" beside an address. Deliberately quiet — it is a repair, not one of
   the row's actions, and it sits with the thing it edits rather than in the
   button column, which is already three buttons wide on a phone. */
.lnk-edit {
  background: none; border: 0; padding: 0 0 0 4px; font: inherit; font-size: 11.5px;
  color: var(--link, #2C6E9B); text-decoration: underline; cursor: pointer;
}
.lnk-edit:hover, .lnk-edit:focus-visible { color: var(--ink); }
.addr-edit { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin: 4px 0 0; }
.addr-input {
  flex: 1 1 190px; min-width: 0; font-size: 13px; padding: 6px 8px;
  border: 1px solid var(--line, #D9D6C9); border-radius: 7px;
  background: var(--field-bg, #fff); color: var(--ink);
}
.addr-msg:empty { display: none; }
.addr-msg .notice { margin: 8px 0 0; padding: 9px 11px; font-size: 13px; line-height: 1.45; }
.acc-person .who .rl {
  display: inline-block; font-size: 10.5px; letter-spacing: .08em; text-transform: uppercase;
  font-weight: 800; color: var(--harbor); margin-top: 3px;
}
/* "no password set yet" is the single most actionable line on this screen —
   it is how the club finds out that four of seven officers never got in — and
   it was 11.5px at 3.89:1, which is under the contrast floor and the smallest
   text in the portal. 14px in the danger colour: it is a problem, not a note. */
.acc-person .who .pend { color: var(--danger); font-weight: 700; font-size: 14px; }
/* The role, when it can be changed. Sized and coloured like the label it
   replaces, so the row reads the same whether or not you may edit it. */
.acc-person .who .rl-edit {
  /* 27px tall, on the control that changes somebody's job title. */
  min-height: 44px;
  margin-top: 4px; width: auto; max-width: 100%; padding: 4px 26px 4px 8px;
  font-size: 12px; font-weight: 700; letter-spacing: .04em; color: var(--harbor);
  border: 1px solid var(--line); border-radius: 7px; background-color: #FBFCFD;
}
.acc-person .who .rl-edit:hover { border-color: var(--harbor); }
/* The maintenance account: visibly outside the board, not styled as a warning. */
.acc-person.admin { border-left: 4px solid var(--harbor); background: #FAFBFD; }
.acc-person .who .rl.admin { color: var(--muted); font-style: italic; text-transform: none; letter-spacing: 0; font-size: 11.5px; }
/* WHAT YOU HAVE TO HIT, ON THE SCREEN THAT DECIDES WHO MOVES THE MONEY.
   --------------------------------------------------------------------------
   The box that grants a duty was 13×13px. Apple's own minimum is 44, and this
   is a volunteer in their seventies, on a phone, granting somebody the power to
   record and delete financial entries. The whole chip is the target now, which
   it always claimed to be — it is a <label> wrapping the box — and it is 44px
   tall so the claim is true. The box itself is 20px, because a tick nobody can
   see is a state nobody can check. */
.acc-caps { display: flex; flex-wrap: wrap; gap: 8px; }
.acc-cap {
  display: inline-flex; align-items: center; gap: 9px; cursor: pointer; user-select: none;
  border: 1px solid var(--line); border-radius: 10px; padding: 8px 13px; font-size: 14px;
  background: #FBFCFD; color: var(--muted); min-height: 44px;
}
.acc-cap:hover { border-color: var(--harbor); }
.acc-cap input { margin: 0; width: 20px; height: 20px; flex: 0 0 auto; accent-color: var(--harbor); }
.acc-cap.on { background: #EAF2FA; border-color: var(--harbor); color: var(--navy); font-weight: 600; }
.acc-cap.on.sensitive { background: #FBF3E4; border-color: var(--gold); color: #6E5809; }
.acc-cap[aria-disabled="true"] { opacity: .5; cursor: not-allowed; }
.acc-person .act { display: flex; gap: 8px; align-items: center; }
.acc-granted { font-size: 11.5px; color: var(--muted); margin-top: 6px; }
@media (max-width: 900px) { .acc-person { grid-template-columns: 1fr; } }

/* read-only finance area for a board member who does not keep the books */
.ro-note {
  display: flex; gap: 12px; align-items: center; flex-wrap: wrap;
  background: #EEF3F8; border: 1px solid #C6D6E4; border-radius: 11px;
  padding: 13px 17px; margin-bottom: 16px; font-size: 14.5px; color: var(--navy);
}
.ro-note .ic { font-size: 19px; }
.ro-note b { font-weight: 800; }

/* ==========================================================================
   THE DOOR SHEET
   Attendance, taken at the door on a phone or a tablet by whoever happens to
   be sitting there. Two constraints shape the whole screen:

     · the marks must be big enough to hit accurately while somebody is
       talking to you and taking your $3 — hence 44px targets, always three
       across, never a dropdown; and
     · the money must be legible without scrolling, because the reason the
       sheet exists is to be compared with the cash box.
   ========================================================================== */
.door-hero {
  background: linear-gradient(135deg, var(--navy-deep), var(--navy-lift));
  border-radius: 18px; padding: 24px 28px 22px; color: #fff;
  box-shadow: 0 18px 44px rgba(15,42,67,.25); margin-bottom: 20px;
  position: relative; overflow: hidden;
}
.door-hero::after {
  content: ""; position: absolute; right: -60px; top: -70px; width: 300px; height: 300px;
  background: url("/brand/mark.svg?role=watermark") no-repeat center / contain; opacity: .07; pointer-events: none;
}
.door-hero .dh-head { display: flex; align-items: baseline; gap: 14px; margin-bottom: 8px; }
.door-hero .dh-head h3 { color: #fff; font-family: var(--serif); font-size: 22px; }
.door-hero .asof { color: var(--gold); font-size: 13.5px; margin-left: auto; font-weight: 600; }
.door-hero > p { color: #A9BBCE; font-size: 15px; max-width: 780px; }
.door-hero strong { color: #fff; }
.dh-pick { display: flex; gap: 14px; flex-wrap: wrap; align-items: flex-end; margin-top: 18px; position: relative; }
.dh-pick .bk-field label { color: #8FA5BC; }
.dh-pick .bk-field input, .dh-pick .bk-field select { border-color: rgba(255,255,255,.2); }

.door-tiles { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin-bottom: 18px; }
.door-tiles .fh-tile { background: var(--navy); border-color: rgba(255,255,255,.14); }
@media (max-width: 900px) { .door-tiles { grid-template-columns: repeat(2, 1fr); } }

.door-grid { display: grid; grid-template-columns: 1.25fr 1fr; gap: 20px; align-items: start; }
@media (max-width: 1080px) { .door-grid { grid-template-columns: 1fr; } }
.door-side { display: flex; flex-direction: column; gap: 18px; }
.door-note { font-size: 14px; color: var(--muted); line-height: 1.5; margin-bottom: 12px; }
.door-note .opt, label .opt { font-weight: 400; color: var(--muted); }

.door-rosterhead { display: flex; gap: 12px; align-items: flex-end; flex-wrap: wrap; margin-bottom: 12px; }
.door-rosterhead p { font-size: 13.5px; color: var(--muted); margin-top: 3px; }
.door-rosterhead input[type="search"] { margin: 0; max-width: 220px; }
.door-filter { display: flex; gap: 6px; }
.door-legend { display: flex; gap: 14px; flex-wrap: wrap; padding: 10px 0 14px; border-top: 1px solid var(--line); }
.door-legend .dl { display: inline-flex; align-items: center; gap: 7px; font-size: 13px; color: var(--muted); }
.door-legend .dl b {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 26px; height: 24px; padding: 0 6px; border-radius: 7px;
  color: #fff; font-size: 12.5px; font-weight: 800;
}

/* The roster scrolls inside its own card rather than running the page to five
   thousand pixels: at the door, the running totals and the cash box have to
   stay on screen while you work down the names. */
#att-roster { max-height: 62vh; overflow-y: auto; overscroll-behavior: contain; }

/* one member, one row, three targets */
.door-row {
  display: flex; align-items: center; gap: 12px;
  padding: 7px 4px 7px 10px; border-bottom: 1px solid var(--line);
}
.door-row:last-child { border-bottom: 0; }
.door-row.on { background: #F4F8FB; border-radius: 9px; }
.door-row.saving { opacity: .55; }
.door-row .who { flex: 1; min-width: 0; }
.door-row .who b { display: block; font-size: 15px; color: var(--ink); font-weight: 600; }
.door-row .who small { display: block; font-size: 12px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.door-row .dmarks { display: flex; gap: 6px; flex: none; }
.door-row .dm {
  min-width: 44px; height: 44px; border-radius: 11px; cursor: pointer;
  border: 1.5px solid var(--line); background: #fff; color: var(--muted);
  font-size: 15px; font-weight: 800; line-height: 1; padding: 0 8px;
}
.door-row .dm:hover { border-color: var(--harbor); color: var(--navy); }
.door-row .dm.on { color: #fff; border-width: 1.5px; box-shadow: 0 2px 8px rgba(15,42,67,.2); }
.door-row .dm:disabled { opacity: .4; cursor: not-allowed; }

/* guests — the only place a first-timer's email is ever captured */
.guest-row {
  display: grid; grid-template-columns: 30px 1fr auto auto; gap: 10px; align-items: center;
  padding: 9px 0; border-bottom: 1px solid var(--line); font-size: 14.5px;
}
.guest-row:last-of-type { border-bottom: 0; }
.guest-row .gk {
  width: 28px; height: 28px; border-radius: 9px; background: var(--harbor-tint);
  display: flex; align-items: center; justify-content: center; font-weight: 800; color: var(--navy);
}
.guest-row .gn b { display: block; font-weight: 600; color: var(--ink); }
.guest-row .gn small { font-size: 12.5px; color: var(--muted); }
.guest-row .ga { font-variant-numeric: tabular-nums; font-weight: 700; }
.guest-row .gb { display: flex; gap: 6px; align-items: center; }
.guest-row .gb .btn { white-space: nowrap; }
/* "no email — ask before they leave" was a note; it is the button now. A guest
   with no address needs both actions available — record the address, and put
   them on the roster — and this column is far too narrow for two buttons: they
   squeezed the guest's own name into three wrapped lines, which is not what
   somebody working a queue at the door should be reading. The hint was already
   sitting there saying what to do, so it does it. */
.guest-row .gn-add {
  display: inline-block; padding: 0; border: 0; background: none;
  font: inherit; font-size: 12.5px; color: var(--muted);
  text-decoration: underline; text-underline-offset: 2px; cursor: pointer;
}
.guest-row .gn-add:hover { color: var(--navy); }
.guest-row .gj { font-size: 12px; color: var(--good); font-weight: 700; }
.door-guestform { display: grid; grid-template-columns: 1.1fr 1.4fr; gap: 9px; margin-top: 14px; align-items: end; }
.door-guestform .g-kind { grid-column: 1 / 2; }
.door-guestform .g-amt { grid-column: 2 / 3; }
.door-guestform button { grid-column: 1 / -1; }
.door-guestform label, .door-extraform label { margin: 0 0 3px; font-size: 11.5px; letter-spacing: .07em; text-transform: uppercase; color: var(--muted); }
.door-guestform input, .door-guestform select, .door-extraform input, .door-extraform select { margin: 0; }
@media (max-width: 620px) { .door-guestform { grid-template-columns: 1fr; } .door-guestform .g-kind, .door-guestform .g-amt { grid-column: 1 / -1; } }

/* the cash box */
.money-lines { border: 1px solid var(--line); border-radius: 11px; overflow: hidden; }
.money-lines .ml {
  display: flex; align-items: center; gap: 12px; padding: 10px 14px;
  border-bottom: 1px solid var(--line); font-size: 14.5px;
}
.money-lines .ml:last-child { border-bottom: 0; }
.money-lines .ml .l { flex: 1; color: var(--ink); }
.money-lines .ml .l small { display: block; font-size: 12px; color: var(--muted); }
.money-lines .ml .v { font-variant-numeric: tabular-nums; font-weight: 700; }
.money-lines .ml.total { background: #F7F9FB; font-weight: 800; }
.money-lines .ml.total .l, .money-lines .ml.total .v { color: var(--navy); font-size: 16px; }
.money-lines .ml.good { background: #F1F9F4; color: #17603D; font-weight: 700; }
.money-lines .ml.bad { background: #FCF3F1; color: #8E2B22; font-weight: 700; }
.door-count { display: flex; gap: 12px; flex-wrap: wrap; margin-top: 14px; }
.door-count .bk-field input { margin: 0; }
.extra-list { margin-top: 12px; }
.ex-row {
  display: grid; grid-template-columns: 1fr auto 32px; gap: 10px; align-items: center;
  font-size: 14px; padding: 7px 0; border-bottom: 1px dashed var(--line);
}
.ex-row small { display: block; font-size: 12px; color: var(--muted); }
.door-extraform { display: grid; grid-template-columns: 1fr 92px 1fr auto; gap: 8px; align-items: end; margin-top: 12px; }
@media (max-width: 620px) { .door-extraform { grid-template-columns: 1fr; } }

.door-post { border: 2px solid var(--gold); }
.door-settings { border: 1px solid var(--line); border-radius: var(--radius, 14px); background: #fff; }
.door-settings > summary {
  cursor: pointer; padding: 15px 20px; font-weight: 700; color: var(--navy); font-size: 15px; list-style: none;
}
.door-settings > summary::-webkit-details-marker { display: none; }
.door-settings > summary::before { content: "▸ "; color: var(--gold-deep); }
.door-settings[open] > summary::before { content: "▾ "; }
.door-setgrid { display: grid; grid-template-columns: 1fr 1fr; gap: 22px; }
@media (max-width: 760px) { .door-setgrid { grid-template-columns: 1fr; } }
.set-group h4 {
  font-size: 11.5px; letter-spacing: .1em; text-transform: uppercase; color: var(--gold-deep);
  margin-bottom: 8px; font-weight: 800;
}
.set-group label { font-size: 13px; margin-top: 10px; }
.set-group input, .set-group select { margin: 0; }

/* ---- the month grid: the paper sheet, one column per night ---- */
.grid-scroll { overflow-x: auto; border: 1px solid var(--line); border-radius: 12px; background: #fff; }
table.grid-sheet { width: 100%; border-collapse: collapse; font-size: 14px; }
table.grid-sheet th, table.grid-sheet td { border-bottom: 1px solid var(--line); padding: 6px 8px; }
table.grid-sheet .mname {
  text-align: left; white-space: nowrap; position: sticky; left: 0; background: #fff;
  font-weight: 600; color: var(--ink); box-shadow: 1px 0 0 var(--line); max-width: 220px; overflow: hidden; text-overflow: ellipsis;
}
table.grid-sheet thead th { background: #F7F9FB; font-size: 12px; color: var(--muted); }
table.grid-sheet thead .mname { background: #F7F9FB; }
table.grid-sheet .gcol { width: 44px; text-align: center; }
table.grid-sheet .gcol .d { display: block; font-size: 15px; font-weight: 800; color: var(--navy); }
table.grid-sheet .gcol .w { display: block; font-size: 10.5px; color: var(--muted); }
table.grid-sheet .gcell { text-align: center; }
table.grid-sheet .gcell.money { font-size: 12px; font-variant-numeric: tabular-nums; color: var(--muted); }
table.grid-sheet tr.none .mname { color: var(--muted); font-weight: 400; }
table.grid-sheet .n { text-align: right; font-variant-numeric: tabular-nums; font-weight: 700; width: 62px; }
table.grid-sheet tfoot td, table.grid-sheet tfoot th { background: #F7F9FB; font-size: 12.5px; }
.gm {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 24px; height: 22px; padding: 0 5px; border-radius: 6px;
  color: #fff; font-size: 11.5px; font-weight: 800;
}

@media print {
  .portal-rail, .subnav, .door-hero, #att-grid-stats,
  #att-csv, #att-print, .demo-banner { display: none !important; }
  body[data-page="board"] #portal-view { padding-left: 0 !important; }
  .grid-scroll { border: 0; overflow: visible; }
  table.grid-sheet { font-size: 11px; }
  .gm { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
}

/* Messages from the door-hero buttons sit inside the hero, on a dark ground. */
.door-hero #att-top-msg:not(:empty) { margin-top: 14px; position: relative; }
.door-hero #att-top-msg .notice { margin: 0; }
.door-hero #att-top-msg .notice-err { background: #3A1614; border-color: #8E4139; color: #FFD9D3; }
.door-hero #att-top-msg .notice-ok { background: #10352A; border-color: #2E6B55; color: #B9EAD6; }

/* ==========================================================================
   Wave 2 — Design Review II proposals 3, 5 and 6
   ========================================================================== */

/* -------- the pane toolbar: a fixed home for the action -------------------
   Every pane header carries, pinned right: search (⌘K), the one primary
   action for the view, and secondary actions behind ⋯. It never moves, so
   it never has to be found. On desktop the header sticks while a long
   register scrolls. */
.ph-actions { display: flex; gap: 10px; align-items: center; flex: none; margin-left: auto; flex-wrap: wrap; }
.ph-actions .btn svg.ic { margin-right: 2px; }
/* `flex: none` above means this row sizes to its own content and never shrinks
   to the page, so `flex-wrap` had nothing to wrap inside: a long call to
   action — "Invite a board member", "Open an attendance sheet", "Record new
   minutes" — pushed the whole document 23–49px wider than the screen, and
   every portal section scrolled sideways on a phone. Below the tablet
   breakpoint the row takes a line of its own and its buttons share it.

   This block sits *after* the base rule deliberately. The same override lived
   in the phone media query further up the file and did nothing, because
   `.ph-actions { flex: none }` here has equal specificity and comes later. */
@media (max-width: 1024px) {
  .ph-actions { flex: 1 1 100%; margin-left: 0; min-width: 0; }
  .ph-actions .btn, .ph-actions .ph-search { flex: 1 1 auto; justify-content: center; }
  .ph-actions .ph-menu-wrap { flex: none; }
  /* ⌘K means nothing to a touch screen */
  .ph-search kbd { display: none; }
}
/* On a phone the pane's ONE action gets the row: search folds to its icon
   (the palette itself is unchanged) and the gold button takes the width. */
@media (max-width: 760px) {
  .ph-actions .ph-search { flex: none; }
  .ph-search .ph-search-lbl { display: none; }
}
.ph-search {
  display: inline-flex; align-items: center; gap: 8px;
  font-family: var(--sans); font-size: 14px; font-weight: 600;
  color: var(--muted); background: var(--surface);
  border: 1.5px solid var(--hairline); border-radius: 10px;
  padding: 9px 13px; cursor: pointer; min-height: 42px;
}
.ph-search:hover { border-color: var(--harbor); color: var(--navy); }
.ph-search kbd {
  font-family: var(--sans); font-size: 11px; font-weight: 700;
  background: var(--surface-2); border: 1px solid var(--hairline);
  border-radius: 5px; padding: 1px 6px; color: var(--muted);
}
.ph-more {
  width: 42px; height: 42px; border-radius: 10px; flex: none;
  border: 1.5px solid var(--hairline); background: var(--surface);
  color: var(--navy); cursor: pointer; display: grid; place-items: center;
}
.ph-more:hover { border-color: var(--harbor); }
.ph-menu {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 80;
  background: var(--surface); border: 1px solid var(--hairline); border-radius: 12px;
  box-shadow: var(--shadow-lg); padding: 6px; min-width: 230px;
}
.ph-menu button {
  display: flex; gap: 10px; align-items: center; width: 100%;
  font-family: var(--sans); font-size: 14.5px; font-weight: 600; text-align: left;
  color: var(--ink); background: none; border: none; border-radius: 8px;
  padding: 10px 12px; cursor: pointer;
}
.ph-menu button:hover { background: var(--surface-2); }
.ph-menu button.danger { color: var(--danger); }
.ph-menu-wrap { position: relative; }
@media (min-width: 1025px) {
  body[data-page="board"] .pane-head {
    position: sticky; top: 74px; z-index: 40;
    background: rgba(248, 247, 242, 0.93); backdrop-filter: blur(9px);
    padding: 10px 4px 12px; margin: -10px -4px 14px;
    border-radius: 0 0 14px 14px;
  }
}

/* -------- the command palette: one way to reach everything ---------------- */
.cmdk-scrim {
  position: fixed; inset: 0; z-index: 400;
  background: rgba(10, 29, 48, 0.48); backdrop-filter: blur(3px);
  display: flex; align-items: flex-start; justify-content: center;
  padding: 12vh 16px 16px;
}
.cmdk {
  width: min(600px, 100%); background: var(--surface);
  border-radius: 16px; overflow: hidden;
  border: 1px solid rgba(15, 42, 67, 0.12);
  box-shadow: 0 28px 70px rgba(10, 29, 48, 0.42);
}
.cmdk .field {
  display: flex; align-items: center; gap: 12px;
  padding: 16px 18px; border-bottom: 1px solid var(--hairline);
}
.cmdk .field svg.ic { width: 19px; height: 19px; color: var(--muted); }
.cmdk .field input {
  flex: 1; border: none !important; outline: none !important; padding: 0;
  font-family: var(--sans); font-size: 17px; color: var(--ink);
  background: none; min-height: 0;
}
.cmdk .field .esc {
  font-size: 11px; color: var(--muted); font-weight: 700;
  border: 1px solid var(--hairline); border-radius: 5px; padding: 2px 7px;
}
.cmdk .list { max-height: min(400px, 55vh); overflow: auto; padding: 7px; }
.cmdk .grp {
  font-size: 10.5px; letter-spacing: .14em; text-transform: uppercase;
  font-weight: 800; color: var(--muted); padding: 11px 11px 5px;
}
.cmdk .row {
  display: flex; gap: 12px; align-items: center; width: 100%;
  padding: 10px 11px; border-radius: 9px; cursor: pointer;
  background: none; border: none; font-family: var(--sans); text-align: left;
}
.cmdk .row.sel { background: var(--harbor-tint); }
.cmdk .row .ric {
  width: 28px; height: 28px; border-radius: 7px; flex: none;
  background: var(--surface-2); color: var(--navy);
  display: grid; place-items: center;
}
.cmdk .row .ric svg.ic { width: 15px; height: 15px; }
.cmdk .row .t { font-size: 14.5px; font-weight: 600; color: var(--ink); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmdk .row .h { font-size: 12.5px; color: var(--muted); margin-left: auto; flex: none; }
.cmdk .none { padding: 28px; text-align: center; color: var(--muted); font-size: 14.5px; }

/* -------- the member sheet: where the whole record lives ------------------
   One component, opened from the roster's ⋯ (and later by a long-press at
   the door). Fields autosave on blur — nothing here has a Save button to
   forget. */
.mem-sheet { width: min(440px, 96vw); }
.mem-sheet .drawer-body { padding: 18px 24px 24px; }
.ms-field { margin-bottom: 13px; }
.ms-field label {
  margin: 0 0 4px; font-size: 11px; letter-spacing: .1em;
  text-transform: uppercase; font-weight: 800; color: var(--muted);
}
.ms-field label .hint { font-weight: 400; text-transform: none; letter-spacing: 0; }

/* The club's own details, on Website → Club Details. Its labels carry a hint
   explaining what the field is for — "the line under the club's name on the
   home page" — and without this they inherit the label's weight and sit on the
   same line, so the label and its explanation read as one run-on sentence.
   Scoped to this pane rather than to every label in the portal: the same class
   is used elsewhere in markup this was not written for. */
#web-details label .hint {
  display: block;
  font-weight: 400;
  font-size: 12.5px;
  color: var(--muted);
  text-transform: none;
  letter-spacing: 0;
  margin-top: 2px;
}
#web-details h4 {
  font-size: 12px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 26px 0 4px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}
#web-details h4:first-of-type { border-top: none; padding-top: 0; }
/* Two fields side by side, aligned on the *controls* rather than on the boxes.
   "Birthday — any format; the year is discarded" wraps to two lines on a phone
   and "Joined" does not, so laying the pair out top-down put one input a whole
   line above the other. The cells stretch to the taller of the two and the
   input is pushed to the bottom of its own cell, which is the only alignment
   a reader is looking for: the boxes they type into. */
.ms-two { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; align-items: stretch; }
.ms-two .ms-field { display: flex; flex-direction: column; margin-bottom: 13px; }
.ms-two .ms-field input, .ms-two .ms-field select { margin-top: auto; }
.ms-status {
  background: var(--surface-2); border-radius: 10px; padding: 12px 15px;
  font-size: 13.5px; color: var(--muted); margin: 4px 0 16px; line-height: 1.55;
}
/* On a wide drawer the destructive action is pushed to the far edge, away from
   the one somebody actually means to press. Once the row wraps that margin
   left one button against the left edge and the other against the right, so
   below the wrap point they stack full width instead. */
.ms-actions { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 18px; }
@media (max-width: 560px) {
  .ms-actions { flex-direction: column; align-items: stretch; }
  .ms-actions .btn { width: 100%; margin-left: 0 !important; }
}

/* -------- the roster, taught by affordance ------------------------------- */
.mem-tip {
  display: flex; gap: 12px; align-items: center;
  background: var(--gold-tint); border: 1px solid #EBDCA8; border-radius: 11px;
  padding: 12px 16px; margin-bottom: 14px;
  font-size: 14px; color: #5C4A0C;
}
.mem-tip svg.ic { width: 18px; height: 18px; color: var(--gold-deep); flex: none; }
.mem-tip .btn { margin-left: auto; flex: none; }
.row-more {
  width: 34px; height: 34px; border-radius: 8px; border: 1px solid var(--hairline);
  background: var(--surface); color: var(--navy); cursor: pointer;
  display: inline-grid; place-items: center;
}
.row-more:hover { border-color: var(--harbor); background: var(--surface-2); }
.row-more svg.ic { width: 16px; height: 16px; }
th .col-hint { cursor: help; color: var(--gold-deep); font-weight: 700; }

/* ==========================================================================
   Wave 3 — Design Review II proposals 7, 8 and 9
   ========================================================================== */

/* -------- the register as an instrument -----------------------------------
   The running balance is the figure the whole screen exists to show, so it
   pins to the right edge and survives any horizontal scroll; the header
   already pins to the top. */
table.ck th.bal { position: sticky; right: 0; z-index: 21; }
table.ck td.bal {
  position: sticky; right: 0; z-index: 4; background: #fff;
  box-shadow: -7px 0 8px -7px rgba(15, 42, 67, 0.18);
}
table.ck tbody tr.row:nth-child(4n-1) td.bal,
table.ck tbody tr.row:nth-child(4n) td.bal { background: #FAFBFC; }
table.ck tbody tr.row:hover td.bal { background: #F2F7FC; }
table.ck tbody tr.row.open td.bal { background: #EAF2FA; }
table.ck tbody tr.row.future td.bal { background: #FFFBF0; }
tr.ck-new td.bal { background: #FFFCF2 !important; }

/* undo instead of confirm: the action happens, the way back stays on screen */
.undo-toast {
  position: fixed; left: 50%; bottom: 26px; transform: translateX(-50%);
  z-index: 450; display: flex; align-items: center; gap: 14px;
  background: var(--navy-deep); color: #fff; border-radius: 12px;
  padding: 13px 18px; font-size: 14px; box-shadow: 0 14px 34px rgba(10, 29, 48, 0.4);
  max-width: min(560px, calc(100vw - 32px));
}
.undo-toast button {
  background: none; border: none; color: var(--gold);
  font-family: var(--sans); font-weight: 800; font-size: 14px; cursor: pointer;
  padding: 4px 2px; flex: none;
}
.undo-toast .bar { width: 44px; height: 3px; background: rgba(255,255,255,.22); border-radius: 2px; overflow: hidden; flex: none; }
.undo-toast .bar i { display: block; height: 100%; width: 100%; background: var(--gold);
  animation: undo-drain 10s linear forwards; }
@keyframes undo-drain { from { width: 100%; } to { width: 0; } }

/* -------- Door Mode --------------------------------------------------------
   The one screen used standing up, one-handed, with a queue. Full-bleed:
   no rail, no header, no sub-nav. Search first, thumb-sized rows, the tally
   pinned to the bottom, the cash box one tap behind it. Dark follows the
   device (or the portal's own setting) — a white 400-nit screen in a dark
   hall blinds the person holding it. */
/* The way in: a full-width call to action at the head of the open sheet.
   Same gold as every other primary action, sized for the thumb that is about
   to spend the evening pressing it. */
.dm-cta {
  display: flex; align-items: center; gap: 14px; width: 100%;
  background: var(--gold); color: var(--navy-deep);
  border: none; border-radius: 14px; cursor: pointer;
  padding: 13px 18px; margin-bottom: 18px; text-align: left;
  font-family: var(--sans);
  box-shadow: 0 6px 18px rgba(201, 162, 39, 0.35);
  transition: transform 0.12s, box-shadow 0.12s, background 0.15s;
}
.dm-cta:hover { background: #DDB93E; }
.dm-cta:active { transform: translateY(1px); }
.dm-cta:disabled { opacity: 0.55; cursor: default; box-shadow: none; }
.dm-cta .dm-cta-ico {
  flex: none; width: 40px; height: 40px; border-radius: 50%;
  background: var(--navy); color: var(--gold); display: grid; place-items: center;
}
.dm-cta .dm-cta-ico svg.ic { width: 19px; height: 19px; }
.dm-cta .dm-cta-txt { min-width: 0; }
.dm-cta .dm-cta-txt b { display: block; font-size: 16.5px; font-weight: 800; }
.dm-cta .dm-cta-txt small { display: block; font-size: 13px; font-weight: 500; color: rgba(10, 29, 48, 0.74); margin-top: 1px; }
.dm-cta .dm-cta-go { margin-left: auto; flex: none; font-size: 24px; font-weight: 700; line-height: 1; }

#door-mode {
  position: fixed; inset: 0; z-index: 500;
  background: var(--paper); display: flex; flex-direction: column;
}
.dm-head {
  background: var(--navy); color: #fff; flex: none;
  padding: calc(10px + env(safe-area-inset-top)) 18px 12px;
  display: flex; align-items: center; gap: 14px;
}
.dm-head .dm-sub { font-size: 11px; color: #8FA5BC; letter-spacing: .08em; text-transform: uppercase; font-weight: 800; }
.dm-head .dm-title { font-family: var(--serif); font-size: 20px; line-height: 1.2; }
.dm-head .dm-exit {
  margin-left: auto; flex: none;
  font-family: var(--sans); font-size: 14px; font-weight: 700;
  color: #fff; background: rgba(255,255,255,.12);
  border: 1px solid rgba(255,255,255,.25); border-radius: 10px;
  padding: 10px 16px; cursor: pointer; min-height: 44px;
}
.dm-search {
  margin: 12px 16px 0; flex: none;
  display: flex; align-items: center; gap: 10px;
  background: var(--surface); border: 1.5px solid var(--hairline);
  border-radius: 12px; padding: 4px 6px 4px 14px;
}
.dm-search svg.ic { width: 17px; height: 17px; color: var(--muted); flex: none; }
.dm-search input {
  flex: 1; border: none !important; outline: none !important;
  background: none; font-size: 16px !important; min-height: 46px; padding: 0;
}
.dm-list { flex: 1; overflow-y: auto; padding: 10px 16px 8px; -webkit-overflow-scrolling: touch; }
.dm-row {
  display: flex; align-items: center; gap: 12px; width: 100%;
  background: var(--surface); border: 1.5px solid var(--hairline); border-radius: 13px;
  padding: 11px 14px; margin-bottom: 8px; cursor: pointer;
  font-family: var(--sans); text-align: left; min-height: 56px;
  -webkit-user-select: none; user-select: none; -webkit-touch-callout: none;
}
.dm-row .nm { font-size: 16px; font-weight: 600; color: var(--ink); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dm-row .mk {
  margin-left: auto; flex: none; width: 38px; height: 38px; border-radius: 50%;
  border: 1.5px solid var(--hairline); background: none;
  display: grid; place-items: center; font-size: 14px; font-weight: 800; color: #9AA5B1;
}
.dm-row.in { border-color: #BFE0CD; background: #F4FBF7; }
.dm-row.in .mk { background: var(--good); border-color: var(--good); color: #fff; }
.dm-row.mk-comp .mk { background: var(--gold); border-color: var(--gold); color: #3A2E05; }
.dm-row.mk-work .mk { background: var(--harbor); border-color: var(--harbor); color: #fff; font-size: 12px; }
.dm-bar {
  flex: none; display: flex; align-items: center; gap: 20px;
  background: var(--surface); border-top: 1px solid var(--hairline);
  padding: 12px 18px calc(14px + env(safe-area-inset-bottom));
}
.dm-bar .cl { display: block; font-size: 10px; letter-spacing: .1em; text-transform: uppercase; font-weight: 800; color: var(--muted); }
.dm-bar .cnt { display: block; font-family: var(--serif); font-size: 22px; color: var(--navy); line-height: 1.1; font-variant-numeric: tabular-nums; }
.dm-bar .dm-cashbtn { margin-left: auto; }

/* the cash box panel slides over the list, the tally stays */
.dm-cash { flex: 1; overflow-y: auto; padding: 14px 16px; }
.dm-cash .m-card, .dm-cash .dmc {
  background: var(--surface); border: 1px solid var(--hairline); border-radius: 13px;
  padding: 15px 17px; margin-bottom: 12px;
}
.dm-cash h5 { font-family: var(--sans); font-size: 13.5px; font-weight: 800; color: var(--navy); margin-bottom: 9px; }
.dm-cash .ln { display: flex; gap: 12px; font-size: 14px; color: var(--muted); padding: 3px 0; }
.dm-cash .ln b { margin-left: auto; color: var(--ink); font-variant-numeric: tabular-nums; }
.dm-cash .ln.tot { border-top: 1px solid var(--hairline); margin-top: 5px; padding-top: 8px; }
.dm-cash .ln.tot b { font-family: var(--serif); font-size: 18px; color: var(--navy); }
.dm-cash input.dm-counted {
  font-family: var(--serif); font-size: 24px !important; color: var(--navy);
  border: 1.5px solid var(--gold); border-radius: 11px; padding: 10px 14px; min-height: 54px;
}
.dm-back {
  font-family: var(--sans); font-size: 14px; font-weight: 700; color: var(--harbor);
  background: none; border: none; cursor: pointer; padding: 8px 0; min-height: 44px;
}

/* long-press action sheet */
.dm-sheet-scrim { position: fixed; inset: 0; z-index: 510; background: rgba(10,29,48,.45); }
.dm-sheet {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 511;
  background: var(--surface); border-radius: 18px 18px 0 0;
  padding: 14px 16px calc(16px + env(safe-area-inset-bottom));
  box-shadow: 0 -16px 44px rgba(10,29,48,.3);
}
.dm-sheet .who { font-family: var(--serif); font-size: 19px; color: var(--navy); margin: 4px 4px 12px; }
.dm-sheet button.opt {
  display: flex; gap: 12px; align-items: center; width: 100%;
  font-family: var(--sans); font-size: 15.5px; font-weight: 600; color: var(--ink);
  background: none; border: none; border-radius: 10px; padding: 13px 12px;
  cursor: pointer; min-height: 50px; text-align: left;
}
.dm-sheet button.opt:hover { background: var(--surface-2); }
.dm-sheet button.opt .sym {
  width: 32px; height: 32px; border-radius: 50%; flex: none;
  display: grid; place-items: center; font-size: 13px; font-weight: 800;
  border: 1.5px solid var(--hairline); color: var(--muted);
}
.dm-sheet .cancel { width: 100%; margin-top: 6px; }

/* -------- appearance: dark is a re-tune, not an inversion ------------------
   Navy stays the identity. Surfaces drop to deep harbor blues, hairlines
   lift, gold stays gold. Applied by class (the JS resolves "match device"),
   scoped to the portal. */
:root.theme-dark {
  --paper: #0B1622; --card: #132131; --surface: #132131; --surface-2: #0F1C29;
  --ink: #E7EDF4; --muted: #93A6B9; --line: #25384C; --hairline: #25384C;
  /* Navy on a navy surface is the one token the re-tune had missed, and it was
     missed everywhere at once: "Email address" and "Password" on the sign-in
     card, Title/Date/Time/Category/Location/Description on the event form,
     Subject/Message on the campaign composer, Full name/Email/Role on the
     invitation, and every capability checkbox on Board Access. All of them
     were drawn in #0F2A43 on a #132131 card — legible only if you knew what
     they said. Moved as a token so the labels that are deliberately quieter
     (.bk-field, .acct-add, the door pickers) keep their own colour: those
     rules are more specific and still win. */
  --label-ink: #E7EDF4;
  --harbor-tint: #1A2C40;
  --gold-tint: #33301A; --good-tint: #16301F; --danger-tint: #38201C;
  color-scheme: dark;
}
/* THE TEXTURE IS PAINTED BY `body`, SO IT IS TURNED OFF ON `body`.
   `background-image: none` on :root does not reach it — the sand stays,
   over the dark paper, and the theme reads as half-applied rather than as
   a rule in the wrong place. This is the one declaration in the dark block
   that may not move to the root with the others. */
:root.theme-dark body { background-image: none; background-color: var(--paper); }
:root.theme-dark h1, :root.theme-dark h2,
:root.theme-dark h3, :root.theme-dark h4 { color: #E7EDF4; }
:root.theme-dark .card,
:root.theme-dark .chart-card,
:root.theme-dark .stat-tile,
:root.theme-dark .camp-card,
:root.theme-dark .prompt-row,
:root.theme-dark .doc-row,
:root.theme-dark .minute-card,
:root.theme-dark .bk-toolbar,
:root.theme-dark .cat-item,
:root.theme-dark .bud-row,
:root.theme-dark .audit-bar,
:root.theme-dark .aud-item,
:root.theme-dark .logo-tile,
:root.theme-dark .fix-card,
:root.theme-dark .cmdk,
:root.theme-dark .ph-menu,
:root.theme-dark .glance .g,
:root.theme-dark .needs,
:root.theme-dark .ck-wrap { background: var(--surface); border-color: var(--hairline); }
:root.theme-dark .stat-tile .val,
:root.theme-dark .doc-row .t,
:root.theme-dark .minute-card summary,
:root.theme-dark .glance .g .v,
:root.theme-dark .need .nt .t,
:root.theme-dark .cmdk .row .t,
:root.theme-dark table.ck .bal,
:root.theme-dark .bk-cell .v.net { color: #E7EDF4; }
:root.theme-dark .need:hover { background: var(--surface-2); }
:root.theme-dark .needs .hd,
:root.theme-dark .need { border-color: var(--hairline); }
:root.theme-dark input:where(:not([type="checkbox"], [type="radio"], [type="range"], [type="hidden"])),
:root.theme-dark select,
:root.theme-dark textarea {
  background: var(--surface-2); color: var(--ink); border-color: var(--hairline);
}
/* The eye's hover/active colours are navy and harbor, both of which vanish
   against a dark surface. Lifted, not inverted, like everything else here. */
:root.theme-dark .pw-eye:hover,
:root.theme-dark .pw-eye:focus-visible { color: #E7EDF4; }
:root.theme-dark .pw-eye[aria-pressed="true"] { color: var(--gold); }
/* The capability chips on Board Access hard-code three light backgrounds, so
   the dark re-tune left them near-white while their text followed --muted down
   to #93A6B9 — 2.4:1, and on the *off* chip, which is the one that says this
   person may not do this. That is the panel the club reads to answer "who can
   move our money", so an unreadable "no" is the worst of the three to lose. */
/* Links were never re-tuned either: Harbor Blue is chosen to sit on white and
   manages 2.9:1 on a dark card. "Forgot your password?" — on the one screen a
   locked-out officer is looking at — was the worst of them. Lifted rather than
   recoloured, so a link still reads as the same blue.

   Scoped to page content on purpose. The site header and footer are navy in
   both themes and colour their own links; a blanket `a` rule repaints the
   active nav pill and turns the gold "Become a Member" button into pale blue
   on gold, which is 1.15:1 — worse than the thing being fixed. Links that are
   really buttons are excluded for the same reason. */
:root.theme-dark .section a:not(.btn),
:root.theme-dark .portal-shell a:not(.btn) { color: #8FB7DC; }
/* The intro disclosure is a <summary>, not an <a>, so the rule above cannot
   reach it — and Harbor Blue on the dark card is 2.92:1, the exact failure the
   `password` note records. Same colour the links were given. */
:root.theme-dark .maillog > summary::before { border-left-color: #8FB7DC; }
:root.theme-dark .howto > summary,
:root.theme-dark .howto-steps li::marker { color: #8FB7DC; }
/* `.linkish` reads as a link and is a <button>, so it is excluded from the
   rule above by the very clause that protects the gold CTA. Same 2.9:1, same
   lift: "Show all N members" on Attendance → Trends, and "Manage categories"
   on Society Documents. */
:root.theme-dark .linkish { color: #8FB7DC; }
:root.theme-dark .linkish:hover { color: #C7D9EC; }
/* And the rows the chips sit on, for the same reason: a literal #fff that the
   token remap could not reach, holding text that had followed --ink up to
   #E7EDF4 — the board member's own name, white on white. */
:root.theme-dark .acc-person { background: var(--surface); }
:root.theme-dark .acc-person.concentrated { background: #2A1E1C; }
:root.theme-dark .acc-person.admin { background: var(--surface-2); }
:root.theme-dark .acc-cap { background: var(--surface-2); color: var(--muted); }
:root.theme-dark .acc-cap.on { background: var(--harbor-tint); color: var(--ink); }
:root.theme-dark .acc-cap.on.sensitive { background: var(--gold-tint); color: #E9CE7A; }
:root.theme-dark .subnav { background: #0F1C29; }
:root.theme-dark .subnav button { color: var(--muted); }
:root.theme-dark .subnav button.active { background: #1E3145; color: #E7EDF4; }
:root.theme-dark .pane-head { background: rgba(11, 22, 34, 0.93); }
:root.theme-dark .acct-id b { color: #E7EDF4; }
:root.theme-dark .ph-search, :root.theme-dark .ph-more
  { background: var(--surface); border-color: var(--hairline); color: var(--muted); }
:root.theme-dark .ph-menu button { color: var(--ink); }
:root.theme-dark .ph-menu button:hover { background: var(--surface-2); }
:root.theme-dark .btn-ghost { color: #C7D3E0; border-color: #3A5068; }
:root.theme-dark .btn-ghost:hover { background: #1E3145; color: #fff; }
:root.theme-dark table.data th { color: var(--muted); border-color: var(--hairline); }
:root.theme-dark table.data td { border-color: var(--hairline); }
:root.theme-dark table.ck td.bal { background: var(--surface); }
:root.theme-dark table.ck tbody tr.row { border-color: var(--hairline); }
:root.theme-dark table.ck tbody tr.row:nth-child(4n-1),
:root.theme-dark table.ck tbody tr.row:nth-child(4n) { background: #16273A; }
:root.theme-dark table.ck tbody tr.row:nth-child(4n-1) td.bal,
:root.theme-dark table.ck tbody tr.row:nth-child(4n) td.bal { background: #16273A; }
:root.theme-dark table.ck tbody tr.row:hover,
:root.theme-dark table.ck tbody tr.row:hover td.bal { background: #1B3049; }
:root.theme-dark table.ck tbody tr.row.open,
:root.theme-dark table.ck tbody tr.row.open td.bal { background: #1E3550; }
:root.theme-dark tr.ck-new, :root.theme-dark tr.ck-new td.bal
  { background: #221E10 !important; }
:root.theme-dark td.ck-ed:hover .ck-cell { background: #1B3049; box-shadow: inset 0 0 0 1px #2E4763; }
:root.theme-dark td.ck-ed input, :root.theme-dark td.ck-ed select { background: var(--surface-2); }
:root.theme-dark tr.ck-detail > td { background: #10233A; }
:root.theme-dark .ck-foot { background: var(--surface-2); border-color: var(--hairline); }
:root.theme-dark .kbd { background: var(--surface-2); border-color: var(--hairline); color: var(--ink); }
:root.theme-dark .mail-fix h4,
:root.theme-dark .mail-row .t { color: #E7EDF4; }
:root.theme-dark .mail-fix p { color: var(--ink); }
:root.theme-dark .mail-fix,
:root.theme-dark .mail-fix p.todo { border-color: var(--hairline); }
:root.theme-dark .mail-fix p.todo { background: var(--gold-tint); }
:root.theme-dark .mail-fix p.todo b { color: #E5C766; }
:root.theme-dark .mail-fix.calm p.todo { background: var(--surface-2); }
:root.theme-dark .mail-fix details pre,
:root.theme-dark .mail-row details pre { background: var(--surface-2); }
:root.theme-dark .mem-tip { background: var(--gold-tint); border-color: #4A4020; color: #E5D9A8; }
:root.theme-dark .mem-edit { color: var(--ink); }
:root.theme-dark .mem-edit:hover { border-color: var(--hairline); background: var(--surface-2); }
:root.theme-dark .mem-edit:focus { background: var(--surface-2); }
:root.theme-dark .row-more { background: var(--surface); border-color: var(--hairline); color: var(--ink); }
:root.theme-dark .drawer { background: var(--paper); }
:root.theme-dark .drawer-filters, :root.theme-dark .drawer-foot { background: var(--surface); border-color: var(--hairline); }
:root.theme-dark .aud-day { background: var(--paper); }
:root.theme-dark .pill.ok { background: var(--good-tint); color: #7FD6A4; }
:root.theme-dark .pill.warn { background: var(--gold-tint); color: #E0C566; }
:root.theme-dark .pill.bad { background: var(--danger-tint); color: #F4A79B; }
:root.theme-dark .pill.info { background: var(--harbor-tint); color: #C7D3E0; }
:root.theme-dark .dm-row.in { background: #16301F; border-color: #2C5138; }
:root.theme-dark .cmdk .row.sel { background: #1E3145; }
:root.theme-dark .cmdk .row .ric { background: var(--surface-2); color: #C7D3E0; }
:root.theme-dark .fin-chip { background: var(--surface); border-color: var(--hairline); color: var(--ink); }
:root.theme-dark .fin-chip b { color: #E7EDF4; }
:root.theme-dark .hbar-row .nm { color: var(--ink); }
:root.theme-dark .hbar-row .track { background: var(--surface-2); }
:root.theme-dark .cat-row .track { background: var(--surface-2); }
:root.theme-dark .cat-row .ico { background: var(--harbor-tint); }
/* Dark is a re-step of the same status scale, not an automatic flip — each
   step re-chosen against the dark surface (#132131) and re-validated there. */
:root.theme-dark {
  --burn-ok: #2E9464; --burn-hot: #B08F1E; --burn-critical: #CC4436;
  --burn-none: #7C90A5; --burn-track: #0F1C29;
  --burn-over-zone: #2E1A18; --burn-surplus-zone: #16301F;
}
:root.theme-dark .burn-row .ico { background: var(--harbor-tint); }
/* The marks invert: light ink with a dark halo, so they still read on a fill
   and on bare track without the heavy white bar the light-mode halo would be. */
:root.theme-dark .burn-row .tick { background: #E7EDF4; }
:root.theme-dark .burn-row .budget-mark {
  background: repeating-linear-gradient(to bottom, #E7EDF4 0 4px, rgba(11, 22, 34, .85) 4px 7px);
}
:root.theme-dark .burn-row .tick,
:root.theme-dark .burn-row .budget-mark { box-shadow: 0 0 0 1.5px rgba(11, 22, 34, .85); }
:root.theme-dark .burn-legend i.k-tick { background: #E7EDF4; }
:root.theme-dark .burn-legend i.k-budget {
  background: repeating-linear-gradient(to bottom, #E7EDF4 0 4px, transparent 4px 7px);
}
:root.theme-dark .burn-verdict b { color: #E7EDF4; }
:root.theme-dark .burn-verdict[data-tone="ok"] .bv-ico { color: #7FD6A4; }
:root.theme-dark .burn-verdict[data-tone="hot"] .bv-ico { color: #E0C566; }
:root.theme-dark .burn-verdict[data-tone="critical"] .bv-ico { color: #F4A79B; }
:root.theme-dark .pace.ok { background: var(--good-tint); color: #7FD6A4; }
:root.theme-dark .pace.hot { background: var(--gold-tint); color: #E0C566; }
:root.theme-dark .pace.critical { background: var(--danger-tint); color: #F4A79B; }
:root.theme-dark .pace.none, :root.theme-dark .pace.flat { background: var(--surface-2); color: var(--muted); }
:root.theme-dark .burn-empty b { color: #E7EDF4; }
:root.theme-dark .burn-empty, :root.theme-dark .burn-verdict { border-color: var(--hairline); }
:root.theme-dark .burn-filter button { background: var(--surface); border-color: var(--hairline); }
:root.theme-dark .burn-filter button.on { background: var(--navy); border-color: var(--navy); color: var(--gold); }
:root.theme-dark .burn-row + .burn-row { border-top-color: var(--hairline); }
:root.theme-dark .bk-summary { background: var(--hairline); border-color: var(--hairline); }
:root.theme-dark .bk-cell { background: var(--surface); }
:root.theme-dark .bk-chip { background: var(--surface); border-color: var(--hairline); color: var(--ink); }
:root.theme-dark .bk-chip.on { background: var(--navy); color: var(--gold); border-color: var(--navy); }
:root.theme-dark .door-row { background: var(--surface); border-color: var(--hairline); }
:root.theme-dark .empty, :root.theme-dark .pill-note { color: var(--muted); }
:root.theme-dark .pill-note { background: var(--harbor-tint); }
:root.theme-dark .notice-err { background: #38201C; color: #F4A79B; border-color: #57302A; }
:root.theme-dark .notice-ok { background: #16301F; color: #7FD6A4; border-color: #2C5138; }
:root.theme-dark .notice-warn { background: #322A14; color: #E6C86A; border-color: #554925; }

/* -------- density: a scale, not a squeeze ----------------------------------
   One token moves and padding follows. Type does not shrink; touch targets
   never drop below 44px on touch layouts. */
body[data-page="board"].density-compact { --s4: 12px; --s5: 16px; }
body[data-page="board"].density-compact .stat-tile { padding: 13px 15px; }
body[data-page="board"].density-compact .stat-tile .val { font-size: 27px; }
body[data-page="board"].density-compact .chart-card { padding: 15px 17px; }
body[data-page="board"].density-compact table.data th { padding: 6px 10px; }
body[data-page="board"].density-compact table.data td { padding: 5px 10px; }
body[data-page="board"].density-compact table.ck td { height: 26px; }
body[data-page="board"].density-compact .ck-cell { min-height: 25px; padding: 3px 10px; }
body[data-page="board"].density-compact .doc-row { padding: 11px 16px; }
body[data-page="board"].density-compact .fh-tile { padding: 10px 13px 9px; }
body[data-page="board"].density-compact .fh-tile .v { font-size: 26px; }
body[data-page="board"].density-compact .fin-hero { padding: 18px 22px 16px; }
body[data-page="board"].density-compact .burn-row { padding: 4px 0; }
body[data-page="board"].density-compact .burn-row .track { height: 18px; }
body[data-page="board"].density-compact .burn-verdict { padding: 9px 12px; margin: 8px 0 10px; }
body[data-page="board"].density-compact .need { padding: 11px 16px; }
body[data-page="board"].density-compact .glance .g { padding: 10px 13px; }
body[data-page="board"].density-compact .glance .g .v { font-size: 22px; }

/* the preferences popover lives on the person, next to Sign Out */
.prefs-row { padding: 8px 12px 4px; }
.prefs-row .pl { font-size: 10.5px; letter-spacing: .12em; text-transform: uppercase; font-weight: 800; color: var(--muted); margin-bottom: 6px; }
.prefs-seg { display: inline-flex; gap: 3px; background: var(--harbor-tint); padding: 3px; border-radius: 9px; }
.prefs-seg button {
  font-family: var(--sans); font-size: 12.5px; font-weight: 700; color: var(--muted);
  background: none; border: none; border-radius: 7px; padding: 6px 12px; cursor: pointer;
}
.prefs-seg button.on { background: var(--surface); color: var(--navy); box-shadow: 0 1px 3px rgba(15,42,67,.18); }
:root.theme-dark .prefs-seg button.on { background: #1E3145; color: #E7EDF4; }

/* dark-mode gaps caught in review: account chips and the Door Mode tally */
:root.theme-dark .ck-acct { background: var(--surface); border-color: var(--hairline); }
:root.theme-dark .ck-acct.on { background: #1E3145; border-color: var(--harbor); }
:root.theme-dark .ck-acct .bl { color: #E7EDF4; }
:root.theme-dark .dm-bar .cnt { color: #E7EDF4; }
:root.theme-dark .dm-head { background: #0F2438; }
:root.theme-dark .dm-search input { color: var(--ink); }
:root.theme-dark .dm-cash .ln.tot b { color: #E7EDF4; }
:root.theme-dark .dm-sheet .who { color: #E7EDF4; }
:root.theme-dark .stat-tile .lbl, :root.theme-dark .stat-tile .sub { color: var(--muted); }

/* ---- the sign-in link a board can hand over by hand --------------------
   Shown after an invitation and on demand for anyone still pending. It looks
   like a thing to be copied rather than a status message: the URL in a real
   input, selectable, monospaced, next to one button. */
.invite-link {
  margin-top: 18px; padding: 16px 18px;
  background: var(--gold-tint, #F4F1E4);
  border: 1px solid var(--gold-lift, #A99560);
  border-radius: var(--radius, 12px);
}
.invite-link .il-head {
  font-size: 12.5px; font-weight: 800; letter-spacing: .12em;
  text-transform: uppercase; color: var(--gold-deep); margin-bottom: 6px;
}
.invite-link .il-note { font-size: 14.5px; color: var(--muted); margin-bottom: 12px; }
.invite-link .il-row { display: flex; gap: 8px; align-items: center; }
.invite-link input {
  flex: 1; min-width: 0; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px; padding: 10px 12px; border: 1px solid var(--line);
  border-radius: 8px; background: #fff; color: var(--ink);
}
.invite-link button { flex: none; white-space: nowrap; }
@media (max-width: 620px) {
  .invite-link .il-row { flex-wrap: wrap; }
  .invite-link button { width: 100%; }
}

/* the two actions on a pending person's row */
.acc-person .act { display: flex; gap: 8px; flex-wrap: wrap; align-items: flex-start; }

/* ========== THE More SHEET, AND THE TILE THAT OPENS IT =====================
   Above the phone breakpoint the rail is a vertical column with room for
   everything, so More is redundant and never drawn. */
@media (min-width: 1025px) { #rail-more { display: none; } }

/* A bottom sheet rather than a centred card: it opens from the edge the thumb
   is already at, and the rows land under it rather than at the top of a phone.
   Built by BASS.overlay(), so the ×, the backdrop, Escape and Back are all
   already handled — this only says where it sits and what it looks like. */
.lightbox.more-sheet { align-items: flex-end; justify-content: center; padding: 0; }
.more-panel {
  width: 100%; max-width: 560px; background: var(--surface);
  border-radius: 18px 18px 0 0; padding: 20px 16px calc(18px + env(safe-area-inset-bottom));
  max-height: 82vh; overflow-y: auto;
  box-shadow: 0 -10px 40px rgba(0,0,0,.28);
}
.more-panel h2 {
  font-size: 13px; font-weight: 800; letter-spacing: .1em; text-transform: uppercase;
  color: var(--muted); margin: 0 4px 12px;
}
.more-rows { display: grid; gap: 4px; }
/* A row is a full-width target, not a link in a list: the bar it replaces had
   52px tiles and this must not be harder to hit than what it stands in for. */
.more-panel .more-row {
  display: flex; align-items: center; gap: 13px; width: 100%;
  min-height: 52px; padding: 10px 12px; border-radius: 12px;
  border: 1px solid transparent; background: none; cursor: pointer;
  font-family: var(--sans); font-size: 16px; font-weight: 600;
  color: var(--navy); text-align: left;
}
.more-panel .more-row:hover, .more-panel .more-row:focus-visible { background: var(--surface-2); }
.more-panel .more-row:focus-visible { outline: 2px solid var(--harbor); outline-offset: 1px; }
.more-panel .more-row .nm { flex: 1 1 auto; min-width: 0; }
.more-panel .more-row svg.ic { width: 20px; height: 20px; flex: 0 0 auto; color: var(--muted); }
.more-panel .more-row .go { width: 16px; height: 16px; opacity: .45; }
.more-panel .more-row .badge {
  flex: 0 0 auto; background: var(--gold); color: var(--gold-deep);
  font-size: 12px; font-weight: 800; border-radius: 999px; padding: 1px 8px; min-width: 20px;
  text-align: center;
}
/* The account controls, moved in whole. They are a different KIND of thing
   from a destination — a setting and a way out, not a section — so they sit
   below a rule rather than in the same list. */
.more-account { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--hairline); }
/* Specificity: the rule that hides this from the BAR is
   `body[data-page="board"] .rail-account-wrap` (0,2,1). A bare
   `.more-account .rail-account-wrap` (0,2,0) loses to it and the account
   section renders as an empty divider. Matched here rather than marked
   !important, so the next reader can still see which rule wins and why. */
body[data-page="board"] .more-account .rail-account-wrap { display: block; }
/* `flex-direction` and the centring are inherited from the BAR's own tile
   rules, which stack an icon over a 9.5px label — right for a 52px tile, wrong
   for a row in a list. Named explicitly so this reads like the destinations
   above it rather than like a tile that wandered in. */
.more-account #prefs-btn {
  display: flex; flex-direction: row; align-items: center; justify-content: flex-start;
  gap: 13px; width: 100%; text-align: left;
  min-height: 52px; padding: 10px 12px; border-radius: 12px;
  border: 1px solid transparent; background: none; cursor: pointer;
  font-family: var(--sans); font-size: 16px; font-weight: 600; color: var(--navy);
  letter-spacing: 0; line-height: 1.2;
}
.more-account #prefs-btn:hover, .more-account #prefs-btn:focus-visible { background: var(--surface-2); }
.more-account #prefs-btn svg.ic { width: 20px; height: 20px; color: var(--muted); }
/* The popover opens in place inside the sheet, where there is room for it,
   instead of upward out of a 52px tile at the bottom of a phone. */
.more-account .ph-menu {
  position: static; display: block; min-width: 0; width: 100%;
  box-shadow: none; border: 1px solid var(--hairline); margin-top: 6px;
}

/* The More tile itself wears the same clothes as every other tile in the bar;
   only its badge differs, because it is a SUM of what it hides. */
#rail-more .badge { background: var(--gold); color: #3A2E05; }

/* ---- the appearance control, in the site footer --------------------------
   The footer is the club's navy in BOTH themes, so this cannot inherit the
   portal popover's pale `--harbor-tint` pill — that is a token the dark block
   remaps, and a control that reads correctly in one theme and washes out in
   the other is the fault this whole change is about. Mixed from #fff against
   the navy the footer actually paints, so it is the same control on both.

   Gold marks the setting in force: on this navy it measures 6.06:1, which
   carries a 12px label on its own. */
.site-footer .theme-pick { display: inline-flex; align-items: center; gap: 9px; }
.site-footer .theme-pick .tl {
  font-size: 11px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase;
  color: rgba(255, 255, 255, .62);
}
.site-footer .theme-pick .prefs-seg {
  background: rgba(255, 255, 255, .10); padding: 3px; border-radius: 9px;
}
.site-footer .theme-pick .prefs-seg button {
  background: none; border: 0; cursor: pointer; border-radius: 7px;
  padding: 4px 10px; font-family: var(--sans); font-size: 12.5px; font-weight: 600;
  color: rgba(255, 255, 255, .78); line-height: 1.3;
}
.site-footer .theme-pick .prefs-seg button:hover { color: #fff; }
.site-footer .theme-pick .prefs-seg button:focus-visible {
  outline: 2px solid var(--gold); outline-offset: 1px;
}
.site-footer .theme-pick .prefs-seg button.on {
  background: rgba(255, 255, 255, .16); color: var(--gold); box-shadow: none;
}
/* At phone width the legal strip is already a column; the control keeps its
   own row rather than being squeezed beside the copyright line. */
@media (max-width: 760px) {
  .site-footer .theme-pick { order: -1; }
}

/* ============ WHAT IS BEHIND A FIGURE — the drill-down panel ===============
   A budget row is a BUTTON now, so it needs a button's defaults undone: the
   grid layout, the alignment and the inherited type all have to survive the
   change of element, or the whole list re-flows into centred 13px system
   text. `width: 100%` because a button shrink-wraps its content and the bar
   would stop spanning the row. */
button.burn-row {
  width: 100%; text-align: left; font: inherit; color: inherit;
  background: none; border: 0; cursor: pointer; border-radius: 8px;
}
button.burn-row:hover { background: var(--surface-2); }
button.burn-row:focus-visible { outline: 2px solid var(--harbor); outline-offset: -2px; }
/* A line with no id cannot be opened, so it must not look pressable. */
button.burn-row:not([data-drill]) { cursor: default; }
button.burn-row:not([data-drill]):hover { background: none; }

/* The two share-of-total cards on the dashboard open the same way. Their rows
   are separated by a margin rather than by a rule, so the hover surface has to
   be made rather than inherited: it takes the row's own 13px of rhythm as
   padding and gives it back as margin, which leaves the list spaced exactly as
   it was drawn before it could be pressed. The negative side margin lets the
   highlight reach into the card's padding without shortening a single bar —
   the bars are the measurement on this card, and a track 16px shorter than the
   one above it in the neighbouring card is a chart that disagrees with itself. */
button.cat-row {
  position: relative;
  width: calc(100% + 24px); text-align: left; font: inherit; color: inherit;
  background: none; border: 0; cursor: pointer; border-radius: 9px;
  padding: 4px 18px 4px 12px; margin: 0 -12px 5px;
}
button.cat-row:hover { background: var(--surface-2); }
button.cat-row:focus-visible { outline: 2px solid var(--harbor); outline-offset: -2px; }
button.cat-row:not([data-drill]) { cursor: default; }
button.cat-row:not([data-drill]):hover { background: none; }

/* THE ONLY THING ON THE ROW THAT SAYS IT OPENS. Hover is not an answer: half
   this board reads these cards on a phone, where there is no hover at all, and
   a control whose only affordance is a pointer state is a control most of them
   never find.

   It is POSITIONED, not a fifth column, and that is the whole reason it fits.
   These cards sit three-up on a desktop and the bar is what is left after a
   fixed name and a fixed figure — a column and its 12px gutter would come
   straight off the one part of the row that is a measurement. This hangs in
   the button's own padding, which is card whitespace the row was not using;
   the six pixels the gap costs are all it takes from the track. The button
   stops 12px short of the card's inner edge, which is clear even at compact
   density, where the card's padding drops to 17px.

   Rotation rather than a second glyph: the same mark turning over reads as
   the same control changing state, where a swapped icon reads as a new one.
   `margin-top` does the centring so `transform` is free to be the state. */
.cat-row .caret {
  position: absolute; right: 2px; top: 50%; margin-top: -6px;
  width: 12px; height: 12px; color: var(--muted);
  /* A 24-unit glyph drawn at 12px halves the sprite's 1.6 stroke to a hairline;
     this is the same weight on screen as every other icon in the portal. */
  stroke-width: 2.6;
  transition: transform .18s ease, color .18s ease;
}
button.cat-row:hover .caret { color: var(--ink); }
button.cat-row[aria-expanded="true"] .caret { transform: rotate(180deg); color: var(--ink); }
/* "or not": a line with no category id cannot be opened, and must not offer. */
button.cat-row:not([data-drill]) .caret { display: none; }
/* Below 1025 the row is three stacked lines — name, bar, figure — so a caret
   centred on the row floats beside the bar in the middle of nothing. It pins
   to the first line, level with the name it belongs to. */
@media (max-width: 1024px) { .cat-row .caret { top: 14px; margin-top: 0; } }
/* The panel opens between two rows that were never separated by a rule, so it
   carries its own breathing room on the side the margin above it lost. */
.fin-drill + button.cat-row { margin-top: 2px; }

/* It expands WHERE IT WAS PRESSED and is not a layer: the reason for opening a
   line is nearly always to compare it with the one above it, and a scrim over
   that comparison answers a question nobody asked. */
.fin-drill {
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 10px;
  padding: 4px 12px 10px; margin: 2px 0 12px;
}
/* The separator the panel interrupted. `.burn-row + .burn-row` no longer
   matches once something is between them, and the list would lose a rule at
   whichever line happened to be open. */
.fin-drill + .burn-row { border-top: 1px solid var(--line); }

.fd-scroll { overflow-x: auto; }
.fd-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.fd-table th {
  text-align: left; font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase;
  color: var(--muted); font-weight: 800; padding: 8px 8px 5px;
  border-bottom: 1px solid var(--line);
}
.fd-table td { padding: 7px 8px; border-bottom: 1px solid var(--line); vertical-align: top; color: var(--ink); }
.fd-table tbody tr:last-child td { border-bottom: 0; }
.fd-dt { white-space: nowrap; color: var(--muted); font-variant-numeric: tabular-nums; }
.fd-desc { overflow-wrap: anywhere; }
.fd-acct { display: block; color: var(--muted); font-size: 12px; margin-top: 1px; }
.fd-tag {
  display: inline-block; background: var(--gold-tint); color: var(--gold-deep);
  font-size: 10.5px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase;
  border-radius: 4px; padding: 0 5px;
}
:root.theme-dark .fd-tag { color: var(--gold); }
.fd-n, th.fd-n { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.fd-foot {
  display: flex; gap: 10px 14px; align-items: center; flex-wrap: wrap;
  padding-top: 9px; margin-top: 4px; border-top: 2px solid var(--line);
  font-size: 13px; color: var(--muted);
}
.fd-foot .fd-sum { flex: 1 1 220px; }
.fd-foot .fd-sum b { color: var(--ink); font-variant-numeric: tabular-nums; }
.fd-foot .fd-sum em { font-style: normal; }
.fd-msg { padding: 12px 4px; font-size: 13.5px; color: var(--muted); }
.fd-msg.fd-err { color: var(--danger); font-weight: 600; }

/* At 390 the account line and the date already stack; what has to give is the
   padding, or three columns of tabular figures wrap mid-number. */
@media (max-width: 560px) {
  .fd-table { font-size: 12.5px; }
  .fd-table th, .fd-table td { padding: 6px 4px; }
  .fin-drill { padding: 4px 9px 9px; }
}

/* ------------------------ the portal on a phone, without the shop front --
   A signed-in board member on a 390×844 phone was giving 453 pixels to
   chrome before any work appeared: 162 to the public site's header, 226 to
   the pane masthead, 65 to the bottom bar. Fifty-four per cent of the screen,
   leaving 391 for the register, the roster or the door sheet.

   The header is the public site's shop front — Home, Events, Gallery, Board
   Portal, Facebook, Become a Member — and a volunteer taking money at a door
   does not need to be sold a membership. `sidemenu` decided to leave it alone
   because re-cutting it "would make the portal a different website", and that
   reasoning holds on a desktop, where it costs a strip. On a phone it costs a
   fifth of the working area.

   So: only when signed in (body.portal-open), only on a phone, and never on
   the public pages, the bar keeps the club's mark and name and drops the
   navigation — which the bottom tab bar already provides, better. */
@media (max-width: 1024px) {
  body.portal-open .site-header .main-nav { display: none; }
  body.portal-open .site-header .bar { padding-top: 10px; padding-bottom: 10px; }
  /* The location line is the public site introducing the club to a stranger.
     Somebody signed in knows which club they are in. */
  body.portal-open .site-header .brand .name small { display: none; }
  body.portal-open .site-header .brand .name { font-size: 17px; line-height: 1.2; }
  body.portal-open .site-header .brand .mark { width: 34px; height: 34px; }

  /* The masthead repeats the club's name as a breadcrumb over a heading that
     says the same thing — the audit's finding, and 30 of those 226 pixels. */
  body.portal-open .pane-head .crumb { display: none; }
  body.portal-open .pane-head p { font-size: 15px; }
}

/* ------------------------------------------- one tile per row on a small phone
   These grids all step 4 → 2 and then stop, so on a 320px phone a tile is 133px
   wide with 89px inside it — and the figure in it is 33px serif, which measures
   99px for a four-figure sum. The number ran past its own white card.

   A regression of the `floating` work rather than an old fault: before
   `min-width: 0` a tile could not shrink below its contents, so it pushed the
   grid wider instead — which is what floated the bottom bar. Letting the tile
   shrink was right; letting the figure spill was not.

   ONE PER ROW BELOW 360px. 375 and up is untouched, deliberately: there the
   same figures measure -1px against their box, so the common phone keeps two
   tiles across and keeps the figure at 33px, which is the size this audience
   needs.

   AT THE END OF THE FILE, AND !important, because the `max-width: 1024px`
   block above re-sets three of these five — .bk-summary with !important of its
   own — and between two rules of equal specificity the later one wins whatever
   the media query says. Placed here it cannot be quietly overridden by a rule
   added below it. */
@media (max-width: 359px) {
  .stat-row, .glance, .camp-agg, .fh-grid, .bk-summary {
    grid-template-columns: 1fr !important;
  }
}

/* ---------------------------------------- the one row that leaves the portal
   Every other row in the More sheet goes to a pane inside it. This one opens
   the club's own website, so it is separated by a rule and carries the same
   arrow as the rest rather than a different affordance — a board member should
   not have to learn a second convention to find the front door. */
.more-out {
  margin-top: 10px; padding-top: 10px;
  border-top: 1px solid var(--line);
}
.more-leave { text-decoration: none; }
.more-leave .nm { color: var(--ink); }

/* -------------------------------------------- Board Access, hittable and read
   Measured, not guessed: a 43×19 correction link, a 27px role menu, four
   13×13 checkboxes, and buttons at 36 and 40. Everything you press on the
   screen that decides who can move the club's money now clears 44px. */
#tab-access button,
#tab-access select,
#tab-access input[type="email"],
#tab-access input[type="text"] { min-height: 44px; }
#tab-access .acc-person .act button { min-height: 44px; min-width: 44px; padding-inline: 14px; }

/* The two links written into sentences here were 4.48:1 — a hair under the
   floor, and the same pair the `password` work measured and left. They are
   part of a paragraph, so they are not 44px targets and are not made to look
   like buttons; they are simply readable now. */
#tab-access a:not(.btn) { color: #2C5578; text-decoration-thickness: 1px; }
/* Dark keeps the colour the `password` work chose for every other link on a
   dark card; a hex picked for white paper is 2.9:1 on navy. */
:root.theme-dark #tab-access a:not(.btn) { color: #8FB7DC; }

/* ------------------------------------------- the setup list a new club sees
   Drawn like the watch list it sits above, because a board member should not
   have to learn a second kind of row — with a number in front, because this
   one is a procedure and the other is a state of affairs. It exists only
   while there is something left to do and then never appears again. */
.vsetup .wlab { color: var(--gold-deep); }
.setup-row .sn {
  flex: 0 0 auto; width: 26px; height: 26px; border-radius: 50%;
  display: inline-grid; place-items: center; margin-right: 2px;
  background: var(--gold); color: var(--navy);
  font-weight: 700; font-size: 14px; font-variant-numeric: tabular-nums;
}

/* The breadcrumb is hidden on views that have no second rung — see
   paintCrumb(). It has a display of its own, so [hidden] alone would not
   stop it drawing. */
.pane-head .crumb[hidden] { display: none; }

/* -------------------------------------- the footer, for somebody signed in
   Measured on the club's own installation, signed in on Today: the public
   footer is 1,045px under 515px of work on a 390px phone, and 966 under 476 on
   an iPad — more than half the page, on every screen, in both cases. Its 113
   words are the mission statement, an Explore column, the meeting times and a
   Facebook button: the club introducing itself to a stranger, printed under
   the treasurer's books. Inside the portal it is not drawn.

   IT IS NOT REPLACED WITH A SHORTER ONE. A row of links was tried on
   13 August and taken out the same day, on the board's own reading of it: on a
   desktop it is a navy band across the foot of the window carrying one thing a
   board member might want, and that thing — "Visit the club's website" — is
   already in the More sheet, which is where somebody goes looking for it. A
   second copy of a menu item, given a band of its own, is repetition wearing
   the costume of navigation.

   So the portal has no footer, and nothing is lost: Appearance is in the
   account menu at the foot of the rail, and the public site is one tap away
   through More. If a footer is ever proposed here again it needs a job the
   rail does not already do.

   The public site is untouched: this is scoped to body.portal-open, exactly as
   the header collapse is. */
body.portal-open #site-footer .cols,
body.portal-open #site-footer .legal { display: none; }

/* WITH NO FOOTER, THE PAGE HAS TO REACH THE BOTTOM OF THE WINDOW ITSELF.
   The sand wash is a linear-gradient sized `auto`, so its tile is the
   background positioning area — and a background repeats. On a portal screen
   short enough to fit (Today on a desktop is 781px in a 900px window) the
   gradient ran out at the foot of the body box and STARTED AGAIN at its
   lightest stop, putting a damp-to-dry step across the last 35px of the
   window. Exactly the seam the texture's own comment warns about, which the
   footer had been standing over rather than fixing.

   min-height is the whole answer: the body reaches the bottom, the gradient's
   one tile covers the window, and there is nothing beyond it to repeat into.
   Not `height` — a pane taller than the window must still be free to grow. */
body.portal-open { min-height: 100vh; }

/* ------------------------------- a row with two things you can do to it
   The birthday list lives in a column about 340px wide, and a second button
   beside the first left the member's own name a zero-width column and turned
   the row into a 355px tower — the name broken one word per line.

   The buttons drop onto their own line when the row is too narrow to hold
   them beside the name, which is the ordinary answer and keeps both readable.
   The name is what the row is FOR, so it is the part that keeps its width. */
.doc-row { flex-wrap: wrap; }
.doc-row > div[style*="min-width"] { flex: 1 1 55%; min-width: 11rem; }
.doc-row .actions { flex: 0 1 auto; margin-left: auto; display: flex; gap: 8px; flex-wrap: wrap; }


/* ═══════════════════════════════════════════════ THE SHOP (public/shop.html)
   Two small components and nothing else — the shelf, the product page and the
   basket are otherwise built from `.card`, `.btn` and the page's own type, so
   the shop reads as part of the club's site rather than as a bolted-on store. */

/* An option pill. A radio the size of a fingertip, because the person choosing
   a size is on a phone — see the `plainly` note about who this is built for. */
.shop-opt { display: inline-flex; align-items: center; gap: 7px; cursor: pointer;
  border: 1px solid var(--hairline); border-radius: 6px; padding: 9px 13px;
  background: var(--surface); font-size: 15px; line-height: 1; }
.shop-opt input { margin: 0; }
.shop-opt:has(input:checked) { border-color: var(--navy); box-shadow: inset 0 0 0 1px var(--navy); font-weight: 600; }
.shop-opt:focus-within { outline: 2px solid var(--gold); outline-offset: 2px; }
/* SOLD OUT IS SHOWN, NEVER HIDDEN. A size that has quietly vanished from the
   list reads as one the club never offered; struck through, it reads as one
   that will come back. Not colour alone — the words "sold out" are in the
   label, and the control is genuinely disabled. */
.shop-opt.gone { opacity: .5; cursor: not-allowed; text-decoration: line-through; }
.shop-opt.gone img { filter: grayscale(1); }

/* The basket link, on every shop screen. Right-aligned and quiet until there
   is something in it, then it carries the count — which is the only thing on
   these pages a buyer needs to be able to find without being told. */
.shop-bar { display: flex; justify-content: flex-end; margin: 0 0 10px; }
.shop-bar a {
  display: inline-flex; align-items: center; gap: 6px; font-weight: 600;
  font-size: 15.5px; padding: 9px 15px; border-radius: 999px;
  border: 1px solid var(--hairline); background: var(--surface); text-decoration: none;
}
.shop-bar a:hover { border-color: var(--navy); }
.shop-bar a:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

.shop-total { display: flex; justify-content: space-between; gap: 16px;
  padding: 4px 0; font-variant-numeric: tabular-nums; }

/* The product page is one column on a phone and two where there is room. It is
   NOT a grid at the top level: a picture that has to sit beside its own
   controls on a 375px screen is how a shop stops being usable. */
@media (min-width: 760px) {
  .shop-detail { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) !important; align-items: start; }
}

/* The order tracker on /order.html and /shop-thanks.html. Four stages on one
   line, and it must survive a 320px phone — which is where somebody standing
   in a kitchen actually reads it. */
.ord-track { display: flex; margin: 20px 0 4px; }
.ord-step { flex: 1; text-align: center; position: relative; padding-top: 22px;
  font-size: 12.5px; line-height: 1.3; color: var(--muted); min-width: 0; }
.ord-step::before { content: ""; position: absolute; top: 2px; left: 50%; margin-left: -6px;
  width: 12px; height: 12px; border-radius: 50%; background: var(--hairline); z-index: 1; }
.ord-step::after { content: ""; position: absolute; top: 7px; left: 50%; width: 100%; height: 2px;
  background: var(--hairline); }
.ord-step:last-child::after { display: none; }
.ord-step.done { color: var(--ink); font-weight: 600; }
.ord-step.done::before, .ord-step.done::after { background: var(--good); }
.ord-step.now::before { background: var(--gold); outline: 3px solid var(--gold); outline-offset: 2px; }
