/* ---- Design tokens ----
   Palette inspired by a hangar at dusk + safety-orange aircraft trim:
   --bg:        #1B2430  deep slate navy
   --surface:   #232E3D  panel surface
   --surface-2: #283443  raised panel
   --border:    #33404F
   --text:      #EDEFF2
   --muted:     #8FA0B3
   --accent:    #EC7B4E  safety orange (throttle/warning trim)
   --good:      #4FA97C  active / positive
   --bad:       #D9534F  pending / rejected
   Type: "Barlow Condensed" (display, gauge-like numerals) + "Inter" (body) + "JetBrains Mono" (data/tables)
*/

@import url('https://fonts.googleapis.com/css2?family=Barlow+Condensed:wght@600;700&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;600&display=swap');

* { box-sizing: border-box; }

/* Turn off the mobile browsers' automatic text inflation.
   Left alone, iOS Safari and Chrome on Android grow the type in blocks they
   guess are body copy, by a factor derived from the block's width against the
   viewport's. The factor therefore differs per block — so turning a phone to
   landscape didn't scale the page, it scaled SOME of it, and the type scale
   below stopped holding: a 14px paragraph in a wide container came out bigger
   than a 16px heading in a narrow one.
   100%, not `none`: `none` also stops the reader pinch-zooming text on some iOS
   versions, which trades a cosmetic bug for an accessibility one. 100% keeps
   manual zoom and the reader's own text-size preference working, and only
   declines the browser's uninvited guess. */
html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

/* The whole admin site is dark. Telling the browser so makes it draw its
   native widgets — date fields, select menus, scrollbars — for a dark
   background. Without it Safari paints a date field's value in its
   light-scheme grey on our dark surface, unreadable next to the text fields
   beside it. */
:root {
  color-scheme: dark;
  /* Type scale.
     The app had twenty-one distinct font sizes in this file and eleven more in
     inline styles, with 11.5 / 12 / 12.5 / 13 / 13.5 all doing the same job
     within two pixels of each other. Half-pixel steps don't read as hierarchy,
     they read as carelessness. Five steps, each far enough from the next to be
     a deliberate choice:
       micro  badges, chips, uppercase eyebrows
       meta   dates, counts, supporting lines under a heading
       body   reading text inside a panel
       title  secondary panel headings
       panel  panel headings
     Where two levels sit close together, colour does the separating (muted vs
     text), not another half pixel. */
  --fs-micro: 11px;
  --fs-meta: 12.5px;
  --fs-body: 14px;
  --fs-title: 16px;
  --fs-panel: 18px;
  --fs-head: 21px;    /* section headings above a panel title */
  --fs-display: 26px; /* stat numbers, hero figures, the one-per-screen big thing */

  --bg: #1B2430;
  --surface: #232E3D;
  /* Nudged down from #2C3949. The raised panel was the lightest thing any text
     had to sit on, so it alone set how pale the status colours had to be. Half a
     step darker buys back real saturation everywhere and still reads as raised. */
  --surface-2: #283443;
  --border: #33404F;
  --text: #EDEFF2;
  /* Was #8FA0B3, which measured 4.39:1 on --surface-2 — just under AA, on the
     supporting text an older reader most needs. */
  --muted: #92A3B5;
  /* The fill and the label are the same orange again. They were split when the
     text had to be paled to clear a lighter panel; darkening --surface-2 closed
     that gap, so the button, the border and the link are one colour once more.
     Contrast on the fill went UP doing it — a dark label reads 6.55:1 here
     against 5.42:1 on the old, more saturated orange. */
  --accent: #EC7B4E;
  /* --accent paints; --accent-text is read.
     Safety orange is a fine FILL (dark text on it clears 5:1), but as a text
     colour it measured 4.06:1 on --surface and 3.47:1 on --surface-2 — both
     under AA, across 40 rules covering links, active tabs and headings. Worse
     on the accent-tinted chips, where the tint lifts the background toward the
     text: 2.83:1 at the darkest.
     Two values, because one could not serve both. A colour tint LIGHTENS the
     panel under it, dragging the background toward the text, so a chip needs a
     paler label than flat panel does. Forcing a single token to clear the
     chips washed out every link and heading on the site to pay for 22 badges.
     So: --accent-text for type on a plain surface, --accent-text-on-tint for
     type sitting on a wash of its own colour. Pick by what is behind it. */
  --accent-text: #EC7B4E;
  --accent-text-on-tint: #F09774;
  --good: #4FA97C;
  --bad: #D9534F;
  /* Same split as --accent / --accent-text, and for the same reason: these read
     fine as fills but not as type. --good measured 4.08:1 on --surface-2 and
     --bad 2.96:1 — and --bad carries error messages, which is the last text
     that should be hard to read. Both values below clear 4.5:1 on every
     surface, including their own status tints. */
  --good-text: #50AC7E;
  --good-text-on-tint: #7FC3A1;
  --bad-text: #E37E7B;
  --bad-text-on-tint: #E79491;
}

/* Use these instead of an inline font-size. Colour rides along on .t-meta
   because "smaller and quieter" is one decision, not two. */
.t-micro { font-size: var(--fs-micro); }
.t-meta { font-size: var(--fs-meta); color: var(--muted); }
.t-body { font-size: var(--fs-body); }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: 'Inter', system-ui, sans-serif;
  -webkit-font-smoothing: antialiased;
  /* Column flex so the shared site footer sits at the viewport bottom even
     when the view above it (e.g. the login card) is short. */
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

h1, h2, h3, .display {
  font-family: 'Barlow Condensed', sans-serif;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  margin: 0;
}

.mono { font-family: 'JetBrains Mono', monospace; }

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

/* --- App shell ---
   Shared header for both the login screen and the logged-in app. These
   values are deliberately copied 1:1 from the public homepage's nav.top /
   .brandmark (public/site.css) rather than approximated, so the bar itself
   doesn't shift position, color, or size when a member crosses from the
   public site into the app — same background rgba, same blur, same border
   color, same max-width wrap, same logo size. */
/* Brand row + tabs row stick together as one block, so the menu options stay
   fixed at the top while only the page content scrolls beneath. */
.app-header {
  position: sticky;
  top: 0;
  z-index: 30;
}
.topbar {
  background: rgba(27, 36, 48, 0.9);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid #364256;
}
.topbar-inner {
  max-width: 1180px;
  margin: 0 auto;
  padding: 12px clamp(20px, 5vw, 56px);
  min-height: 86px;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* Stay one row while it fits; when brand + actions no longer fit, the actions
     block wraps onto its own row instead of overflowing the viewport. */
  flex-wrap: wrap;
  gap: 10px 16px;
}
.topbar .brand { display: flex; align-items: center; gap: 16px; text-decoration: none; }
.topbar .brand-logo { height: 60px; width: auto; border-radius: 8px; flex-shrink: 0; }
/* The logo already carries the club name; the line beside it is the section
   label (or, once signed in, who you're signed in as), set off by a divider.
   Matches the public nav's brand treatment. */
.topbar .brand-text {
  display: flex; flex-direction: column; line-height: 1.3;
  padding-left: 16px; border-left: 1px solid #364256;
}
.topbar .brand-text span {
  font-family: 'JetBrains Mono', monospace; font-size: var(--fs-micro); color: var(--muted);
  letter-spacing: 0.08em; text-transform: uppercase;
}
/* Email (primary) reads brighter than the role line beneath it, with a little
   gap so the two lines aren't cramped together. */
#whoami { color: var(--text); }
.topbar .brand-text .whoami-role { display: inline-block; margin-top: 3px; color: var(--muted); }
.topbar-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }

/* Same ghost-button treatment as the public nav's .btn-ghost, applied to
   both the <a> (Public Site) and <button> (Account/Log out) controls in the
   app header, so all three read as one family instead of mixing the nav's
   mono/ghost style with the app's default Barlow Condensed buttons. */
.btn-public {
  display: inline-flex; align-items: center; justify-content: center;
  font-family: 'JetBrains Mono', monospace; font-size: var(--fs-meta); font-weight: 600;
  letter-spacing: 0.06em; text-transform: uppercase; color: var(--text);
  background: transparent; border: 1px solid #364256; border-radius: 2px; padding: 10px 20px;
  text-decoration: none; white-space: nowrap; cursor: pointer;
}
/* Keep the ghost look on hover. Account/Log out are <button>s, so without
   pinning the background here the global `button:hover` (solid orange) bleeds
   through and leaves orange text on an orange fill — unreadable. */
.btn-public:hover { background: transparent; border-color: var(--accent); color: var(--accent-text); filter: none; }

/* Once the actions wrap below the brand there's room to keep the signed-in
   identity visible, but a long email must truncate rather than widen the row. */
.topbar .brand { min-width: 0; }
.topbar .brand-text { min-width: 0; }
.topbar .brand-text span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 62vw; }

/* Desktop keeps all the actions visible — there's room, and hiding a control
   behind a menu where it already fits is a downgrade. */
.topbar-menu-toggle { display: none; }

@media (max-width: 720px) {
  /* The panel below hangs out of .topbar, and .topbar's backdrop-filter makes it
     a stacking context — so the panel's own z-index is resolved INSIDE the
     topbar and can't lift it above .tabs-bar, which is position:relative and
     later in the DOM. Ordering the topbar itself is what puts the panel on top.  */
  .topbar { position: relative; z-index: 2; }
  .topbar-inner {
    min-height: 0; padding-top: 12px; padding-bottom: 12px;
    /* Logo and trigger stay on one row; the panel below is positioned against
       this box. */
    position: relative; flex-wrap: nowrap;
  }
  .topbar .brand-logo { height: 48px; }

  .topbar-menu-toggle {
    display: inline-flex; align-items: center; gap: 4px;
    /* 44px tall: the minimum comfortable touch target, and this is now the only
       way to reach Account or Log out on a phone. */
    flex: 0 0 auto; padding: 12px 11px;
    background: transparent; color: var(--text);
    border: 1px solid #364256; border-radius: 2px; cursor: pointer;
  }
  .topbar-menu-toggle:hover { background: transparent; border-color: var(--accent); color: var(--accent-text); filter: none; }
  .topbar-menu-caret { font-size: var(--fs-micro); color: var(--muted); transition: transform 0.2s ease; }
  .topbar-inner.menu-open .topbar-menu-caret { transform: rotate(180deg); color: var(--accent-text); }
  .topbar-inner.menu-open .topbar-menu-toggle { border-color: var(--accent); color: var(--accent-text); }

  /* Four buttons used to wrap onto a second full-width row under the logo,
     costing a whole row of vertical space on the smallest screens for controls
     nobody presses often. Same buttons, now a panel that only exists when asked
     for — so the header is one row whatever the role. */
  .topbar-actions {
    display: none;
    position: absolute; top: calc(100% - 6px); right: clamp(20px, 5vw, 56px);
    z-index: 60; min-width: 190px;
    flex-direction: column; align-items: stretch; gap: 0;
    padding: 6px; border: 1px solid #364256; border-radius: 6px;
    background: var(--panel, #212B39);
    box-shadow: 0 12px 28px rgba(0, 0, 0, 0.45);
  }
  .topbar-inner.menu-open .topbar-actions { display: flex; }
  /* Inside the panel they're menu rows, not ghost buttons. */
  .topbar-actions .btn-public {
    border: 0; border-radius: 4px; justify-content: flex-start;
    padding: 11px 12px; text-align: left; letter-spacing: 0.04em;
  }
  .topbar-actions .btn-public:hover { background: rgba(255, 255, 255, 0.06); color: var(--accent-text); }
}
@media (max-width: 430px) {
  /* Tightest layout: drop the identity line so the logo and the trigger have
     the row to themselves. */
  .topbar .brand-text { display: none; }
  .topbar .brand-logo { height: 42px; }
}

/* Section tabs, styled as the second row of the nav block: identical link
   treatment to the public site's .navlinks a.link (Inter 14px, muted grey,
   thin orange underline that slides in) rather than the app's old bold
   condensed tabs, so public → member area keeps one link language. The bar
   shares the topbar's background; the topbar's own border-bottom is dropped
   inside the app view (below) so brand row + links row read as one block. */
.tabs-bar {
  position: relative;
  background: rgba(27, 36, 48, 0.9);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid #364256;
}
#app-view .topbar { border-bottom: none; }
.tabs-inner {
  max-width: 1180px;
  margin: 0 auto;
  box-sizing: border-box;
  padding: 0 clamp(20px, 5vw, 56px);
  display: flex;
  align-items: center;
}
.tabs {
  display: flex;
  flex-wrap: wrap;      /* narrow screens: tabs drop to a second row instead of collapsing into a menu */
  gap: 2px 30px;        /* small row-gap so wrapped rows aren't cramped; the tab padding carries the rest */
  flex: 1 1 auto;
  min-width: 0;
}
/* Compact "current tab ▾" button shown only when the full row can't fit
   (see updateTabsLayout) — the horizontal tabs collapse into a dropdown. */
.tabs-toggle { display: none; }
.tab {
  flex: 0 0 auto; /* don't shrink, so overflow is measurable (drives the collapse) */
  font-size: var(--fs-body);
  color: var(--muted);
  padding: 12px 0 14px;
  cursor: pointer;
  position: relative;
  white-space: nowrap;
}
.tab::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 10px; height: 1px;
  background: var(--accent); transform: scaleX(0); transform-origin: left;
  transition: transform 0.2s ease;
}
.tab:hover { color: var(--text); }
.tab:hover::after { transform: scaleX(1); }
.tab.active { color: var(--text); }
.tab.active::after { transform: scaleX(1); }

/* --- Collapsed nav (when the tabs don't fit on one row) --- */
.tabs-bar.collapsed .tabs-toggle {
  display: flex; align-items: center; gap: 8px; width: 100%;
  background: transparent; border: none; color: var(--text);
  font-family: inherit; font-size: var(--fs-body); padding: 13px 0; cursor: pointer;
}
.tabs-bar.collapsed .tabs-toggle-caret { margin-left: auto; color: var(--muted); transition: transform 0.2s ease; }
.tabs-bar.collapsed.open .tabs-toggle-caret { transform: rotate(180deg); }
.tabs-bar.collapsed .tabs {
  position: absolute; top: 100%; left: 0; right: 0; z-index: 29;
  flex-direction: column; gap: 0; display: none;
  background: rgba(27, 36, 48, 0.98); backdrop-filter: blur(10px);
  border-bottom: 1px solid #364256; box-shadow: 0 10px 22px rgba(0, 0, 0, 0.4);
}
.tabs-bar.collapsed.open .tabs { display: flex; }
.tabs-bar.collapsed .tab {
  padding: 12px clamp(20px, 5vw, 56px);
  border-bottom: 1px solid var(--border);
}
.tabs-bar.collapsed .tab:last-child { border-bottom: none; }
.tabs-bar.collapsed .tab::after { display: none; } /* the sliding underline doesn't fit a vertical list */
.tabs-bar.collapsed .tab.active { color: var(--accent-text); box-shadow: inset 3px 0 0 var(--accent); }
.tabs-bar.collapsed .tab:hover { background: rgba(232, 98, 44, 0.08); }

/* --- Grouped nav dropdowns (People / Money / Club / More) --- */
.tab-group { position: relative; flex: 0 0 auto; }
.tab-grouphead {
  display: inline-flex; align-items: center; gap: 5px; white-space: nowrap;
  background: transparent; border: none; box-shadow: none; font-family: inherit;
  font-size: var(--fs-body); text-transform: none; letter-spacing: normal; color: var(--muted);
  padding: 12px 0 14px; cursor: pointer;
}
.tab-grouphead:hover { background: transparent; color: var(--text); filter: none; box-shadow: none; }
.tab-grouphead.active { color: var(--text); }
.tab-grouphead.active::after { transform: scaleX(1); }
.grp-caret { font-size: var(--fs-micro); color: var(--muted); transition: transform 0.2s ease; }
.tab-group.open .grp-caret { transform: rotate(180deg); }
.tab-menu {
  display: none; position: absolute; top: 100%; left: -14px; min-width: 176px; z-index: 40;
  background: rgba(27, 36, 48, 0.98); backdrop-filter: blur(10px);
  border: 1px solid #364256; border-top: none; border-radius: 0 0 8px 8px;
  box-shadow: 0 12px 24px rgba(0, 0, 0, 0.4); padding: 6px 0;
}
.tab-group.open .tab-menu { display: block; }
.tab-menu-item { display: flex; align-items: center; justify-content: space-between; gap: 14px; padding: 9px 18px; font-size: var(--fs-body); color: var(--muted); cursor: pointer; white-space: nowrap; }
.tab-menu-item:hover { background: rgba(232, 98, 44, 0.10); color: var(--text); }
.tab-menu-item.active { color: var(--accent-text); box-shadow: inset 3px 0 0 var(--accent); }

/* A tab that needs a dues-paid membership. Present and clickable — it explains
   itself when opened — but visibly not yet yours.
   Deliberately not `disabled`: an unpaid member should be able to look at what
   they're missing, and a control that does nothing when clicked is worse than
   one that says why. The padlock does the signalling; the dimming keeps these
   from competing with the tabs that work. */
.tab-islocked { opacity: 0.62; }
.tab-islocked:hover { opacity: 1; }
.tab-lock { font-size: 0.78em; margin-left: 6px; opacity: 0.85; line-height: 1; }
/* The dropdown's rows are space-between (label left, badge right), which would
   fling the padlock to the far edge — pull it back beside the label. The row's
   14px gap has to go with it, or it stacks on the glyph's own margin and the
   padlock drifts off on its own. */
.tab-menu-item.tab-islocked { justify-content: flex-start; gap: 0; }

/* The page a locked tab renders. Accent left rail, not the danger red — nothing
   has gone wrong, it just isn't open yet. */
.locked-tab { border-left: 3px solid var(--accent); }
.locked-tab-title { font-weight: 700; font-size: var(--fs-title); color: var(--text); }
.locked-tab-text { color: var(--muted); font-size: var(--fs-body); line-height: 1.55; margin: 4px 0 0; max-width: 62ch; }

/* Action-needed count badge (open grievances, new feedback, pending claims…). */
.tab-badge {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 17px; height: 17px; padding: 0 5px; margin-left: 6px;
  font-size: var(--fs-micro); font-weight: 700; line-height: 1; font-family: var(--font-body, inherit);
  background: var(--accent); color: #1B1310; border-radius: 999px; flex: 0 0 auto;
}
.tab-menu-item .tab-badge { margin-left: 0; }

/* Grouped nav inside the collapsed (mobile) menu: flatten the dropdowns into
   labelled sections in the vertical list. */
.tabs-bar.collapsed .tab-group { display: block; }
.tabs-bar.collapsed .tab-grouphead {
  display: block; width: 100%; text-align: left; pointer-events: none;
  padding: 14px clamp(20px, 5vw, 56px) 4px; font-size: var(--fs-micro); letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--muted);
}
.tabs-bar.collapsed .grp-caret { display: none; }
/* A group head is only a section label in the collapsed menu — don't give it the
   active-item accent even when one of its items is current. */
.tabs-bar.collapsed .tab-grouphead.active { color: var(--muted); box-shadow: none; }
.tabs-bar.collapsed .tab-menu {
  position: static; display: block; background: none; border: none;
  box-shadow: none; border-radius: 0; padding: 0; min-width: 0; backdrop-filter: none;
}
.tabs-bar.collapsed .tab-menu-item {
  padding: 12px clamp(20px, 5vw, 56px); border-bottom: 1px solid var(--border); font-size: var(--fs-body);
}
.tabs-bar.collapsed .tab-menu-item:hover { background: rgba(232, 98, 44, 0.08); }
.tabs-bar.collapsed .tab-menu-item.active { color: var(--accent-text); box-shadow: inset 3px 0 0 var(--accent); }

/* Same content grid as the public site's .wrap (site.css): 1180px max width
   with the same responsive gutters, so nothing shifts when crossing between
   the homepage and the member area. */
main { padding: 28px clamp(20px, 5vw, 56px) 44px; max-width: 1180px; margin: 0 auto; }

/* --- Instrument panel: gauge-style stat readouts as discrete cards on a
   gapped grid (numeral above its label, matching the public homepage's
   readout strip). Discrete cards — not one shared slab — so a partial last
   row simply ends instead of leaving empty slab cells. */
.gauges {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: 12px;
  margin-bottom: 24px;
}
/* Explicit column counts for stat sections: each section uses a count that
   divides its tile total exactly (6 tiles -> cols-3, 8 -> cols-4, 10 ->
   cols-5), so no row is ever left with a lone orphan tile. The narrow
   breakpoints only step down to other divisors of the same totals (6 -> 2x3,
   8 -> 2x4, 10 -> 2x5), so the grid stays even at every width. */
.gauges.cols-3 { grid-template-columns: repeat(3, 1fr); }
.gauges.cols-4 { grid-template-columns: repeat(4, 1fr); }
.gauges.cols-5 { grid-template-columns: repeat(5, 1fr); }

/* Semantic tile rows: a labeled row lays its tiles out evenly across the full
   width whatever their count, so a straggler tile is impossible — the pool
   layout wrapped eleven tiles as 5-5-1 and left one floating. Tiles within a
   row are always equal; width differences BETWEEN rows read as grouping, not
   emphasis, because each row carries its own label. */
.gauge-row-label {
  font-family: 'JetBrains Mono', monospace; font-size: var(--fs-micro);
  font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--muted); margin: 0 0 8px;
}
.gauges.even { display: flex; flex-wrap: wrap; margin-bottom: 18px; }
.gauges.even > * { flex: 1 1 160px; min-width: 0; }
@media (max-width: 860px) {
  .gauges.cols-4, .gauges.cols-5 { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 700px) {
  .gauges.cols-3 { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 430px) {
  .gauges.cols-3, .gauges.cols-4, .gauges.cols-5 { grid-template-columns: 1fr; }
}
.gauge {
  display: flex;
  flex-direction: column-reverse;
  justify-content: flex-end;
  gap: 6px;
  padding: 18px clamp(16px, 2.2vw, 24px);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 6px;
  box-shadow: 0 6px 16px rgba(10, 14, 20, 0.25);
}
/* A tile you can open. It's a real <button> so it's keyboard-reachable and
   announced as a control — which means explicitly undoing the UA button styles,
   since .gauge was written for a <div> and inherits none of them. */
button.gauge {
  font: inherit;
  color: inherit;
  text-align: inherit;
  width: 100%;
  cursor: pointer;
  /* Chrome's UA sheet puts align-items:center on every <button>, and .gauge is a
     column flex container — so as a button its label and value were centred
     while the plain <div> tiles beside them stayed left. Match the divs. */
  align-items: normal;
  transition: background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease, transform 0.15s ease;
}
/* The global `button:hover` fills solid orange and outranks .gauge, so hovering
   a tile lit the whole card up. A tile isn't a call to action — it's a surface
   you can open — so it darkens instead, and the lift does the signalling. */
button.gauge:hover, button.gauge:focus-visible {
  background: var(--bg);
  border-color: var(--border);
  box-shadow: 0 8px 22px rgba(10, 14, 20, 0.4);
  transform: translateY(-1px);
  filter: none;
}
button.gauge:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* The affordance: a quiet chevron that firms up on hover, so the tile reads as
   openable without a permanent piece of chrome shouting on every card. */
.gauge-more {
  position: absolute; top: 10px; right: 12px;
  font-size: var(--fs-micro); color: var(--muted); opacity: 0.55;
  transition: opacity 0.15s ease, color 0.15s ease;
}
button.gauge { position: relative; }
button.gauge:hover .gauge-more, button.gauge:focus-visible .gauge-more { opacity: 1; color: var(--text); }

.gauge .label {
  font-family: 'JetBrains Mono', monospace;
  font-size: var(--fs-micro);
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}
.gauge .value {
  font-family: 'JetBrains Mono', monospace;
  font-variant-numeric: tabular-nums;
  font-size: clamp(22px, 2.2vw, 28px);
  font-weight: 600;
  white-space: nowrap;
}
.gauge .value.good { color: var(--good-text); }
.gauge .value.bad { color: var(--bad-text); }

/* Compact, centered "people chips" under the monthly-report number tiles
   (New members / Volunteers) — keeps each name and its detail together and
   centered, instead of a wide, edge-justified table. */
/* ---- Welcome overlay (the moment someone becomes a member) ---------------- */
/* Taller than the other modals because it carries a receipt, a short perks
   list and one ask — but capped and scrolled, so a phone never has to fight it. */
.wc-panel { max-width: 560px; max-height: 88vh; overflow-y: auto; }
/* The receipt leads. Someone who has just moved money wants "it went through"
   answered before anything else — especially on Zelle, where they did the
   transfer by hand and are half-expecting to have got it wrong. */
.wc-receipt {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 12px 14px; margin: 0 0 18px;
  border-radius: 4px; background: rgba(79,169,124,0.12); border-left: 3px solid var(--good);
}
.wc-tick {
  flex: 0 0 24px; height: 24px; border-radius: 50%; margin-top: 1px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--good); color: #10221a; font-weight: 700; font-size: var(--fs-body);
}
.wc-receipt strong { display: block; font-size: var(--fs-title); color: var(--text); }
.wc-receipt span { display: block; color: var(--muted); font-size: var(--fs-body); line-height: 1.5; margin-top: 2px; }
.wc-title {
  font-family: 'Barlow Condensed', sans-serif; font-weight: 700;
  font-size: var(--fs-display); color: var(--text); margin: 0 0 4px;
}
/* Each idea gets its own box. As one flat column the gate code, the sign-off
   and the events ran together into a wall the eye slid off — and the gate code
   is the single most practical thing here, so it can't be one paragraph among
   several. Boxed, they read as five separate things worth knowing. */
.wc-cell {
  margin-top: 14px; padding: 14px 16px;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 4px;
}
.wc-cell h3 {
  font-family: 'JetBrains Mono', monospace; font-size: var(--fs-micro); font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.1em; color: var(--muted); margin: 0 0 10px;
}
.wc-cell p { color: var(--muted); font-size: var(--fs-body); line-height: 1.55; margin: 0 0 12px; max-width: 58ch; }
.wc-cell button { margin-top: 2px; }
.wc-cell-note { font-size: var(--fs-meta) !important; margin: 10px 0 12px !important; }
.wc-cell-note:last-child { margin-bottom: 0 !important; }
.wc-cell-note strong { color: var(--text); }

/* The gate code is the key to the field — printed like a key, not like prose. */
.wc-gate { border-color: var(--accent); }
.wc-code {
  font-family: 'JetBrains Mono', monospace; font-weight: 600;
  font-size: var(--fs-display); letter-spacing: 0.14em; color: var(--accent-text);
}
.wc-hint { font-size: var(--fs-meta) !important; margin: 4px 0 0 !important; font-style: italic; }

/* The events cell is the one the club most wants acted on — a member who turns
   up to something is a member who stays — so it gets the green accent. */
.wc-events-cell { border-left: 3px solid var(--good); }
.wc-perks { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 9px; }
.wc-perks li { font-size: var(--fs-body); line-height: 1.5; color: var(--muted); }
.wc-perks strong { color: var(--text); }
/* Underlined, not just coloured. On a card of three similar lines an orange
   word alone doesn't read as "press me" — and this is the only pressable thing
   in the list. */
.wc-perks a { color: var(--accent-text); text-decoration: underline; text-underline-offset: 2px; }
.wc-perks a:hover { color: var(--text); }
.wc-weather { display: flex; align-items: center; gap: 12px; }
.wc-weather strong { display: block; font-size: var(--fs-body); color: var(--text); }
.wc-weather span { display: block; font-size: var(--fs-meta); color: var(--muted); }
/* The forecast sits last in the cell now, under its own heading, so the dashed
   rule separates it from the invitation and button above rather than from the
   event list. Kept as a rule rather than plain space because without it — on a
   phone especially, where each event stacks into two lines — the forecast read
   as one more item in the list above. */
.wc-weather-block {
  margin-top: 16px; padding-top: 14px; border-top: 1px dashed var(--border);
}
.wc-subhead {
  margin: 0 0 8px; font-size: var(--fs-meta); color: var(--muted);
  text-transform: uppercase; letter-spacing: 0.04em; font-weight: 700;
}
.wc-events { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 7px; }
.wc-events li { display: flex; gap: 12px; font-size: var(--fs-body); color: var(--text); }
.wc-when { flex: 0 0 auto; color: var(--muted); font-family: 'JetBrains Mono', monospace; font-size: var(--fs-meta); min-width: 86px; }
/* The one ask. Boxed so it reads as a request rather than more information,
   and placed last so nobody has to get past it to reach the good news. */
.wc-ask { background: transparent; border-style: dashed; }
.wc-foot {
  display: flex; align-items: center; justify-content: flex-end; flex-wrap: wrap; gap: 12px;
  margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--border);
}
@media (max-width: 560px) {
  .wc-foot { flex-direction: column; align-items: stretch; }
  .wc-foot button { width: 100%; }
  /* Date over title instead of beside it. The gap BETWEEN events has to grow
     with it — at 7px apart with 1px inside, six stacked lines read as one
     block rather than three events. */
  .wc-events li { flex-direction: column; gap: 1px; }
  .wc-events { gap: 12px; }
}
/* The conditions panel hides its weather glyph under 600px because it competes
   with the verdict line there. In here it sits on its own row with room to
   spare, so it stays — it's the one bit of colour in the overlay. */
@media (max-width: 600px) { .wc-weather .wx { display: flex; } }

/* ---- Flight Training ------------------------------------------------------ */
/* The page a new pilot is sent to, and the one an experienced newcomer needs
   too. It answers three questions in this order: where do I stand, what happens
   here, and what can I read. Everything below is driven by two facts — whether
   the member has been signed off, and what skill level they told us. */

/* Where you stand. The one thing actually asked of a member, so the only place
   on the page the accent shouts. */
.ft-gate {
  display: flex; gap: 14px; align-items: flex-start;
  border-left: 3px solid var(--accent);
}
.ft-gate .ft-ic { flex: 0 0 auto; margin-top: 2px; color: var(--accent-text); }
.ft-gate h2 { margin: 0 0 4px; }
.ft-gate p { color: var(--muted); font-size: var(--fs-body); margin: 0; max-width: 60ch; }
.ft-gate.cleared { border-left-color: var(--good); }
.ft-gate.cleared .ft-ic { color: var(--good-text); }
.ft-gate-actions { display: flex; flex-wrap: wrap; gap: 9px; margin-top: 13px; }

/* What your skill level is doing for you. Sits high because the half about wind
   limits matters to someone who never scrolls as far as the videos. */
.ft-tune {
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px;
  padding: 13px 17px; margin-bottom: 15px;
  color: var(--muted); font-size: var(--fs-meta); line-height: 1.6; text-wrap: pretty;
}
.ft-tune strong { color: var(--text); }
.ft-tune a { color: var(--accent-text); }

/* The club's process. Steps an experienced pilot has already done recede rather
   than disappear: removing them would hide the shape of the thing, and would
   assume the member judged their own level correctly. */
.ft-steps { display: flex; flex-direction: column; margin-top: 4px; }
.ft-step { padding: 11px 0; border-top: 1px solid var(--border); }
.ft-step:first-child { border-top: none; padding-top: 2px; }
.ft-step b { display: block; font-size: var(--fs-title); margin-bottom: 2px; }
.ft-step span { color: var(--muted); font-size: var(--fs-meta); line-height: 1.55; }
.ft-step.done { padding: 7px 0; }
.ft-step.done b { font-size: var(--fs-body); color: var(--muted); }
.ft-step.done span { color: var(--muted); opacity: .8; }
.ft-step .ft-had { font-size: var(--fs-meta); font-style: italic; font-weight: 400; color: var(--muted); margin-left: 7px; }

/* Resource cards. Three states carried by form, not by six different hues:
   unseen plain, part-watched an accent rail, watched a tick and the title
   stepping back so unseen things stand out. */
.ft-grid { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 2px; }
.ft-card {
  flex: 1 1 250px; min-width: 210px; display: flex; gap: 10px; align-items: flex-start;
  text-align: left; background: var(--surface-2); border: 1px solid var(--border);
  border-left: 3px solid transparent; border-radius: 7px; padding: 11px 13px;
  cursor: pointer; font: inherit; color: inherit;
  /* This is a <button>, so the global button rule applies — and that rule is
     written for a button-shaped button: uppercase, letter-spaced, nowrap.
     `font: inherit` looks like it undoes that, but the font shorthand covers
     none of these three. The result was a card title running on one line, in
     caps, straight out of its own card. overflow-wrap so a long unbroken title
     can't manage it either. */
  text-transform: none;
  letter-spacing: normal;
  white-space: normal;
  overflow-wrap: anywhere;
  transition: border-color .12s, background .12s;
}
.ft-card:hover { background: var(--surface); }
.ft-card:hover .ft-t { color: #fff; }
.ft-card:focus-visible { outline: none; box-shadow: 0 0 0 2px var(--accent); }
.ft-card .ft-mark { flex: 0 0 auto; width: 15px; height: 15px; margin-top: 3px; color: var(--muted); }
.ft-card .ft-mid { flex: 1 1 auto; min-width: 0; }
.ft-t { font-weight: 600; font-size: var(--fs-body); line-height: 1.35; color: var(--text); }
.ft-d { color: var(--muted); font-size: var(--fs-meta); margin-top: 3px; line-height: 1.45; }
.ft-meta {
  font-family: 'JetBrains Mono', monospace; font-size: var(--fs-micro); color: var(--muted);
  margin-top: 5px; font-variant-numeric: tabular-nums; letter-spacing: .02em;
}
.ft-card.part { border-left-color: var(--accent); }
.ft-card.part .ft-meta { color: var(--accent-text); }
.ft-card.done { border-left-color: var(--good); }
.ft-card.done .ft-mark { color: var(--good-text); }
.ft-card.done .ft-t { color: var(--muted); font-weight: 500; }

.ft-sec { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin: 22px 0 3px; }
.ft-sec:first-of-type { margin-top: 4px; }
.ft-sec h3 { font-size: var(--fs-title); font-weight: 700; margin: 0; }
.ft-sec .ft-runtime { font-family: 'JetBrains Mono', monospace; font-size: var(--fs-micro);
  color: var(--muted); white-space: nowrap; font-variant-numeric: tabular-nums; }
.ft-why { color: var(--muted); font-size: var(--fs-meta); margin: 0 0 9px; max-width: 63ch; }

/* Panels that are live guidance until you're signed off and history after.
   Native <details> so the control is a real disclosure widget. */
.ft-fold { background: var(--surface); border: 1px solid var(--border); border-radius: 6px; margin-bottom: 15px; }
.ft-fold > summary { list-style: none; cursor: pointer; padding: 14px 20px;
  display: flex; align-items: center; gap: 10px; color: var(--muted); font-size: var(--fs-body); }
.ft-fold > summary::-webkit-details-marker { display: none; }
.ft-fold > summary:hover { color: var(--text); }
.ft-fold > summary strong { color: var(--text); }
.ft-fold > summary .ft-chev { margin-left: auto; flex: 0 0 auto; transition: transform .15s; }
.ft-fold[open] > summary { border-bottom: 1px solid var(--border); }
.ft-fold[open] > summary .ft-chev { transform: rotate(180deg); }
.ft-fold .ft-inner { padding: 16px 20px 18px; }

/* Signer chips in the proficiency-check overlay. Bare names: the heading above
   already asked the question, so a subtitle on every row just answers it twice. */
.ft-signer {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 5px 13px 5px 6px; background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 999px; font-size: var(--fs-meta); font-weight: 600; color: var(--text);
}
.ft-signer .member-int-avatar { width: 26px; height: 26px; font-size: var(--fs-micro); }

/* The video overlay. The stage is a fixed 16:9 so the panel doesn't jump as the
   iframe loads, and it scrolls inside itself on a phone. */
.ft-stage { position: relative; aspect-ratio: 16 / 9; background: #000; width: 100%; }
.ft-stage iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
.ft-player-foot { display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-start;
  justify-content: space-between; padding: 14px 0 0; }
.ft-player-foot h3 { margin: 0 0 3px; font-size: var(--fs-title); }
.ft-player-foot .ft-sub { color: var(--muted); font-size: var(--fs-meta); margin: 0; }

/* Brief highlight after jumping down the page, so a reader can see where they
   landed instead of hunting for what changed. */
@keyframes ft-landed {
  from { box-shadow: 0 0 0 3px rgba(232,98,44,.55); }
  to   { box-shadow: 0 0 0 3px rgba(232,98,44,0); }
}
.ft-flash { animation: ft-landed 1.6s ease-out; }
@media (prefers-reduced-motion: reduce) {
  .ft-flash { animation: none; box-shadow: 0 0 0 2px var(--accent); }
  .ft-card, .ft-fold > summary .ft-chev { transition: none; }
}

/* A section on the profile that needs separating from the one above it. The
   handbook sat flush under the directory switches with nothing between them. */
.profile-break {
  margin-top: 26px; padding-top: 20px; border-top: 1px solid var(--border);
}

/* ---- Pay dues / donate: the two payment options, side by side -------------
   Was an inline style string, duplicated verbatim in two render functions, and
   the two columns disagreed about where their button went. The boxes were equal
   height already — .row stretches them — but the content ran top-down, and the
   card column carries a Square iframe the Zelle column doesn't. So one button
   sat level with the other's fine print.
   Column flex with the action block pushed to the bottom lines them up whatever
   sits above, which also stops the layout shifting when Square finishes loading
   or a Zelle instruction line wraps. */
.pay-option {
  flex: 1; min-width: 260px;
  display: flex; flex-direction: column;
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 6px; padding: 18px 20px;
}
.pay-option h3 { font-size: var(--fs-panel); margin: 0 0 6px; }
.pay-option .pay-lead { color: var(--muted); font-size: var(--fs-meta); margin: 0 0 12px; }
/* The body grows, so the action block lands at the bottom of a stretched column
   and the two buttons line up no matter what sits above them. That has to stay
   structural: Square's card element is one row on a narrow column and two on a
   wide one, so any fixed floor here is only ever right at one screen width.
   A floor of 6.4em was tried and it broke desktop — Square's two-row form is
   about twice that, so the card body sailed past the floor while the Zelle body
   sat on it, and the buttons came apart by roughly 60px. */
.pay-option .pay-body { flex: 1 1 auto; display: flex; flex-direction: column; }
.pay-option .pay-body > p { margin: 0 0 4px; }
.pay-option .pay-body > p:last-child { margin-bottom: 0; }
/* The card element hugs its button. When the Zelle side is the taller of the two
   this is what keeps the entry fields sitting on top of the thing that submits
   them, instead of stranded under the "credit or debit" line with the slack
   below. When the card side is taller it has no slack to move through and this
   does nothing. Deliberately not applied to the Zelle text: pushed down, its
   memo line drifts away from the sentence it belongs with. */
.pay-option .pay-body > div:last-child { margin-top: auto; }
/* Square reserves a validation-message slot under the card fields and keeps it
   empty — and invisible — until something is wrong. That is 40px of blank space
   inside the card element (a 16.5px line plus 11px margins), and it reads as a
   gap between the fields and the Pay button that no margin here can explain.
   Collapse it while it is empty; the moment Square writes a message the selector
   stops matching, the space comes back, and the message shows. Scoped to the pay
   boxes, and if Square ever renames the class we lose the tightening, nothing
   more. */
.pay-option .pay-body .sq-card-message:empty { display: none; }
.pay-option .pay-action { margin-top: 16px; }
.pay-option .pay-action button { width: 100%; }
.pay-option .pay-fine { color: var(--muted); font-size: var(--fs-meta); margin: 10px 0 0; line-height: 1.5; }
/* The one instruction on this card that has consequences: without a name in the
   memo the club gets an anonymous transfer and can't match it to anybody. It was
   the third of three identical grey lines, 4px below its neighbour, and read as
   more small print. Set apart, and the two words that matter are picked out. */
/* `> p.pay-memo`, not `.pay-memo`: the rule above it is
   `.pay-option .pay-body > p { margin: 0 0 4px }`, which carries an element
   selector and so outranks a plain two-class selector — its shorthand was
   silently zeroing the margin-top set here. Measured 4px when it should have
   been 14. The element in the selector is load-bearing. */
/* A small fixed gap, not `margin-top: auto`. Auto let this line absorb whatever
   slack the column had, which set it adrift from the sentence above — the gap
   should say "read this separately", not "these are unrelated". */
.pay-option .pay-body > p.pay-memo {
  color: var(--muted); font-size: var(--fs-meta); line-height: 1.5;
  margin: 10px 0 0;
}
.pay-option .pay-memo strong { color: var(--text); }
/* Pinning the action block to the bottom lined up the blocks, not the buttons:
   the Zelle note runs to two lines and the card note to one, so the taller note
   pushed its own button up and left the card's sitting lower.
   Reserving two lines for both makes the blocks the same height, which puts the
   buttons on the same line and leaves a little slack under the shorter note —
   the right way round, since the button is the thing the eye is looking for. */
.pay-option .pay-action .pay-fine { min-height: 3em; }
/* Stacked, there's nothing to line up with, so let the note be its own height. */
@media (max-width: 620px) {
  .pay-option .pay-action .pay-fine { min-height: 0; }
}

/* Bottom-aligning the action block lines up the blocks, not the buttons, and the
   two blocks are not the same height: the card one carries an error slot the
   Zelle one doesn't, and either note can wrap to a third line at some widths.
   Reserving two lines papers over the common case and drifts everywhere else —
   measured 19px apart across every container width from 700px down.
   Subgrid settles it by construction. The two boxes share one set of four rows —
   heading, lead, body, action — so the action row starts at the same y in both
   columns whatever is above it, and the button is that row's first child. No
   number to keep in sync with the content.
   Where subgrid is missing the flex rules above still run: buttons within a
   score of pixels rather than exactly level, which is where this started. */
@supports (grid-template-rows: subgrid) {
  /* `.row.pay-row`, not `.pay-row`: `.row { display: flex }` is defined further
     down this file at equal specificity, so a single class here loses on source
     order and the grid silently never applies — which leaves the boxes holding
     `subgrid` with no parent grid to inherit from, worse than doing nothing.
     No media query either: `auto-fit` drops to one column at exactly the width
     the boxes stop fitting, which is where the flex version wrapped. A viewport
     breakpoint would have to guess that width from the outside — 620px was the
     guess, and it was about 90px off, leaving a band where the columns sat side
     by side with the alignment switched off. */
  /* Every row sized to its own content — no `1fr` on the body. A flexible row
     absorbs whatever height the grid has spare, which pushed both buttons down
     and opened a gap under the card fields that belonged to neither column. With
     all four rows on content, the body row is exactly the taller of the two
     bodies, so the taller column has no gap at all and the shorter one has only
     the real difference between them. */
  .row.pay-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
                 grid-template-rows: auto auto auto auto;
                 /* `.row`'s 12px gap belongs between the two boxes. As a grid it
                    would also open between the four rows, doubling up with the
                    margins those elements already carry. */
                 column-gap: 12px; row-gap: 0; }
  .row.pay-row > .pay-option { display: grid; grid-template-rows: subgrid; grid-row: span 4; }
  /* With no row gap, stacked boxes would touch. Both boxes carry it, not just the
     first: the margin sits outside the border box but inside the row the grid
     sizes, so giving it to one column alone ends that box 12px short of the
     other and the two bottom edges stop lining up. */
  .row.pay-row > .pay-option { margin-bottom: 12px; }
}

/* Profile: birthdate leads this row once there is room for two columns, so the
   two fields a member types (AMA number, birthdate) sit in one column and the
   two the AMA lookup fills (expiration, membership type) sit in the other.
   Only above 768px. Narrower than that the row stacks, "beside" stops meaning
   anything, and the reading order is what matters — and stacked, the source
   order is already the better one: the two looked-up fields come one after the
   other instead of being split by birthdate.
   `order` rather than reordering the markup, so the phone keeps source order.
   Safe for keyboard users: it doesn't change tab order, and the field it moves
   past is a read-only div that can't be focused. */
@media (min-width: 768px) {
  .profile-lookup-row .pf-birthdate { order: -1; }
}

/* ---- Profile: saved vs. merely remembered -------------------------------- */
/* The form keeps what you typed even if you never saved it, which is kind until
   it's confusing: the field shows your new answer, the club still holds the old
   one, and nothing says which is which. These mark the difference on the field
   itself, so the answer doesn't depend on reading a banner. */
.field-unsaved > label::after {
  content: 'not saved';
  margin-left: 8px; padding: 1px 6px; border-radius: 2px;
  font-family: 'JetBrains Mono', monospace; font-size: var(--fs-micro);
  font-weight: 600; letter-spacing: 0.04em; text-transform: none;
  background: rgba(232, 98, 44, 0.16); color: var(--accent-text-on-tint);
}
.field-unsaved > input, .field-unsaved > select {
  border-color: var(--accent);
  box-shadow: inset 3px 0 0 var(--accent);
}
/* Pinned to the bottom of the panel rather than printed once at the top: a
   change made near the end of a long form used to announce itself somewhere
   you'd already scrolled past. */
.profile-unsaved-bar {
  position: sticky; bottom: 0; z-index: 3;
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: 12px;
  margin: 18px -18px -18px; padding: 13px 18px;
  background: var(--surface-2); border-top: 2px solid var(--accent);
  border-radius: 0 0 4px 4px;
}
/* Not-yet-savable reads as a caution, not an alarm: nothing is wrong, it just
   isn't their turn. Same amber the save hint uses. */
.profile-unsaved-bar.blocked strong { color: var(--accent-text); }

.profile-unsaved-bar strong { display: block; font-size: var(--fs-body); color: var(--text); }
.profile-unsaved-bar span { display: block; color: var(--muted); font-size: var(--fs-meta); line-height: 1.5; max-width: 60ch; }
.profile-unsaved-actions { display: flex; gap: 8px; flex-wrap: wrap; flex: 0 0 auto; }
.profile-unsaved-actions button { width: auto; }
@media (max-width: 620px) {
  .profile-unsaved-bar { flex-direction: column; align-items: stretch; }
  .profile-unsaved-actions button { flex: 1 1 0; }
}
/* Three real choices, stacked so none of them is the accidental one. */
.unsaved-choices { display: flex; flex-direction: column; gap: 8px; margin-top: 18px; }
.unsaved-choices button { width: 100%; }
.danger-text { color: var(--bad-text); }
.danger-text:hover { border-color: var(--bad); color: var(--bad-text); }

/* Said under "your membership is active" when the Zelle transfer behind it
   hasn't been matched to the club's account yet. Quiet, not alarming — this is
   the normal state for a few days, and the member has done nothing wrong. */
.dues-unverified { color: var(--muted); font-size: var(--fs-meta); line-height: 1.55; margin: 10px 0 0; max-width: 68ch; }

/* The ledger before anyone has asked for it. Not an empty state — the report
   simply hasn't run — so it reads as an instruction rather than "no data". */
.ledger-idle { color: var(--muted); font-size: var(--fs-body); margin: 0; }
.ledger-idle strong { color: var(--text); }

/* An event in the attendance report with nothing logged against it. Muted so
   the eye lands on the events that took money, but present, because "no row"
   and "no takings" are different facts and only one of them needs chasing. */
.att-blank td { color: var(--muted); }
.att-blank td[colspan] { text-align: left; font-style: italic; }
.att-note {
  color: var(--muted); font-size: var(--fs-meta); line-height: 1.55;
  margin: 12px 0 0; max-width: 70ch;
}

.report-people-h {
  text-align: center;
  font-family: 'JetBrains Mono', monospace;
  font-size: var(--fs-micro);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--muted);
  margin: 20px 0 12px;
}
/* A grouping's categories (e.g. New members + Welcome back) laid out side by
   side when there's room, wrapping to the next line when there isn't. */
.report-cats {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 32px;
  justify-content: center;
  align-items: flex-start;
}
.report-cat { flex: 1 1 300px; min-width: 0; }
/* Compact, centered people/event cards within a category. */
.report-people {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  justify-content: center;
  margin: 10px 0 0;
}
.report-person {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 14px 14px 13px;
  text-align: center;
  width: 150px;          /* uniform card size across every section */
  box-sizing: border-box;
}
/* Initials avatar (people) — matches the member directory circles. */
.rp-avatar {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  overflow: hidden;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--accent);
  color: #1b1310;
  font-family: var(--font-display, inherit);
  font-weight: 700;
  font-size: var(--fs-title);
}
/* The member's actual profile photo, when they have one. */
.rp-avatar img { width: 100%; height: 100%; object-fit: cover; display: block; }
.report-person .rp-name { font-weight: 600; color: var(--text); font-size: var(--fs-body); line-height: 1.2; }
.report-person .rp-meta { color: var(--muted); font-size: var(--fs-meta); margin-top: 1px; }
/* Events held without volunteers — a simple centered list of event · date. */
.event-lines { display: flex; flex-direction: column; gap: 7px; text-align: center; margin-top: 12px; }
.event-line { font-size: var(--fs-body); }
.event-line .el-title { font-weight: 600; color: var(--text); }
.event-line .el-date { color: var(--muted); }

/* Volunteers grouped by event: a centered event sub-heading above each group
   of people chips. */
.vol-group { margin-top: 12px; }
.vol-group-head { text-align: center; font-size: var(--fs-body); margin-bottom: 8px; }
.vol-group-head .vg-title { font-weight: 600; color: var(--text); }
.vol-group-head .vg-date { color: var(--muted); }

/* Monthly / year-end report expense tables (by category / by subcategory):
   centered, fixed column widths so the Count and Total columns line up
   vertically between the two stacked tables regardless of label length, with
   the numbers right-aligned (tabular) so their digits line up too. */
.rep-exp-table { table-layout: fixed; width: 100%; max-width: 620px; margin: 0 auto; }
.rep-exp-table col.re-count { width: 18%; }
.rep-exp-table col.re-total { width: 24%; }
.rep-exp-table th, .rep-exp-table td { padding: 8px 16px; }
.rep-exp-table td:not(.num), .rep-exp-table th:not(.num) { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rep-exp-table .num { text-align: right; font-variant-numeric: tabular-nums; }

/* Placeholder shown in a dashboard section while its data loads. */
.dash-loading {
  color: var(--muted); font-size: var(--fs-body); padding: 22px 4px;
  animation: dashPulse 1.2s ease-in-out infinite;
}
@keyframes dashPulse { 0%,100% { opacity: 0.45; } 50% { opacity: 0.85; } }
@media (prefers-reduced-motion: reduce) { .dash-loading { animation: none; } }

/* Section divider for grouping stats (Finances / Membership / Engagement) on
   the dashboard and inside reports. */
.stat-section {
  margin: 26px 0 12px; color: var(--muted); font-size: var(--fs-meta); font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.12em;
  border-bottom: 1px solid var(--border); padding-bottom: 6px;
}
.stat-section:first-child { margin-top: 0; }

/* --- Cards / panels --- */
.panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 20px;
  margin-bottom: 20px;
}
.panel h2 { font-size: var(--fs-panel); margin-bottom: 14px; color: var(--muted); }

/* --- Getting-started onboarding checklist --- */
.panel.onboarding { border-color: var(--accent); }
.panel.onboarding h2 { color: var(--text); }
.chk-list { display: flex; flex-direction: column; gap: 2px; }
.chk-item {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 11px 4px; border-top: 1px solid var(--border);
}
.chk-item:first-child { border-top: none; }
.chk-dot {
  flex: 0 0 22px; height: 22px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: var(--fs-body); line-height: 1; margin-top: 1px;
  border: 1px solid var(--border); color: var(--muted); background: var(--surface-2);
}
.chk-dot.done { background: rgba(79,169,124,0.18); border-color: transparent; color: var(--good-text-on-tint); }
.chk-dot.opt { color: var(--accent-text); }
/* The one step to do now. Only this row carries a button, so it also carries the
   only emphasis — the rest of the list is the map, not the instruction. */
.chk-item.now .chk-dot { border-color: var(--accent); color: var(--accent-text); }
.chk-item.now .chk-body strong { color: var(--text); }
/* Steps still ahead recede: they're context for where you are, not four things
   being asked of you at once. */
.chk-item:not(.now):not(.done) .chk-body strong { color: var(--muted); font-weight: 500; }
.chk-item:not(.now):not(.done) .chk-help { display: none; }
.chk-body { flex: 1; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.chk-body strong { font-weight: 600; font-size: var(--fs-body); }
.chk-item.done .chk-body strong { color: var(--muted); font-weight: 500; }
.chk-help { font-size: var(--fs-meta); color: var(--muted); }
.chk-go {
  flex: 0 0 auto; align-self: center;
  padding: 6px 12px; font-size: var(--fs-meta);
}
/* On a phone the buttons refused to shrink (flex 0 0 auto) while the text column
   did, so a step with two of them — "No children" beside "Add children" — left
   the help text one word wide with the buttons sitting over it. They drop to
   their own lines under the text instead, in source order, so the quieter
   answer reads first. Indented to the text column, not the bullet. */
@media (max-width: 620px) {
  .chk-item { flex-wrap: wrap; }
  .chk-body { flex: 1 1 calc(100% - 34px); }
  .chk-go {
    flex: 1 1 calc(100% - 34px);
    margin-left: 34px;
    align-self: stretch;
    justify-content: center;
  }
}
/* Brief highlight when a checklist item scroll-jumps to a panel. */
@keyframes panelFlash { 0% { box-shadow: 0 0 0 2px var(--accent); } 100% { box-shadow: 0 0 0 2px transparent; } }
.panel.flash { animation: panelFlash 1.1s ease-out; }
@media (prefers-reduced-motion: reduce) { .panel.flash { animation: none; } }

/* --- Member events & volunteering --- */
.event-card { position: relative; transition: box-shadow .18s ease, border-color .18s ease, transform .18s ease, opacity .18s ease; }
/* Imminent events (today / tomorrow / this weekend) get a subtle accent edge. */
.event-card.soon::before { content: ''; position: absolute; left: 0; top: 12px; bottom: 12px; width: 3px;
  background: var(--accent); border-radius: 0 3px 3px 0; }
.event-card.past { opacity: 0.85; border-color: var(--border); }
/* Every card lights up, past ones included. Hover used to skip .past, on the
   logic that a finished event has nothing to act on — but it still has a flyer
   to open, coordinators to mail, and a coordinator's own collect tools. On the
   Past filter that meant a whole page where nothing responded to the mouse,
   which reads as broken rather than as finished. The dimming is what says the
   event is over; hover just says which card you're on, and it lifts the dimming
   while you're there.
   This rule has to come after `.event-card.past`, not before: both are two
   selectors, so at equal specificity the later one wins and a hover written
   above would lose its border colour to the one below it. */
.event-card:hover { border-color: rgba(232, 98, 44, 0.35); box-shadow: 0 8px 22px rgba(0, 0, 0, 0.30); transform: translateY(-2px); }
.event-card.past:hover { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .event-card:hover { transform: none; } }

/* Filter toggle (Upcoming / I'm volunteering / Past) */
.event-filter { display: flex; gap: 6px; margin-bottom: 16px; flex-wrap: wrap; }
.event-filter-btn {
  font-size: var(--fs-body); font-weight: 600; padding: 7px 14px; border-radius: 999px; width: auto;
  background: var(--surface-2); border: 1px solid var(--border); color: var(--muted); cursor: pointer;
  transition: border-color .12s, color .12s, background .12s;
}
.event-filter-btn:hover { border-color: var(--accent); color: var(--text); }
.event-filter-btn.active { background: rgba(232,98,44,0.15); border-color: var(--accent); color: var(--accent-text-on-tint); }
.event-filter-btn .cnt { font-weight: 700; opacity: .75; margin-left: 2px; }
.event-filter-btn .pill-scope { font-weight: 400; opacity: .6; font-size: var(--fs-meta); margin-left: 1px; }

/* Card header: date badge + title/meta + optional flyer thumbnail */
/* Date badge = left gutter; .event-body holds everything else in one column. */
.event-row { display: flex; align-items: flex-start; gap: 14px; }
.event-body { flex: 1; min-width: 0; }
.event-head { display: flex; align-items: flex-start; gap: 14px; }
/* The event-day forecast, in the card's top corner beside the flyer (or in the
   corner on its own when there isn't one). Sized to be read at a glance and
   ignored just as easily — it's context for a decision, not the point of the
   card. */
.event-fc {
  flex: 0 0 auto; display: flex; align-items: center; gap: 7px;
  padding: 6px 10px; border-radius: 7px;
  background: var(--surface-2); border: 1px solid var(--border);
}
.event-fc-icon { display: flex; flex: 0 0 auto; color: var(--muted); }
.event-fc-icon svg { width: 24px; height: 24px; }
.event-fc-body { display: flex; flex-direction: column; line-height: 1.25; }
.event-fc-temp { font-size: var(--fs-body); font-weight: 600; color: var(--text); white-space: nowrap; }
.event-fc-lo { color: var(--muted); font-weight: 400; }
.event-fc-meta { font-size: var(--fs-micro); color: var(--muted); white-space: nowrap; }
/* On a phone there isn't room beside the title — squeezed into the corner it
   crushed the title to two lines and shrank the icon out of existence. It gets
   its own line under the head instead, which also buys back the detail. */
@media (max-width: 620px) {
  .event-head { flex-wrap: wrap; }
  .event-fc { order: 2; flex-basis: 100%; justify-content: flex-start; padding: 6px 9px; }
  .event-fc-body { flex-direction: row; align-items: baseline; gap: 8px; }
}
.event-head-main { flex: 1; min-width: 0; }
.event-desc { margin: 6px 0 0; font-size: var(--fs-body); line-height: 1.55; }
.event-title-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.event-meta { font-size: var(--fs-body); color: var(--accent-text); font-family: var(--mono, monospace); margin-top: 5px; }
/* The blurb tucks up under the title, so the date/time needs its own breathing
   room below it. Scoped to the description so a card without one keeps the
   original tight gap under the title. */
.event-desc + .event-meta { margin-top: 10px; }
/* Venue + address on their own clean line (name still opens the map). */
.event-loc { display: flex; align-items: flex-start; gap: 6px; margin-top: 6px; font-size: var(--fs-body); line-height: 1.5; }
.event-loc .loc-pin { flex: 0 0 auto; color: var(--muted); position: relative; top: 3px; }
.event-loc-body { min-width: 0; }
.event-loc .loc-link { color: var(--text); font-weight: 600; text-decoration: none; }
.event-loc .loc-link:hover { color: var(--accent-text); }
.event-loc-sep { color: var(--border); }
.event-loc-addr { color: var(--muted); }
/* Event detail badges — the same facts the public calendar card shows on its
   flip side, as a chip row grouped with the other logistics above the blurb.
   The accent dot echoes that card's bulleted list so both surfaces match. */
.event-details { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 11px; }
.event-detail-chip {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--fs-meta); font-weight: 600; letter-spacing: 0.01em; white-space: nowrap;
  padding: 4px 11px; border-radius: 999px;
  background: var(--surface-2); border: 1px solid var(--border); color: var(--text);
}
.event-detail-chip::before {
  content: ''; flex: 0 0 auto; width: 5px; height: 5px;
  border-radius: 50%; background: var(--accent);
}
.event-date-badge {
  flex: 0 0 auto; width: 54px; text-align: center; line-height: 1.1;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 10px; padding: 7px 0;
}
.event-date-badge .edb-mon { display: block; font-size: var(--fs-micro); font-weight: 700; letter-spacing: .06em; color: var(--accent-text); }
.event-date-badge .edb-day { display: block; font-size: var(--fs-head); font-weight: 700; color: var(--text); }
.event-date-badge.past { opacity: .55; }
.event-date-badge.past .edb-mon { color: var(--muted); }

/* Status pills */
.ev-pill { font-size: var(--fs-micro); font-weight: 700; letter-spacing: .03em; text-transform: uppercase; padding: 3px 9px; border-radius: 999px; white-space: nowrap; }
.ev-pill.today { background: var(--accent); color: #1B1310; }
.ev-pill.soon { background: rgba(232,98,44,0.16); color: var(--accent-text-on-tint); }
.ev-pill.upcoming { background: var(--surface-2); color: var(--muted); border: 1px solid var(--border); }
.ev-pill.wrapped { background: rgba(79,169,124,0.15); color: var(--good-text-on-tint); }
.ev-pill.ended { background: var(--surface-2); color: var(--muted); }

/* Flyer thumbnail (image flyers) / download link (PDF flyers) */
.event-flyer { flex: 0 0 auto; display: flex; flex-direction: column; align-items: center; gap: 4px; }
.event-flyer-dl { display: inline-flex; align-items: center; gap: 3px; font-size: var(--fs-micro); color: var(--muted); text-decoration: none; }
.event-flyer-dl:hover { color: var(--text); }
.event-flyer-dl svg { flex: 0 0 auto; }
.event-flyer-thumb { position: relative; flex: 0 0 auto; display: block; width: 64px; height: 64px; border-radius: 8px; overflow: hidden; border: 1px solid var(--border); background: var(--surface-2); }
.event-flyer-thumb img, .event-flyer-thumb canvas { width: 100%; height: 100%; object-fit: cover; object-position: top; display: block; }
.event-flyer-thumb.pdf-thumb canvas { background: #fff; }
.flyer-badge { position: absolute; bottom: 3px; right: 3px; font-size: var(--fs-micro); font-weight: 700; letter-spacing: .04em; padding: 1px 4px; border-radius: 3px; background: rgba(0,0,0,0.6); color: #fff; pointer-events: none; }
.event-flyer-file { font-size: var(--fs-body); }

/* Action bar: reads as a footer (thin divider) with the RSVP toggle on the left
   and the quieter add-to-calendar utilities grouped on the right. */
.event-actions { display: flex; align-items: center; gap: 10px 12px; flex-wrap: wrap;
  margin-top: 16px; padding-top: 14px; border-top: 1px solid var(--border); }
/* Anchor "buttons" — download/open links that should read as buttons, not as
   text links. The background/border/radius all come from the bare `button`
   element selector, which an <a> never inherits, so without this an anchor
   carrying .btn-sm rendered as plain orange link text sitting next to real
   buttons. Mirrors `button` + `button.btn-sm`, including the .secondary
   variant (whose :hover must re-declare a background — see the note above
   button:hover). */
a.btn-sm {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  background: var(--accent); color: #000; border: 1px solid transparent;
  padding: 5px 10px; border-radius: 2px;
  font-family: 'JetBrains Mono', monospace; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.04em; font-size: var(--fs-micro);
  text-decoration: none; white-space: nowrap; cursor: pointer;
}
a.btn-sm:hover { background: #f0703a; color: #000; }
a.btn-sm.secondary { background: transparent; color: var(--text); border-color: var(--border); }
a.btn-sm.secondary:hover { background: transparent; border-color: var(--accent); color: var(--accent-text); }
a.btn-sm svg { flex: 0 0 auto; }
.event-actions-label { display: inline-flex; align-items: center; gap: 5px; font-size: var(--fs-meta); color: var(--muted); }
.event-actions-label svg { flex: 0 0 auto; opacity: 0.85; }

/* ---- Nudge banners ------------------------------------------------------ */
/* Every banner that heads the Events page — finish your profile, renewal
   season, membership lapsed, new to flying, find your people, the More-menu
   pointer — is the same four parts, in the same order, at every width:
     marker | message
            | buttons
            | dismiss link, hard right
   One shape, no exceptions. The earlier version put a lone button in a
   right-hand column beside the message and only dropped it below when there
   were three of them, which is why the new-flyer card read as a different
   species from the rest. It also aged badly on a wide screen: a single button
   pinned to the far right of a 1100px card sits most of a foot away from the
   sentence it belongs to, with nothing in between.
   The cost is real and worth naming — a button on its own row is taller than a
   button tucked beside the text. It's ~40-70px per banner, and in practice one
   or two of these show at a time, never all six. */
.nudge {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: 10px 14px;
}
/* Top-aligned with the first line — a marker centred against a four-line
   message floats in the middle of nothing. Fixed width, not `auto`: the icons
   are 18–21px wide, so an auto column started each banner's text at a slightly
   different x and the stack of them read as untidy. */
.nudge-icon {
  grid-column: 1; grid-row: 1; align-self: start;
  display: flex; align-items: center; justify-content: center;
  width: 21px; flex: 0 0 auto; line-height: 1;
  padding-top: 2px; color: var(--accent-text); font-size: var(--fs-panel);
}
.nudge-main {
  grid-column: 2; grid-row: 1; min-width: 0;
  font-size: var(--fs-body); line-height: 1.55; color: var(--text);
}
/* The buttons: a row under the message, left-aligned with it, using the card's
   full width. Whether there's one of them or three changes nothing but how many
   fit on the line. */
.nudge-actions { grid-column: 2; display: flex; flex-wrap: wrap; gap: 8px; }
/* One size for every action on every banner. These were three different sizes
   doing the same job — the profile prompt used .btn-sm, the new-flyer buttons
   their own override, and renew/connect the full-size default — which read as
   three different kinds of button rather than one family.
   This is the middle of the three. Deliberately not the smallest: the loudest
   thing these banners ever say is "your membership has lapsed", and putting
   that button in the smallest type on the site works against the one banner
   that most needs to be acted on. */
.nudge-actions button {
  width: auto; padding: 7px 14px;
  font-size: var(--fs-meta); letter-spacing: 0.05em;
}
/* The dismiss link gets its own row, last, pushed hard right — away from the
   buttons rather than beside them, because "get this off my screen" should not
   look like a second call to action. Right is also the one horizontal position
   that's free on every banner: the message and buttons both own the left, and
   the More-menu card has no button to sit under. */
.nudge-dismiss { grid-column: 2; justify-self: end; }
.nudge-later { color: var(--muted); font-size: var(--fs-meta); text-decoration: none; white-space: nowrap; }
.nudge-later:hover { color: var(--text); text-decoration: underline; }

/* Stacked one per line on a phone, so give them a common width: buttons of
   three different lengths down the left edge read as a ragged list, and the
   full width is a bigger thing to hit with a thumb. */
@media (max-width: 640px) {
  .nudge-actions { display: grid; grid-template-columns: 1fr; }
}

/* One-time "finish your profile to volunteer" banner atop the Events page. */
.profile-gate { border-left: 3px solid var(--accent); }

/* Quiet "did you know" pointer to the More menu's tabs, atop the Events page.
   The shortcut links this card used to carry are gone on purpose — see
   moreMenuHintHtml(). */
.more-menu-hint { border-left: 3px solid var(--accent); }

/* RSVP: solid orange = "still to do" (a clear call to action); green check =
   "confirmed, you're locked in" with a quiet Cancel to undo. Color carries the
   meaning so the two states can't be mistaken for each other. */
.rsvp-btn { width: auto; font-family: inherit; font-size: var(--fs-meta); font-weight: 600; letter-spacing: .02em;
  padding: 6px 18px; border-radius: 999px; cursor: pointer; white-space: nowrap;
  background: var(--accent); border: 1px solid var(--accent); color: #000;
  transition: filter .14s ease; }
.rsvp-btn:hover { filter: brightness(1.07); }
.rsvp-state { display: inline-flex; align-items: center; }
.rsvp-confirmed { display: inline-flex; align-items: center; gap: 6px; white-space: nowrap;
  font-family: inherit; font-size: var(--fs-meta); font-weight: 600; letter-spacing: .02em; text-transform: uppercase;
  padding: 6px 14px; border-radius: 999px;
  background: rgba(79,169,124,0.15); border: 1px solid rgba(79,169,124,0.55); color: var(--good-text-on-tint); }
.rsvp-cancel { margin-left: 10px; background: none; border: 0; padding: 2px 0; cursor: pointer;
  font-family: inherit; font-size: var(--fs-meta); font-weight: 500; letter-spacing: 0; text-transform: none;
  color: var(--muted); }
/* Muted controls brighten on hover rather than going orange: accent on this
   dark panel is only ~3.5:1, so orange-on-hover actually *lowers* legibility
   (the muted resting state is ~4.4:1). Bright text is ~10:1. */
.rsvp-cancel:hover { background: none; color: var(--text); text-decoration: underline; }

/* Add-to-calendar group: pushed to the right, quiet ghost buttons. */
.event-cal-group { display: inline-flex; align-items: center; gap: 8px; margin-left: auto; flex-wrap: wrap; }
.event-cal-group .cal-btn { display: inline-flex; align-items: center; gap: 5px; white-space: nowrap; }
/* On a phone the card body is narrow (the date badge takes a gutter), so the
   two pills read "Calendar" and "Share" and sit on one line together. */
.cal-btn-short { display: none; }
@media (max-width: 480px) {
  .event-cal-group { margin-left: 0; }
  .cal-btn-long { display: none; }
  .cal-btn-short { display: inline; }
}
.cal-btn { width: auto; font-family: inherit; font-size: var(--fs-meta); font-weight: 600; letter-spacing: .02em;
  padding: 5px 12px; border-radius: 999px; cursor: pointer; white-space: nowrap;
  background: transparent; border: 1px solid var(--border); color: var(--muted);
  transition: border-color .12s ease, color .12s ease; }
/* Mirrors .cal-addcal-btn:hover on the public site so the same control behaves
   the same on both surfaces. */
.cal-btn:hover { background: rgba(232, 98, 44, 0.1); border-color: var(--accent); color: var(--text); }
/* Matches the neutral "In N days" status pill (.ev-pill.upcoming). */
.rsvp-count {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: var(--fs-micro); font-weight: 700; letter-spacing: .03em; text-transform: uppercase;
  color: var(--muted); background: var(--surface-2); border: 1px solid var(--border);
  /* 2px rather than 3px vertically: this pill carries a 1px border and the
     .ev-pill badges beside it don't, so equal padding would leave it 2px
     taller. Trimming the padding makes the OUTER heights match. */
  padding: 2px 9px; border-radius: 999px; white-space: nowrap;
}
/* Sized to the text's own line box, so the pill matches the plain badges
   beside it exactly — a 13px icon made it 2px taller than its neighbours. */
.rsvp-count svg { flex: 0 0 auto; width: 11px; height: 11px; }
.rsvp-count { line-height: 1.18; }

/* Volunteer fill progress (card-level summary + per-task mini bar) */
.ev-vol-summary { margin-top: 14px; }
.ev-progress-head { display: flex; justify-content: space-between; font-size: var(--fs-body); color: var(--text); margin-bottom: 5px; }
.ev-progress-pct { color: var(--muted); font-variant-numeric: tabular-nums; }
.ev-progress { height: 7px; background: var(--surface-2); border-radius: 999px; overflow: hidden; }
.ev-progress-bar { height: 100%; background: var(--accent); border-radius: 999px; transition: width .25s ease; }
.ev-progress-bar.full { background: var(--good); }
.ev-progress.mini { height: 4px; }
@media (prefers-reduced-motion: reduce) { .ev-progress-bar { transition: none; } }

.task-list { display: flex; flex-direction: column; gap: 8px; }
.task-item {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 14px;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 6px;
  padding: 12px 14px;
}
.task-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 8px; }
.task-action { flex: 0 0 auto; align-self: center; }
.task-cap {
  font-size: var(--fs-micro); font-weight: 600; padding: 2px 9px; border-radius: 999px;
  background: rgba(79,169,124,0.15); color: var(--good-text-on-tint); white-space: nowrap;
}
.task-cap.full { background: rgba(217,83,79,0.18); color: var(--bad-text-on-tint); }
.vol-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.vol-chip {
  font-size: var(--fs-meta); padding: 3px 9px; border-radius: 999px;
  background: var(--surface); border: 1px solid var(--border); color: var(--text);
}
/* When a coordinator sees an email, stack name + email so both read clearly
   instead of running together on one line. */
.vol-chip.has-email {
  display: inline-flex; flex-direction: column; align-items: flex-start;
  gap: 1px; padding: 5px 11px; border-radius: 12px; line-height: 1.3;
}
.vol-chip .vol-name { font-weight: 600; }
.vol-chip .vol-email { color: var(--muted); text-decoration: none; margin-left: 4px; }
.vol-chip.has-email .vol-email { margin-left: 0; font-size: var(--fs-micro); }
.vol-chip .vol-email:hover { color: var(--text); }

/* Explanatory callout (e.g. "what's a grievance?") — a calm, bordered note. */
.info-callout {
  background: var(--surface-2); border: 1px solid var(--border); border-left: 3px solid var(--accent);
  border-radius: 6px; padding: 13px 16px; margin-bottom: 16px; font-size: var(--fs-body); line-height: 1.6;
}
.info-callout strong { color: var(--text); }
.info-callout p { color: var(--muted); margin: 6px 0 0; }

/* --- Renewal-season nudge banner --- */
.renewal-nudge {
  border-left: 3px solid var(--accent);
  background: linear-gradient(90deg, rgba(232,98,44,0.12), rgba(232,98,44,0.02));
}
.renewal-nudge-title {
  font-family: 'Barlow Condensed', sans-serif; font-size: var(--fs-head); font-weight: 600;
  color: var(--accent-text); letter-spacing: 0.01em;
}
.renewal-nudge-text { color: var(--text); font-size: var(--fs-body); line-height: 1.55; margin: 4px 0 0; max-width: 62ch; }
/* "Find your people" community nudge — green-toned + welcoming, distinct from the
   orange dues nudge. Shares the nudge layout classes for a consistent feel. */
.connect-nudge {
  border-left: 3px solid var(--good);
  background: linear-gradient(90deg, rgba(79,169,124,0.13), rgba(79,169,124,0.02));
}
/* The emoji markers carry no stroke weight, so they need a size or two more
   than the line-art icons to read as the same visual anchor. */
.connect-nudge .nudge-icon, .renewal-nudge .nudge-icon { font-size: var(--fs-head); }
.connect-nudge-title { font-family: 'Barlow Condensed', sans-serif; font-size: var(--fs-head); font-weight: 600; color: var(--good-text); letter-spacing: 0.01em; }
.connect-nudge-text { color: var(--text); font-size: var(--fs-body); line-height: 1.55; margin: 4px 0 0; max-width: 62ch; }
.connect-nudge-text strong { color: var(--good-text); font-weight: 600; }
.connect-nudge .nudge-actions button { background: var(--good); border-color: var(--good); color: #10221a; font-weight: 600; }
.connect-nudge .nudge-later:hover { color: var(--good-text); }
/* Must re-declare the background, not just a filter. `button:hover` sets solid
   orange at equal specificity and later in the file, so a hover rule that only
   tweaks brightness lets the green flip to orange under the pointer. Goes a
   shade darker instead, the same way the stat tiles do. */
.connect-nudge .nudge-actions button:hover {
  background: #43926B;
  border-color: #43926B;
  color: #10221a;
  filter: none;
}

/* Lapsed variant — firmer, red-toned to signal urgency. */
.lapsed-nudge {
  border-left: 3px solid var(--bad);
  background: linear-gradient(90deg, rgba(217,83,79,0.15), rgba(217,83,79,0.03));
}
.lapsed-nudge-title {
  font-family: 'Barlow Condensed', sans-serif; font-size: var(--fs-head); font-weight: 600;
  color: var(--bad-text); letter-spacing: 0.01em;
}
.lapsed-nudge .nudge-icon { color: var(--bad-text); font-size: var(--fs-head); }
.lapsed-nudge-btn { background: var(--bad); }
.lapsed-nudge-btn:hover { background: #c0433f; filter: none; }

/* --- Member Interests directory --- */
/* Categories sit two-up on desktop so the browse area reads as tidy lanes
   instead of one tall wall of pills; the last odd category spans full width.
   Collapses to a single column on narrow screens. */
/* Categories differ wildly in height — Styles carries a dozen tags, Propulsion
   three — and a two-column grid sizes every row to its tallest cell, so each
   short category left a slab of dead space beside the tall one next to it.
   CSS columns pack the blocks vertically instead: each category starts directly
   under the previous one and the browser balances the two columns. break-inside
   keeps a category from being torn across the column boundary, and the margin
   (not grid gap) now carries the vertical rhythm. */
.interest-browse { columns: 2; column-gap: 36px; margin-top: 12px; }
.interest-cat { break-inside: avoid; -webkit-column-break-inside: avoid; margin-bottom: 18px; }
/* A block at the top of a column keeps its margin in some engines, which would
   push the second column out of alignment with the first. */
.interest-cat:first-child { margin-top: 0; }
@media (max-width: 700px) { .interest-browse { columns: 1; } .interest-cat { margin-bottom: 14px; } }
.interest-cat-label {
  font-family: 'Barlow Condensed', sans-serif; text-transform: uppercase;
  letter-spacing: 0.05em; color: var(--muted); font-size: var(--fs-body);
  padding-bottom: 5px; border-bottom: 1px solid var(--border); margin-bottom: 9px;
}
.interest-tags { display: flex; flex-wrap: wrap; gap: 8px; }
/* Small round ✕ beside the results heading to clear the selected interest.
   Background pinned on hover so the global solid-orange button:hover doesn't
   bleed through. */
.int-clear {
  flex: 0 0 auto; width: 22px; height: 22px; padding: 0; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: transparent; border: 1px solid var(--border); color: var(--muted);
  font-size: var(--fs-body); line-height: 1; cursor: pointer;
}
.int-clear:hover { background: transparent; border-color: var(--accent); color: var(--accent-text); filter: none; }
.interest-tag {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: inherit; font-size: var(--fs-body); padding: 6px 12px; border-radius: 999px;
  background: var(--surface-2); border: 1px solid var(--border); color: var(--text);
  cursor: pointer; transition: border-color .15s, background .15s, color .15s;
  /* Buttons inherit white-space: nowrap, and the longest label ("Programming
     (Arduino / Flight Controllers)") is wider than a phone — enough on its own
     to make the whole page scroll sideways. Cap the chip at its container and
     let that one wrap; every shorter chip is unaffected. */
  max-width: 100%; white-space: normal; text-align: left;
}
/* Pin background + colour on hover so the global solid-orange button:hover
   doesn't bleed through; hover only warms the border. */
.interest-tag:hover { border-color: var(--accent); background: var(--surface-2); color: var(--text); }
.interest-tag .cnt { color: var(--muted); font-size: var(--fs-meta); }
.interest-tag.active,
.interest-tag.active:hover { background: rgba(232,98,44,0.15); border-color: var(--accent); color: var(--accent-text-on-tint); }
.interest-tag.active .cnt { color: var(--accent-text); }

/* My Interests checkboxes as bordered "chips" — a boxed background around each
   option so a long row is easy to scan and clearly separated. A checked chip
   warms to the accent; a disabled one (e.g. Instructor/Mentor before Advanced)
   dims. */
.int-opt {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: var(--fs-body); text-transform: none; letter-spacing: normal; color: var(--text); margin: 0;
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px;
  padding: 7px 12px; cursor: pointer; transition: border-color .12s, background .12s;
}
.int-opt:hover { border-color: var(--accent); }
.int-opt:has(input:checked) { border-color: var(--accent); background: rgba(232,98,44,0.12); }
.int-opt input { width: auto; margin: 0; flex: 0 0 auto; }
.int-opt.disabled { opacity: .45; cursor: not-allowed; }
.int-opt.disabled:hover { border-color: var(--border); }
.member-int-card { display: flex; align-items: flex-start; gap: 12px; padding: 11px 0; border-bottom: 1px solid var(--border); }
.member-int-card:last-child { border-bottom: none; }
.member-int-main { flex: 1 1 auto; min-width: 0; }
/* Small "put a face to a name" avatar: approved photo, else initials. */
.member-int-avatar { flex: 0 0 auto; width: 40px; height: 40px; border-radius: 50%; overflow: hidden;
  background: var(--accent); display: flex; align-items: center; justify-content: center; }
.member-int-avatar img { width: 100%; height: 100%; object-fit: cover; }
.member-int-avatar span { color: #1b1310; font-family: var(--font-display, inherit); font-weight: 700; font-size: var(--fs-title); }
.member-int-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.member-int-name { font-weight: 600; font-size: var(--fs-body); }
/* The shared interest, called out beside the name. */
.member-match {
  display: inline-flex; align-items: center; gap: 4px; font-size: var(--fs-meta); white-space: nowrap;
  padding: 2px 10px; border-radius: 999px;
  background: rgba(232,98,44,0.15); border: 1px solid var(--accent); color: var(--accent-text-on-tint);
}
/* Everything else, demoted to one quiet line so the card stays calm. */
.member-also { color: var(--muted); font-size: var(--fs-meta); line-height: 1.5; margin-top: 4px; }

/* Removable chips for a multi-select type-ahead (e.g. event coordinators). */
.chip-row { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 6px; }
.chip-row:empty { display: none; }
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--fs-body); padding: 4px 6px 4px 11px; border-radius: 999px;
  background: var(--surface); border: 1px solid var(--border); color: var(--text);
}
.chip-x {
  width: 18px; height: 18px; padding: 0; border: none; border-radius: 50%;
  background: transparent; color: var(--muted); font-size: var(--fs-title); line-height: 1; cursor: pointer;
}
/* A lighter red than --bad: still reads as "remove", but --bad itself is only
   ~3:1 on this panel, too faint for a 16px glyph. */
.chip-x:hover { background: transparent; color: #F2908C; }

.guest-list { display: flex; flex-direction: column; gap: 6px; margin-top: 8px; }
.guest-line {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  font-size: var(--fs-body); padding: 6px 0; border-bottom: 1px solid var(--border);
}
.guest-line:last-child { border-bottom: none; }
.guest-tags { display: flex; gap: 6px; flex-wrap: wrap; }

/* Location link: keeps the surrounding text colour (grey/accent per context),
   only revealing itself as a link on hover with an underline + pointer. */
.loc-link { color: inherit; text-decoration: none; cursor: pointer; }
.loc-link:hover, .loc-link:focus-visible { text-decoration: underline; }

/* Location map overlay — site-styled chrome around a keyless Google embed. */
.map-overlay {
  position: fixed; inset: 0; z-index: 300;
  background: rgba(10, 14, 20, 0.7); backdrop-filter: blur(3px);
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
.map-box {
  width: 100%; max-width: 640px; background: var(--surface);
  border: 1px solid var(--border); border-top: 3px solid var(--accent);
  border-radius: 8px; overflow: hidden; display: flex; flex-direction: column;
  box-shadow: 0 20px 50px rgba(0,0,0,0.5);
}
.map-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; padding: 16px 18px; }
.map-title { font-size: var(--fs-title); font-weight: 600; color: var(--text); }
.map-addr { font-size: var(--fs-body); color: var(--muted); margin-top: 2px; }
.map-close {
  flex: 0 0 auto; width: 32px; height: 32px; padding: 0; border-radius: 4px;
  background: transparent; border: none; color: var(--muted); font-size: var(--fs-display); line-height: 1; cursor: pointer;
}
.map-close:hover { background: transparent; color: var(--text); }
.map-body { height: 380px; background: var(--surface-2); }
/* Dark-mode the bright map embed so it sits with the app's theme. */
.map-body iframe { width: 100%; height: 100%; border: 0; display: block; filter: invert(0.92) hue-rotate(180deg) brightness(0.95); }
.map-loading { height: 100%; display: flex; align-items: center; justify-content: center; text-align: center; padding: 24px; color: var(--muted); font-size: var(--fs-body); }
.map-foot { padding: 12px 18px; border-top: 1px solid var(--border); font-size: var(--fs-body); }
.map-foot a { color: var(--accent-text); text-decoration: none; }
.map-foot a:hover { text-decoration: underline; }
@media (max-width: 560px) { .map-body { height: 300px; } }

/* --- Forms --- */
/* The fixed line-height is load-bearing: labels can carry a 14px round "?" hint,
   and with line-height normal that inline box grows the line. On iOS it grew it
   by 6px — a labelled-with-hint field sat 6px below the plain field beside it,
   so AMA expiration never lined up with AMA number. Desktop only showed 0.5px of
   it, which is why it reads as an iPad problem. 18px is taller than the 14px
   hint, so the hint now sits inside the line instead of stretching it. */
label { display: block; font-size: var(--fs-meta); line-height: 18px; color: var(--muted); margin: 10px 0 4px; text-transform: uppercase; letter-spacing: 0.04em; }
input, select, textarea {
  width: 100%;
  padding: 9px 10px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 4px;
  color: var(--text);
  font-family: 'Inter', sans-serif;
  font-size: var(--fs-body);
}
/* Force every single-line control to the same height regardless of type —
   native <select>, date and number inputs otherwise render slightly taller or
   shorter than plain text inputs. box-sizing:border-box (global) means this
   height is inclusive of the 9px padding + 1px border. Checkboxes/radios are
   excluded (they size themselves), and textareas keep their own multi-line
   height below. */
input:not([type="checkbox"]):not([type="radio"]), select {
  height: 38px;
  line-height: 1.2;
}
/* iOS renders a date input as a native control that sizes itself and ignores the
   height above — on an iPad the birthdate field came out about twice the height
   of every other field on the form. It also draws the value in a pseudo-element
   that centres itself, so that one value sat centred in a form where everything
   else is left-aligned. All 22 date fields in the app were affected, on every
   iPhone and iPad.
   The appearance reset is what makes iOS honour the height, and it's scoped to
   iOS: -webkit-touch-callout is the usual stand-in for "this is mobile Safari",
   and desktop browsers — where date fields are already the right height and
   would only lose their calendar button — never see it.
   Zeroing the pseudo-element's margin is safe everywhere, so it isn't scoped. */
input[type="date"]::-webkit-date-and-time-value { text-align: left; margin: 0; }
@supports (-webkit-touch-callout: none) {
  input[type="date"] { -webkit-appearance: none; appearance: none; }
}
input:focus, select:focus, textarea:focus {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
textarea { resize: vertical; min-height: 70px; }
/* Inline feedback under the ZIP field (city/state verification). */
.zip-note { display: block; margin-top: 4px; font-size: var(--fs-meta); line-height: 1.3; min-height: 0; }
.zip-note:empty { margin-top: 0; }
.zip-note.ok { color: var(--good, #3fae6b); }
.zip-note.warn { color: var(--warn, #c98a2b); }
.zip-note a { color: var(--accent-text); text-decoration: underline; }
/* Read-only "View" mode for a form — visually distinct from an editable one
   so it's obvious at a glance that changes won't stick. */
input:disabled, select:disabled, textarea:disabled {
  opacity: 0.65;
  cursor: not-allowed;
  background: var(--surface);
}

/* Buttons share the public site's .btn language (site.css): JetBrains Mono
   uppercase, square 2px corners, solid = throttle orange / ghost = thin
   border that goes orange on hover — so controls feel identical on either
   side of the login boundary. */
button {
  appearance: none;
  -webkit-appearance: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--accent);
  color: #000;
  border: 1px solid transparent;
  padding: 10px 20px;
  border-radius: 2px;
  font-family: 'JetBrains Mono', monospace;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  cursor: pointer;
  font-size: var(--fs-meta);
  white-space: nowrap;
  margin: 0;
}
/* Hover lightens whatever colour the button already is, rather than repainting
   it a fixed orange.
   The old rule hard-coded #f0703a, which meant every custom-coloured button had
   to re-declare its own background on hover or silently revert to orange under
   the pointer — a green "Find members like me" and the dashboard stat tiles both
   shipped broken that way, and the trap is invisible until someone hovers.
   Deriving the hover from the element's own background inverts that: a new
   coloured button gets a correct hover for free, and nothing has to defend
   itself. Scoping to unclassed buttons was the other option and is a trap of its
   own here — 228 of the 229 buttons in this app carry a class, so it would have
   removed the hover from all but one of them. */
button:hover { filter: brightness(1.1); }
/* A menu row in the "Change" dialog on a closed training request: a heading and
   a sentence saying what it does, stacked and left-aligned.
   Written as button.closed-action rather than .closed-action on purpose. The
   global `button` rule above is a centred, uppercase, nowrap monospace pill in
   accent orange, and a single-class selector ties with it on specificity only
   by luck of source order — an element+class selector wins outright. Every
   property it overrides is one the global rule sets. */
button.closed-action {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-start;
  gap: 3px;
  width: 100%;
  text-align: left;
  white-space: normal;          /* the sentence has to wrap */
  background: transparent;
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: 11px 13px;
  font-family: inherit;         /* sentences, not the monospace UI face */
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
  font-size: var(--fs-body);
}
button.closed-action:hover { border-color: var(--accent); background: var(--surface-2); filter: none; }
.closed-action-title { font-weight: 600; }
.closed-action-body { color: var(--muted); font-size: var(--fs-meta); line-height: 1.45; }

button.secondary {
  background: transparent;
  color: var(--text);
  border-color: var(--border);
}
button.secondary:hover { background: transparent; border-color: var(--accent); color: var(--accent-text); filter: none; }
/* Dark label, like every other button here. White on this red measured 3.96:1;
   the app's own button language is a dark label on a coloured fill, so the
   danger button was the odd one out as well as the illegible one. */
button.danger { background: var(--bad); color: #1B1310; }
button.danger:hover { background: #E2655F; filter: none; }
button:disabled {
  background: var(--border);
  color: var(--muted);
  border-color: transparent;
  cursor: not-allowed;
}
button:disabled:hover { background: var(--border); color: var(--muted); filter: none; }

/* Compact per-row table actions (Edit, Approve, Reject, Delete, etc.) — kept
   smaller than standalone panel buttons so a row of 2-3 actions doesn't
   dominate the row visually. */
button.btn-sm { padding: 5px 10px; font-size: var(--fs-micro); letter-spacing: 0.04em; }

/* Row action buttons stacked vertically, all the same width, instead of
   wrapping side by side once there are 2-3 of them (Approve/Reject/Edit). */
.action-stack { display: flex; flex-direction: column; gap: 4px; width: 76px; }
.action-stack button { width: 100%; }

.row { display: flex; gap: 12px; flex-wrap: wrap; }
/* The 160px floor keeps fields usable, but it's also a trap: an item that opts
   out of growing must use `flex: 0 0 auto`, never the `flex: 0` shorthand.
   `flex: 0` means `0 1 0%` — a zero basis — so the item collapses to this floor
   and any wider content (a long button label) spills past the panel edge. */
.row > * { flex: 1; min-width: 160px; }

/* --- Tables --- */
/* Scroll wrapper injected around every table (see initTableScrollGuard): keeps
   a wide table inside its panel and lets it scroll horizontally on narrow
   viewports instead of spilling past the cell background / widening the page. */
.table-scroll { max-width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch; }
table { width: 100%; border-collapse: collapse; font-size: var(--fs-body); }
th, td {
  text-align: left;
  padding: 8px 10px;
  border-bottom: 1px solid var(--border);
}
th {
  font-family: 'Barlow Condensed', sans-serif;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  font-size: var(--fs-meta);
}
td.num { font-family: 'JetBrains Mono', monospace; }

/* Expenses list: fixed column widths for stable alignment regardless of how
   long a category/subcategory/attributed-to value happens to be. */
.expense-table { table-layout: fixed; }
.expense-table td, .expense-table th { overflow: hidden; word-wrap: break-word; text-overflow: ellipsis; }
/* An amount is one thing; it never breaks across lines ("$1200.0 / 0"). */
.expense-table td.num { white-space: nowrap; font-variant-numeric: tabular-nums; }

/* Displayed (read-only) dates: centered, never wrapping to a second line.
   Scoped to its own class rather than reusing .num, which is shared with
   money columns elsewhere. */
.date-cell { text-align: center; white-space: nowrap; }
td.date-cell { font-family: 'JetBrains Mono', monospace; }

/* General-purpose centering for a header/cell pair, e.g. Waiver and Status
   on the Members list. */
.center-cell { text-align: center; }

/* Dashboard "AMA expiring" table: text within each cell stays left-justified,
   but the table itself is narrower than the panel and centered as a block
   (like .trend-table below) so the whole 4-column group reads as centered
   within the panel rather than stretched edge-to-edge. Extra horizontal cell
   padding gives clearer breathing room between columns. */
.evenly-spaced-table { table-layout: fixed; width: 92%; margin: 0 auto; }
.evenly-spaced-table th, .evenly-spaced-table td { text-align: left; padding: 8px 24px; }
.evenly-spaced-table td.date-cell, .evenly-spaced-table th.date-cell { text-align: center; }
/* Email tends to be the longest content in the row, so its column reads as
   most cramped against the next one — give that specific gap extra room. */
.evenly-spaced-table th:nth-child(3), .evenly-spaced-table td:nth-child(3) { padding-left: 116px; }

/* Matches .evenly-spaced-table's width/centering/column proportions exactly,
   so a control placed in its 4th cell lines up with the table's last
   (Expires) column beneath it. */
.evenly-spaced-table-header { width: 92%; margin: 0 auto; display: grid; grid-template-columns: repeat(4, 1fr); align-items: center; margin-bottom: 10px; }
/* On a phone the fixed four columns crushed the title into the width of one
   word — the heading spans three of them, and three quarters of 375px minus a
   full-width button leaves almost nothing. Stack instead. */
@media (max-width: 720px) {
  .evenly-spaced-table-header { width: 100%; grid-template-columns: 1fr; gap: 10px; }
  .evenly-spaced-table-header > * { grid-column: 1 / -1 !important; text-align: left !important; }
  .evenly-spaced-table-header button { width: 100%; }
}
/* The table beside it has the same problem: four columns of member data don't
   fit 375px, and an email address is what bursts it. Let it scroll on its own
   rather than squeezing every column past legibility. */
@media (max-width: 720px) {
  .evenly-spaced-table { display: block; overflow-x: auto; white-space: nowrap; }
}

.tt-note { color: var(--muted); font-size: var(--fs-meta); }
/* Four columns in 86% of a 375px screen left the label column ~63px wide, so
   "(covered by a parent's active membership)" came out one word per line and
   pushed past its cell. Full width, and the note drops under the label instead
   of competing with the numbers for the same row. */
@media (max-width: 620px) {
  .trend-table { width: 100% !important; max-width: none !important; }
  .trend-table .tt-note { display: block; margin-top: 2px; }
  /* Three single-digit counts don't need a quarter of the screen each. Capping
     them hands the width back to the label, which is the column carrying the
     sentence. */
  .trend-table th:not(:first-child), .trend-table td:not(:first-child) { width: 46px; }
  .trend-table th:first-child, .trend-table td:first-child { width: auto; }
}

/* Dashboard "Membership trend" table: centered under the (now fixed-size)
   chart above it, narrower than it, with equal-width columns so Year/Active
   members/YoY change are evenly spaced. */
.trend-table { width: 70%; max-width: 480px; margin: 0 auto; table-layout: fixed; }
.trend-table th, .trend-table td { text-align: center; padding: 8px 20px; }

/* Dashboard expense-breakdown tables (by category / by person): fixed,
   matching column widths so Count/Total/bar line up vertically between the
   two stacked panels no matter how long the row labels are, with extra
   horizontal breathing room between fields. */
.breakdown-table { table-layout: fixed; }
.breakdown-table col.bt-label { width: 42%; }
.breakdown-table col.bt-count { width: 16%; }
.breakdown-table col.bt-total { width: 20%; }
.breakdown-table col.bt-bar { width: 22%; }
.breakdown-table th, .breakdown-table td { padding: 8px 18px; }
.breakdown-table .num { text-align: right; }
.bt-bar-track { background: var(--border); border-radius: 3px; overflow: hidden; height: 8px; }
.bt-bar-fill { background: var(--accent); height: 100%; }

/* A heading carrying a count or status badge.
   vertical-align:middle was doing this, and it doesn't mean "centred": it aligns
   the box's middle to the parent's baseline plus half an x-height. Under a large
   condensed heading, next to an 11px badge, that lands the badge visibly high —
   which is what it looked like. Flex centres the badge on the text itself. */
.h-badge { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }

.badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 3px;
  font-size: var(--fs-micro);
  text-transform: uppercase;
  font-family: 'Barlow Condensed', sans-serif;
  font-weight: 700;
}
.badge.approved { background: rgba(79,169,124,0.2); color: var(--good-text-on-tint); }
.badge.pending { background: rgba(232,98,44,0.2); color: var(--accent-text-on-tint); }
.badge.rejected { background: rgba(217,83,79,0.2); color: var(--bad-text-on-tint); }
.badge.active { background: rgba(79,169,124,0.2); color: var(--good-text-on-tint); }
.badge.inactive { background: rgba(139,152,168,0.2); color: var(--muted); }
.badge.pending_renewal { background: rgba(232,98,44,0.2); color: var(--accent-text-on-tint); }

/* Open grievances: pulled to the top of the queue (server-ordered) and flagged
   here so an unresolved one further down the list never gets forgotten. */
tr.grievance-open > td { background: rgba(232,98,44,0.07); }
tr.grievance-open > td:first-child { box-shadow: inset 3px 0 0 var(--accent); }
.grievance-open-callout {
  display: flex; align-items: center; gap: 8px;
  margin: 0 0 14px; padding: 10px 12px; border-radius: 6px;
  background: rgba(232,98,44,0.12); border: 1px solid rgba(232,98,44,0.35);
  border-left: 4px solid var(--accent);
  color: var(--text); font-size: var(--fs-body); font-weight: 600;
}

.msg { padding: 10px 14px; border-radius: 4px; margin-bottom: 14px; font-size: var(--fs-body); }
.msg.error { background: rgba(217,83,79,0.18); color: #FFCFCB; border: 1px solid var(--bad); border-left: 3px solid var(--bad); font-weight: 600; }
.msg.ok { background: rgba(79,169,124,0.15); color: #A9E0C1; border: 1px solid var(--good); }
/* A caution, not a failure: something needs attention but nothing went wrong.
   Uses the accent rather than --bad so it reads as "look at this", not "error". */
.msg.warn { background: rgba(232,98,44,0.12); color: #F6C4AC; border: 1px solid var(--accent); border-left: 3px solid var(--accent); }
/* In progress, or context — "we're waiting on something", "here's what this
   screen is showing you". Deliberately none of the three signal colours: it is
   neither good news nor bad, and 29 of these say things like "verifying with
   the AMA" or "this event has ended".
   This class was used 29 times and never defined. That doesn't fail loudly — the
   base .msg still applies, so each one rendered as unboxed text sitting 14px
   in from its own heading, looking like a layout bug rather than a missing
   style. Which is exactly how it was reported. */
.msg.pending { background: var(--surface-2); color: var(--text); border: 1px solid var(--border); border-left: 3px solid var(--muted); }

/* Read-only "verified" display for values looked up automatically (e.g. AMA
   expiration) instead of a plain disabled input, so it reads as confirmed
   data rather than a greyed-out field. */
.verified-field {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 9px 10px;
  background: rgba(79,169,124,0.12);
  border: 1px solid rgba(79,169,124,0.4);
  border-radius: 4px;
  color: var(--good-text-on-tint);
  font-size: var(--fs-body);
  /* The same 38px every input and select is pinned to above. These sit directly
     beside those controls — AMA expiration next to AMA number, membership type
     next to birthdate — and a floor of 34px let this one be sized by its text
     while its neighbour was sized by a rule, so the two boxes came out different
     heights and their text sat on different lines. A floor rather than a fixed
     height, so a long value that wraps still grows instead of clipping;
     align-items: center above keeps the value centred either way. */
  min-height: 38px;
  box-sizing: border-box;
}
.verified-field.unverified {
  background: rgba(139,152,168,0.1);
  border-color: var(--border);
  color: var(--muted);
}
/* A previously-accepted waiver whose text has since changed — distinct from
   both the green "current" state and the grey "never signed" state. */
.verified-field.stale {
  background: rgba(232,98,44,0.12);
  border-color: rgba(232,98,44,0.4);
  color: var(--accent-text-on-tint);
}

/* Field Access card — the payoff for an active membership: a "field pass" that
   pairs where the field is with this year's gate code. Green ("access granted")
   accent, distinct from the app's orange, so it reads as a reward. */
.field-access {
  border-color: rgba(79,169,124,0.5);
  background: linear-gradient(155deg, rgba(79,169,124,0.10), rgba(79,169,124,0.015) 55%), var(--surface);
}
.field-access h2 { color: var(--text); font-size: var(--fs-title); margin: 0; letter-spacing: .01em; }
.fa-header { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 18px; }
.fa-title { display: flex; align-items: center; gap: 12px; min-width: 0; }
.fa-keyicon { flex: 0 0 auto; width: 40px; height: 40px; border-radius: 10px; display: inline-flex; align-items: center; justify-content: center;
  color: var(--good-text-on-tint); background: rgba(79,169,124,0.14); border: 1px solid rgba(79,169,124,0.4); }
.fa-sub { display: block; color: var(--muted); font-size: var(--fs-meta); margin-top: 2px; }
.fa-active { flex: 0 0 auto; display: inline-flex; align-items: center; gap: 7px; padding: 5px 12px; border-radius: 999px;
  font-size: var(--fs-micro); font-weight: 700; letter-spacing: .09em; text-transform: uppercase; color: var(--good-text-on-tint);
  background: rgba(79,169,124,0.13); border: 1px solid rgba(79,169,124,0.4); }
.fa-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--good); box-shadow: 0 0 7px rgba(79,169,124,0.9); }

.fa-body { display: flex; gap: 16px; flex-wrap: wrap; }
.fa-block { flex: 1 1 250px; min-width: 0; }
.fa-label { display: flex; align-items: center; gap: 6px; margin-bottom: 8px;
  font-size: var(--fs-micro); font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
.fa-label svg { flex: 0 0 auto; color: var(--good-text); opacity: .9; }

.fa-address { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap;
  min-height: 56px; box-sizing: border-box; padding: 12px 14px; border-radius: 9px;
  background: var(--surface-2); border: 1px solid var(--border); color: var(--text); font-size: var(--fs-title);
  text-decoration: none; transition: border-color .15s, background .15s; }
.fa-address:hover { border-color: rgba(79,169,124,0.55); background: rgba(79,169,124,0.06); }
.fa-directions { flex: 0 0 auto; color: var(--good-text); font-size: var(--fs-meta); font-weight: 600; white-space: nowrap; }

.fa-code { display: flex; align-items: center; justify-content: center; min-height: 56px; box-sizing: border-box;
  padding: 10px 14px; border-radius: 9px; background: rgba(9,13,18,0.5); border: 1px solid rgba(79,169,124,0.4);
  font-family: 'JetBrains Mono', monospace; font-size: var(--fs-display); font-weight: 600; letter-spacing: .06em;
  color: var(--good-text); text-shadow: 0 0 20px rgba(79,169,124,0.4); }
.fa-hint { color: var(--muted); font-size: var(--fs-meta); line-height: 1.45; margin-top: 8px; text-align: center; }

/* Green "put a face to a name" nudge on the profile-photo uploader. */
.photo-tip { display: flex; align-items: flex-start; gap: 9px; margin: 0 0 14px; padding: 10px 13px; border-radius: 8px;
  background: rgba(79,169,124,0.10); border: 1px solid rgba(79,169,124,0.35);
  color: var(--text); font-size: var(--fs-body); line-height: 1.5; }
.photo-tip svg { flex: 0 0 auto; margin-top: 1px; color: var(--good-text); }
.photo-tip strong { color: var(--good-text); font-weight: 600; }

.fa-note { display: flex; align-items: flex-start; gap: 8px; margin-top: 18px; padding-top: 14px;
  border-top: 1px solid var(--border); color: var(--muted); font-size: var(--fs-meta); line-height: 1.5; }
.fa-note svg { flex: 0 0 auto; margin-top: 2px; opacity: .8; }

/* Small "?" hint icon next to a label, with the explanation in a native
   tooltip on hover — keeps auto-lookup notes out of the way until asked for. */
.field-hint {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: rgba(143,160,179,0.25);
  color: var(--muted);
  font-size: var(--fs-micro);
  font-style: normal;
  text-transform: none;
  letter-spacing: normal;
  cursor: help;
  /* Centred on the text rather than sitting on the baseline, where its 14px box
     hangs below and pushes the line taller than the line-height above. */
  vertical-align: middle;
  margin-left: 4px;
  user-select: none;
}
.field-hint:hover, .field-hint.open { background: var(--accent); color: #1B1310; }
/* A finger needs more than 14px. Touch devices get a real tap target — which is
   the only way to open a hint at all when there's no hover. */
@media (pointer: coarse) {
  .field-hint { width: 22px; height: 22px; font-size: var(--fs-body); }
  /* And the label's line has to make room for it. At 18px the 22px tap target
     was the tallest thing on the line, so every label carrying a hint came out
     4px taller than one without — which is why AMA expiration sat 4px below AMA
     number on an iPad, and membership type below birthdate, while both looked
     fine on a desktop where the icon is only 14px. Sized here rather than
     shrinking the target: the finger comes first, and a slightly airier label is
     no loss on a touch screen. 24 rather than 22, the icon's own height: the icon
     is centred on the text's middle, which sits slightly above the middle of the
     line, so an exactly-22px line still left it overhanging by 0.7px. */
  label { line-height: 24px; }
}
/* The hint itself. Fixed, appended to <body> and positioned in JS, so it escapes
   any panel's overflow instead of being clipped by it. */
/* Hints are one sentence, except where a field earns a list — a paragraph of
   eleven wrapped lines is accurate and unreadable. showFieldHint renders each
   line of the hint text as its own block, so a blank line in that text becomes
   a small gap before the next line rather than a whole empty one: at this font
   an empty line is 18px, which in a 280px box reads as a hole. */
.field-hint-pop .fh-line { display: block; }
.field-hint-pop .fh-line.fh-break { margin-top: 7px; }
.field-hint-pop {
  position: fixed;
  z-index: 60;
  max-width: min(280px, calc(100vw - 16px));
  padding: 9px 11px;
  background: var(--surface-raised, #1e2733);
  border: 1px solid var(--accent);
  border-radius: 6px;
  box-shadow: 0 6px 20px rgba(0,0,0,0.45);
  color: var(--text);
  font-size: var(--fs-meta);
  line-height: 1.45;
  font-weight: 400;
  /* Asks the browser not to leave one word alone on the last line. Costs
     nothing where it isn't supported. */
  text-wrap: pretty;
  text-transform: none;
  letter-spacing: normal;
}

.auth-shell {
  max-width: 380px;
  margin: 10vh auto;
  padding: 28px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-top: 3px solid var(--accent);
  border-radius: 6px;
}
.auth-shell h1 { font-size: var(--fs-display); margin-bottom: 4px; }
.auth-shell .sub { color: var(--muted); font-size: var(--fs-body); margin-bottom: 18px; }
.link-row { margin-top: 14px; font-size: var(--fs-body); color: var(--muted); display: flex; justify-content: space-between; }

.receipt-thumb { max-width: 90px; max-height: 60px; border-radius: 4px; border: 1px solid var(--border); }
.receipt-preview { max-width: 220px; max-height: 220px; border-radius: 6px; border: 1px solid var(--border); display: block; }

.hidden { display: none !important; }

/* --- Shared site footer ---
   Same footer as the public pages (site.css `footer`), duplicated here with
   a scoped class because the app pages load style.css, not site.css, and
   pulling both in would collide on base element styles. Content/markup in
   login.html mirrors index.html's footer 1:1. */
.site-foot { margin-top: auto; border-top: 1px solid var(--border); padding: 48px 0 40px; }
.site-foot .foot-inner {
  max-width: 1180px; margin: 0 auto; box-sizing: border-box;
  padding: 0 clamp(20px, 5vw, 56px);
}
.site-foot .foot-grid {
  display: grid; grid-template-columns: 1.3fr 1fr 1fr;
  gap: 32px; padding-bottom: 32px; border-bottom: 1px solid var(--border);
}
@media (max-width: 700px) { .site-foot .foot-grid { grid-template-columns: 1fr; } }
.site-foot h5 {
  font-family: 'JetBrains Mono', monospace; font-size: var(--fs-micro); letter-spacing: 0.1em;
  text-transform: uppercase; color: var(--muted); margin: 0 0 14px;
}
.site-foot p { color: var(--muted); font-size: var(--fs-body); line-height: 1.65; margin: 0; max-width: 36ch; }
.site-foot .foot-links { display: flex; flex-direction: column; gap: 10px; }
.site-foot .foot-links a { font-size: var(--fs-body); color: var(--muted); text-decoration: none; }
.site-foot .foot-links a:hover { color: var(--text); }
.site-foot .bottom {
  display: flex; justify-content: space-between; align-items: center;
  padding-top: 24px; flex-wrap: wrap; gap: 10px;
}
.site-foot .bottom span { font-family: 'JetBrains Mono', monospace; font-size: var(--fs-micro); color: var(--muted); }

/* Search-as-you-type suggestion list (e.g. the grievance form's "Regarding"
   field) — a dropdown panel anchored under its text input. */
.autocomplete-suggestions {
  position: absolute;
  z-index: 20;
  top: 100%;
  left: 0;
  right: 0;
  margin-top: 2px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 4px;
  max-height: 220px;
  overflow-y: auto;
  box-shadow: 0 6px 16px rgba(0,0,0,0.35);
}
.autocomplete-option { padding: 8px 12px; font-size: var(--fs-body); cursor: pointer; }
.autocomplete-option:hover { background: rgba(232,98,44,0.15); }
.autocomplete-option.secondary { color: var(--muted); border-top: 1px solid var(--border); }

/* Full-screen overlay modal (currently used for the click-through waiver
   review/acceptance flow). */
.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.65);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  z-index: 1000;
}
.modal-panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-top: 3px solid var(--accent);
  border-radius: 6px;
  width: 100%;
  max-width: 580px;
  /* dvh tracks the VISIBLE viewport. On iOS Safari, vh means the viewport with
     the browser chrome hidden, so an 88vh panel extends behind the address bar
     and toolbar — the bottom of a long overlay sits off-screen with no way to
     reach it. vh first as the fallback for browsers without dvh. */
  max-height: 88vh;
  max-height: 88dvh;
  padding: 24px;
  display: flex;
  flex-direction: column;
  /* Scrolls by default. This was `hidden`, which silently clipped any overlay
     taller than the screen — the file detail for a multi-part project was
     unusable on a phone because its buttons were below the cut. Panels with
     their own inner scroller (.modal-scroll-text) are unaffected: their content
     is already capped, so this never engages for them. */
  overflow-y: auto;
}
.modal-scroll-text {
  white-space: pre-wrap;
  font-size: var(--fs-body);
  line-height: 1.6;
  color: var(--text);
  overflow-y: auto;
  flex: 1;
  min-height: 160px;
  max-height: 45vh;
  padding: 14px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: 4px;
  margin: 14px 0;
}
/* Handbook-review variant: holds rendered Markdown (not preformatted text), so
   let it wrap normally and give each section a clear divider + heading. */
.modal-scroll-text.handbook-doc { white-space: normal; }
.hb-agree-sec + .hb-agree-sec { margin-top: 22px; padding-top: 18px; border-top: 1px solid var(--border); }
.hb-agree-sec-title {
  font-family: 'Barlow Condensed', sans-serif; font-size: var(--fs-head); font-weight: 700;
  letter-spacing: .02em; text-transform: uppercase; color: var(--accent-text); margin-bottom: 8px;
}

/* --- Club Handbook (member Club Rules + By-Laws) --- */
.handbook-toggle { display: flex; gap: 6px; margin: 6px 0 22px; flex-wrap: wrap; }
.hb-btn {
  font-size: var(--fs-body); font-weight: 600; padding: 7px 16px; border-radius: 999px; width: auto;
  background: var(--surface-2); border: 1px solid var(--border); color: var(--muted); cursor: pointer;
  transition: border-color .12s, color .12s, background .12s;
}
.hb-btn:hover { border-color: var(--accent); color: var(--text); }
.hb-btn.active { background: rgba(232,98,44,0.15); border-color: var(--accent); color: var(--accent-text-on-tint); }
.handbook-doc { max-width: 74ch; line-height: 1.7; }
.handbook-doc h3 { font-size: var(--fs-head); margin: 28px 0 8px; color: var(--text); }
.handbook-doc h4 { font-size: var(--fs-title); font-weight: 700; margin: 24px 0 7px; color: var(--text); }
.handbook-doc h3:first-child, .handbook-doc h4:first-child { margin-top: 0; }
.handbook-doc p { margin: 0 0 12px; color: var(--muted); }
.handbook-doc ul { margin: 0 0 16px; padding-left: 20px; }
.handbook-doc li { margin: 0 0 7px; color: var(--muted); line-height: 1.6; }
.handbook-doc strong { color: var(--text); font-weight: 600; }
.handbook-doc a { color: var(--accent-text); text-decoration: none; cursor: pointer; }
.handbook-doc a:hover { text-decoration: underline; }

/* Profile photo preview + directory opt-in checkboxes */
.pf-photo { width: 120px; height: 120px; border-radius: 10px; overflow: hidden; flex: 0 0 auto;
  background: var(--surface-raised, #1e2733); border: 1px solid var(--border);
  display: flex; align-items: center; justify-content: center; }
.pf-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pf-photo span { color: var(--muted); font-size: var(--fs-meta); }
.dir-check { display: flex; align-items: center; gap: 8px; margin: 8px 0; color: var(--text); font-size: var(--fs-body); cursor: pointer; }
.dir-check input { width: auto; margin: 0; }

/* Member directory sliders: an on/off switch left, label text right, a
   hairline between rows. Hiding from the directory and showing a detail on
   your card are mutually exclusive, kept in sync both directions in JS (see
   onDirHiddenChange/onDirDetailChange) rather than disabling anything here. */
.dir-switch {
  display: flex; align-items: center; justify-content: flex-start;
  gap: 10px; padding: 5px 0; border-top: 1px solid var(--border);
  color: var(--text); font-size: var(--fs-body); cursor: pointer;
}
.dir-switch:first-of-type { border-top: none; padding-top: 0; }
.dir-switch input[type="checkbox"] {
  appearance: none; -webkit-appearance: none;
  width: 38px; height: 22px; min-width: 38px;
  background: var(--border); border-radius: 22px;
  position: relative; margin: 0; cursor: pointer;
  transition: background 0.15s ease;
}
.dir-switch input[type="checkbox"]::after {
  content: ""; position: absolute; top: 2px; left: 2px;
  width: 18px; height: 18px; border-radius: 50%;
  background: #fff; transition: left 0.15s ease;
}
.dir-switch input[type="checkbox"]:checked { background: var(--accent); }
.dir-switch input[type="checkbox"]:checked::after { left: 18px; }
.dir-switch input[type="checkbox"]:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Member directory + content-review card grids.
   grid-auto-rows:1fr makes every row (not just siblings in one row) the same
   height, so all cards match and the flip buttons line up across the whole
   grid. Cards are flex columns so that button can pin to the card's bottom. */
/* Tracks are wide enough for the "Emergency contact" pill to sit on one line —
   a narrower track would let the pill's min-content force a card wider than its
   own column and shunt its neighbours sideways. min-width:0 below is the belt to
   that braces, so a long email can't do the same thing. */
.dir-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 16px; grid-auto-rows: 1fr; }
.dir-card { display: flex; flex-direction: column; min-width: 0; background: var(--surface-raised, #1e2733); border: 1px solid var(--border); border-radius: 10px; padding: 18px 14px; text-align: center;
  transition: border-color .16s ease, box-shadow .16s ease; }
/* The accent's job on this page, and its only job: marking what ACTS. Hover warms
   the edge (you're pointing at this, and some cards flip); the emergency phone
   button on the back is the one thing you'd tap. Everything at rest — rings,
   roles, contact details — stays neutral, because a page of faces already has all
   the colour it needs and orange chrome only competes with it. Hover is also
   transient and one card at a time, so it never accumulates into a wall.
   No transform: these sit in a 3D flip context, where moving a face fights the
   rotation. */
.dir-card:hover { border-color: rgba(232, 98, 44, 0.45); box-shadow: 0 2px 14px rgba(0, 0, 0, 0.25); }
/* Fills the space above the pill so the pill lands on the card's bottom edge. */
.dir-card-body { flex: 1 0 auto; min-width: 0; }
/* Let a flip card fill its (uniform) grid row so both faces are card-height. */
.dir-grid .flip-card-inner { height: 100%; }
/* Avatars carry a ring rather than a hard edge: the outer band matches the card
   so the ring reads as a gap around the image, not a border stuck to it.
   Initials sit in a quiet surface tone ON PURPOSE. When most of the club has no
   photo yet, painting every placeholder in the brand accent turns the page into
   a wall of identical orange discs and drowns out the real photos. Muting them
   puts the colour where the content is, and makes a photo visibly worth adding. */
.dir-avatar { width: 104px; height: 104px; border-radius: 50%; overflow: hidden; margin: 0 auto 14px;
  background: var(--surface-2);
  display: flex; align-items: center; justify-content: center;
  box-shadow: 0 0 0 1px var(--border), 0 0 0 4px var(--surface-raised, #1e2733), 0 0 0 5px rgba(255, 255, 255, 0.04); }
.dir-avatar img { width: 100%; height: 100%; object-fit: cover; }
/* A photo gets a slightly brighter ring than an initials placeholder — the same
   "real content stands out" idea the accent ring used to carry, done with
   brightness instead of colour. The ring was orange back when one member in
   twenty had a photo and it marked the exception; once everyone has one it just
   painted every card orange and competed with the faces. The photos are the
   colour on this page; the chrome around them shouldn't argue. */
.dir-avatar.has-photo { box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.22), 0 0 0 4px var(--surface-raised, #1e2733), 0 0 0 5px rgba(255, 255, 255, 0.06); }
/* The one size deliberately off the type scale: these initials are sized to
   fill the avatar circle, so they track the avatar's dimensions rather than the
   text hierarchy. Change them with the circle, not with the scale. */
.dir-avatar span { color: #9DB0C4; font-family: var(--font-display, inherit); font-weight: 700; font-size: 34px; letter-spacing: 0.02em; }
.dir-name { color: var(--text); font-weight: 600; }
/* Officer roles read as a badge — set apart by weight, case and a darker ground
   rather than colour. An office is worth marking, but it isn't an action, and
   colouring it made every card shout the same thing at once. Bold uppercase on a
   recessed pill separates "who" from "what they do" without spending the accent.
   align-self is what actually keeps the pill hugging its text. A card with no
   emergency contact puts this straight inside .dir-card — a column flex
   container, which stretches its children full-width and overrides
   inline-block. Cards WITH one wrap their content in .dir-card-body, a plain
   block, where inline-block behaves. align-self fixes the flex case and is
   simply ignored in the block one, so both render identically. */
.dir-title { display: inline-block; align-self: center; margin-top: 7px; font-size: var(--fs-micro); font-weight: 700; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--text); background: rgba(0, 0, 0, 0.22);
  border: 1px solid var(--border); border-radius: 999px; padding: 3px 9px; }
.dir-contact { margin-top: 6px; font-size: var(--fs-meta); line-height: 1.5; overflow-wrap: anywhere; }
/* Contact details are reference, not a call to action: there are two per card and
   they're the least important thing on it. Muted until you reach for them. */
.dir-contact a { color: var(--muted); text-decoration: none; }
.dir-contact a:hover { color: var(--text); text-decoration: underline; }

/* Generic 3D card-flip primitives — reusable anywhere a card needs a front/back
   reveal (directory cards today, calendar cards later). Front and back share
   the same grid cell so the container auto-sizes to the taller face. */
.flip-card { perspective: 1200px; }
.flip-card-inner { position: relative; display: grid; transform-style: preserve-3d; transition: transform 0.6s cubic-bezier(0.4, 0.2, 0.2, 1); }
.flip-card.flipped .flip-card-inner { transform: rotateY(180deg); }
/* min-width:0 so a face's min-content (a nowrap pill, a long email) can never
   force it wider than the cell it sits in — that pushes neighbouring cards. */
.flip-card-face { grid-area: 1 / 1; min-width: 0; backface-visibility: hidden; -webkit-backface-visibility: hidden; }
.flip-card-back { transform: rotateY(180deg); }
@media (prefers-reduced-motion: reduce) {
  .flip-card-inner { transition: none; }
}

/* Front's "Emergency contact" and back's "Back" are the same pill pinned to the
   bottom of their face (margin-top:auto), so they land on the exact same spot —
   flipping a card back and forth never moves the pointer. */
.dir-ec-trigger, .dir-ec-back-btn {
  align-self: center; margin-top: 12px;
  display: inline-flex; align-items: center; justify-content: center; gap: 4px;
  font-size: var(--fs-micro); font-family: inherit; line-height: 1.45; white-space: nowrap;
  border: 1px solid var(--border); border-radius: 999px; padding: 3px 10px;
  background: transparent; color: var(--muted); cursor: pointer;
}
/* Subtle red wash + bright label, rather than red-on-orange. */
.dir-ec-trigger:hover, .dir-ec-back-btn:hover { background: rgba(224, 87, 76, 0.16); border-color: #e0574c; color: var(--text); }
/* Fills the space above the pill so the details stay optically centred. */
.dir-ec-back { flex: 1 0 auto; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; min-width: 0; }
/* Whose contact this is. The front leads with a face and a name; without this the
   back opened with a stranger's name and no owner, which is ambiguous the moment
   an emergency contact doesn't share the member's surname — exactly when you'd
   least want to guess. Doubles as the anchor that keeps the back feeling like
   the same card as the front. */
.dir-ec-for { display: flex; align-items: center; gap: 7px; max-width: 100%; margin-bottom: 8px; }
.dir-ec-mini { flex: 0 0 auto; width: 26px; height: 26px; border-radius: 50%; overflow: hidden;
  background: var(--surface-2); box-shadow: 0 0 0 1px var(--border);
  display: inline-flex; align-items: center; justify-content: center;
  font-size: var(--fs-micro); font-weight: 700; color: #9DB0C4; letter-spacing: 0.02em; }
.dir-ec-mini.has-photo { box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.22); }
.dir-ec-mini img { width: 100%; height: 100%; object-fit: cover; }
.dir-ec-for-name { font-size: var(--fs-meta); color: var(--muted); font-weight: 600; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Mirrors .dir-title's pill, in the emergency red rather than the brand accent. */
.dir-ec-label { display: inline-block; align-self: center; font-size: var(--fs-micro); text-transform: uppercase;
  letter-spacing: 0.06em; color: #e0574c; font-weight: 700;
  background: rgba(224, 87, 76, 0.12); border: 1px solid rgba(224, 87, 76, 0.4);
  border-radius: 999px; padding: 2px 8px; }
.dir-ec-name { color: var(--text); font-weight: 600; margin-top: 5px; }
.dir-ec-rel { font-size: var(--fs-meta); color: var(--muted); }
/* The one thing this card exists to do. It was a 13px text link — the smallest
   target on a face you'd be reading one-handed, outdoors, in a hurry. Now it's a
   proper button with a real tap target. */
.dir-ec-phone { display: inline-flex; align-items: center; gap: 6px; margin-top: 8px;
  font-size: var(--fs-body); font-weight: 600; color: var(--accent-text-on-tint); text-decoration: none; white-space: nowrap;
  background: rgba(232, 98, 44, 0.1); border: 1px solid rgba(232, 98, 44, 0.45);
  border-radius: 999px; padding: 6px 13px; transition: background .15s ease, color .15s ease; }
.dir-ec-phone:hover { background: var(--accent); color: #1b1310; text-decoration: none; }
.dir-ec-phone svg { flex: 0 0 auto; }

.review-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 18px; }
.review-card { text-align: center; }
.review-card .pf-photo { margin: 0 auto 8px; }
.review-note { color: var(--muted); font-size: var(--fs-meta); margin-top: 3px; }

/* Profile-photo crop/zoom stage */
.crop-stage { width: 100%; max-width: 300px; margin: 0 auto; aspect-ratio: 1 / 1;
  border-radius: 10px; overflow: hidden; background: #000; touch-action: none; }
.crop-stage canvas { width: 100%; height: 100%; display: block; cursor: grab; touch-action: none; }
.crop-stage canvas:active { cursor: grabbing; }

/* Member file library */
.file-chips { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
.file-chip { background: transparent; border: 1px solid var(--border); color: var(--muted); border-radius: 999px;
  padding: 5px 13px; font-size: var(--fs-body); cursor: pointer; font-family: inherit; }
.file-chip:hover { background: rgba(232, 98, 44, 0.1); color: var(--text); border-color: var(--accent); }
.file-chip.active { background: var(--accent); border-color: var(--accent); color: #1b1310; font-weight: 600; }
/* ~210px minimum fits four across at full width, dropping to 3/2/1 as the
   viewport narrows. The cards carry a preview and a short stack of facts, so
   they read fine at this width. */
.file-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 16px; }
/* Column, not row: the preview is a banner across the top now that most files
   actually have one to show (3D renders, PDF first pages). */
.file-card { display: flex; flex-direction: column; background: var(--surface-raised, #1e2733);
  border: 1px solid var(--border); border-radius: 10px; overflow: hidden; cursor: pointer;
  transition: border-color .15s ease, transform .15s ease; }
.file-card:hover, .file-card:focus-visible { border-color: var(--accent); transform: translateY(-2px); outline: none; }
/* Positioning context for the category / pending badges, which overlay the
   preview instead of taking a row in the flow — a conditional row is exactly
   what made cards in the same row disagree about where everything sat. */
.file-thumb-wrap { position: relative; }
/* A FIXED height, not an aspect-ratio: `1fr` grid columns land on fractional
   pixel widths, and an aspect-ratio turns that fraction into a 1-2px height
   difference between cards — enough to knock every row below it out of
   alignment. object-fit:cover handles the framing either way. */
.file-thumb { width: 100%; height: 140px; overflow: hidden; background: rgba(232, 98, 44, 0.08);
  border-bottom: 1px solid var(--border); display: flex; align-items: center; justify-content: center; }
.file-thumb.has-img { background: #0d1117; }
.file-thumb img { width: 100%; height: 100%; object-fit: cover; }
.file-ext { font-family: 'JetBrains Mono', monospace; font-size: var(--fs-head); font-weight: 700; color: var(--accent-text); letter-spacing: 0.06em; }
.file-thumb-wrap .file-cat { position: absolute; top: 8px; left: 8px; max-width: calc(100% - 16px);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.file-thumb-wrap .file-badge { position: absolute; top: 8px; right: 8px; }
.file-body { min-width: 0; flex: 1; display: flex; flex-direction: column; padding: 12px 14px 14px; }
/* min-height reserves both lines even for a one-line title, so the rows below
   start at the same y on every card. */
.file-title { color: var(--text); font-weight: 600; line-height: 1.35; overflow: hidden;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; min-height: 2.7em; }
.file-badge { display: inline-block; font-size: var(--fs-micro); text-transform: uppercase; letter-spacing: 0.05em;
  background: rgba(232, 98, 44, 0.15); color: var(--accent-text-on-tint); border: 1px solid var(--accent);
  border-radius: 4px; padding: 1px 5px; }
.file-meta { color: var(--muted); font-size: var(--fs-meta); margin-top: 3px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.file-stats { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px;
  color: var(--muted); font-size: var(--fs-micro); margin-top: 8px; }
.file-cat { background: rgba(13, 17, 23, 0.82); border: 1px solid var(--border); border-radius: 4px;
  padding: 2px 7px; color: var(--text); font-size: var(--fs-micro); }
.file-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
/* Card-only: one line, always — format+size pinned left, downloads pinned
   right, so the download count sits in the same spot on every card and can
   never wrap. The detail overlay keeps the wrapping defaults above, where
   there's room and nothing to line up against. */
.file-card .file-stats { flex-wrap: nowrap; justify-content: space-between; white-space: nowrap; }
.file-card .file-stats > span:first-child { overflow: hidden; text-overflow: ellipsis; }
/* Fixed one-row tag strip, reserved even when a file has no tags. The mask
   fades whatever runs past the edge, so a clipped tag reads as "there's more"
   rather than as a rendering bug. */
.file-card .file-tags { flex-wrap: nowrap; height: 21px; overflow: hidden;
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 22px), transparent);
  mask-image: linear-gradient(to right, #000 calc(100% - 22px), transparent); }
.file-card .file-tag { flex: 0 0 auto; }
.file-tag { font-size: var(--fs-micro); color: var(--muted); background: rgba(255, 255, 255, 0.05); border: 1px solid var(--border);
  border-radius: 999px; padding: 2px 9px; cursor: pointer; }
.file-tag:hover { color: var(--text); border-color: var(--muted); }
.file-tag.active { color: var(--accent-text); border-color: var(--accent); }
.file-tag.muted { cursor: default; opacity: .7; }
/* margin-top:auto pins the actions to the bottom so buttons line up across a
   row of cards whose titles/tags run to different heights. */
.file-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: auto; padding-top: 12px; align-items: center; }
.filedetail-head { display: flex; gap: 16px; align-items: flex-start; }
.filedetail-thumb { width: 160px; flex: 0 0 auto; border-radius: 8px; border: 1px solid var(--border); border-bottom: 1px solid var(--border); }
.filedetail-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-top: 20px;
  padding-top: 16px; border-top: 1px solid var(--border); }
/* Close is its own group: what it does — leave — isn't one of the actions you
   came here for, so it gets a line to itself under them, at every width.
   A zero-height 100% item is what forces the wrap; the buttons all keep the same
   left edge, since a lone button floating right under a left-aligned row is the
   skewed look this is avoiding. (An earlier flex:1 spacer right-aligned Close
   but fell apart as soon as the row wrapped, which an owner's
   Share/Edit/Merge/Remove always did.) */
.filedetail-break { flex: 0 0 100%; height: 0; }
.filedetail-close { margin-top: 6px; }
@media (max-width: 520px) {
  .filedetail-head { flex-direction: column; }
  .filedetail-thumb { width: 100%; }
  /* On a phone the buttons wrapped mid-row and the spacer landed on the second
     line, shoving Close to the right and leaving a ragged gap. Give the primary
     action its own full-width row and let the rest sit together underneath. */
  .filedetail-primary { flex: 1 0 100%; }
}
/* One row per chosen part in the share/edit form: rename, reorder, remove. */
.projform-part { display: flex; align-items: flex-start; gap: 10px; padding: 8px 0;
  border-bottom: 1px solid var(--border); }
.projform-part:last-child { border-bottom: 0; }
.projform-part input { margin: 0; }
.projform-part-btns { display: flex; gap: 4px; flex: 0 0 auto; padding-top: 2px; }
.projform-part-btns button { padding: 4px 8px; }

/* What's actually in a project — one row per part, each grabbable on its own. */
.filedetail-parts { margin-top: 18px; border-top: 1px solid var(--border); padding-top: 14px; }
.filedetail-parts-head { font-size: var(--fs-micro); text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--muted); margin-bottom: 10px; }
/* Rows can now be two or three lines tall, so align to the top rather than
   centring — otherwise the thumb and download button drift down beside a
   wrapped name. */
.filedetail-part { display: flex; align-items: flex-start; gap: 12px; padding: 10px 0;
  border-bottom: 1px solid var(--border); }
.filedetail-part:last-child { border-bottom: 0; }
.filedetail-part-thumb { width: 48px; height: 48px; flex: 0 0 auto; border-radius: 4px;
  border: 1px solid var(--border); }
.filedetail-part-thumb .file-ext { font-size: var(--fs-micro); }
/* Up to two lines, then ellipsis. These titles share long prefixes ("Thrust
   Vector Nozzle", "Thrust Vector Nozzle Extention Tube"), so a single truncated
   line would render several parts identical. */
.filedetail-part-name { color: var(--text); font-size: var(--fs-body); font-weight: 600;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
/* The filename must wrap ANYWHERE. It has no spaces and underscores aren't
   break opportunities, so without this the word is wider than its column and
   overflows — which is how it ended up rendering underneath the download icon. */
.filedetail-part-file { color: var(--muted); font-size: var(--fs-meta); margin-top: 2px;
  overflow-wrap: anywhere; word-break: break-word; }
/* Never let the row's control shrink or get overlapped by a long name. Covers
   <button> as well as <a>: the closed training requests carry an Edit note
   button here, and a rule written only for anchors left it with flex-shrink 1,
   free to be squeezed by a name with no spaces to wrap on. */
.filedetail-part > a.btn-sm,
.filedetail-part > button.btn-sm { flex: 0 0 auto; }
.review-files { display: flex; flex-direction: column; gap: 14px; }
.review-file { display: flex; gap: 14px; align-items: flex-start; }
/* The review queue keeps the original compact square thumb — it's a list, not a gallery. */
.review-file .file-thumb { width: 56px; height: 56px; aspect-ratio: auto; flex: 0 0 auto; border-radius: 8px; border: 1px solid var(--border); }
.review-file .file-ext { font-size: var(--fs-meta); letter-spacing: 0.03em; }

/* Event flyer preview overlay */
.flyer-backdrop { z-index: 1100; padding: 24px; }
.flyer-overlay { background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  width: min(92vw, 880px); max-height: 92vh; display: flex; flex-direction: column; overflow: hidden; }
.flyer-overlay-head { flex: 0 0 auto; display: flex; align-items: center; justify-content: space-between;
  gap: 16px; padding: 11px 12px 11px 18px; border-bottom: 1px solid var(--border); }
.flyer-overlay-title { font-weight: 600; color: var(--text); font-size: var(--fs-title); }
.flyer-overlay-actions { display: flex; gap: 8px; align-items: center; }
.flyer-overlay-body { flex: 1 1 auto; min-height: 0; background: #0f141a; overflow: auto; }
.flyer-overlay-body.is-img { display: flex; align-items: center; justify-content: center; padding: 14px; }
.flyer-overlay-body.is-img img { max-width: 100%; max-height: 84vh; display: block; }
.flyer-pdf { display: flex; flex-direction: column; align-items: center; gap: 14px; padding: 18px; }
.flyer-pdf-page { max-width: 100%; background: #fff; border-radius: 2px; box-shadow: 0 4px 16px rgba(0,0,0,0.5); }
.flyer-pdf-loading, .flyer-pdf-fallback { color: var(--muted); font-size: var(--fs-body); text-align: center; padding: 52px 20px; }
.handbook-updated { margin: 22px 0 0; font-size: var(--fs-meta); color: var(--muted); }

/* --- Flight checks (safety coordinator / field marshal, member site) --- */
.fc-row { border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px; margin-bottom: 10px; background: var(--surface-raised, #1e2733); }
.fc-row-head { display: flex; align-items: center; gap: 12px; }
/* min-width:0 so a long member name shrinks instead of shoving the button out
   past the row (see the .row note above — same flex min-content trap). */
.fc-row-who { flex: 1; min-width: 0; }
.fc-row-name { font-weight: 600; }
/* Competency badge + adult/youth stay glued together — the pair can wrap below a
   long name as a unit, but never splits across two lines. */
.fc-row-meta { white-space: nowrap; }
.fc-row-status { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 4px; }
.fc-editor { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border); }
.fc-editor-actions { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 12px; }

/* Mentor opt-in on My Profile: dimmed (not hidden) until the member marks
   themselves Intermediate or Advanced, so the offer is discoverable — a
   beginner can see it exists and what unlocks it. */
.mentor-opt.disabled { opacity: .5; }
.mentor-opt.disabled .dir-check { cursor: not-allowed; }
/* A switch waiting on something else — the emergency-contact one before there
   is a contact to share. Same treatment as the mentor checkbox above, which is
   the same idea: available, but not yet. */
.dir-switch.disabled { opacity: .5; }
.dir-switch.disabled input[type="checkbox"] { cursor: not-allowed; }
/* A member still waiting on their field safety check: they can't fly until
   someone signs them off, so the row earns a little urgency in the list. */
.fc-row.waiting { border-left: 3px solid var(--accent); }

/* --- Month picker (reports) ---
   A month grid we own, rather than <input type="month"> — that control's browser
   support is inconsistent enough that it can silently become a bare text box.
   The trigger mimics a select so it sits naturally beside the other inputs. */
.month-picker { position: relative; }
.month-picker-btn {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  width: 100%; text-align: left; font-size: var(--fs-body); font-weight: 400;
  padding: 9px 12px; border-radius: 6px; cursor: pointer;
  background: var(--surface-2); border: 1px solid var(--border); color: var(--text);
  text-transform: none; letter-spacing: normal;
}
.month-picker-btn:hover { border-color: var(--accent); background: var(--surface-2); }
.month-picker-caret { color: var(--muted); font-size: var(--fs-micro); flex: 0 0 auto; }

/* Fixed + body-mounted so a panel's overflow can't clip it. */
.month-pop {
  position: fixed; z-index: 60; width: 268px; padding: 12px;
  background: var(--surface-raised, #1e2733);
  border: 1px solid var(--accent); border-radius: 8px;
  box-shadow: 0 8px 26px rgba(0,0,0,0.5);
}
.month-pop-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.month-pop-year { font-family: var(--font-mono, monospace); font-size: var(--fs-title); font-weight: 700; color: var(--text); }
.month-nav {
  width: 30px; height: 30px; padding: 0; border-radius: 6px; cursor: pointer; font-size: var(--fs-title); line-height: 1;
  background: var(--surface-2); border: 1px solid var(--border); color: var(--text);
}
.month-nav:hover:not(:disabled) { border-color: var(--accent); color: var(--accent-text); background: var(--surface-2); }
.month-nav:disabled { opacity: .35; cursor: not-allowed; }
.month-pop-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; }
.month-cell {
  padding: 9px 0; border-radius: 6px; cursor: pointer; font-size: var(--fs-body); font-weight: 600; width: auto;
  background: var(--surface-2); border: 1px solid var(--border); color: var(--text);
  text-transform: none; letter-spacing: normal;
}
.month-cell:hover:not(:disabled) { border-color: var(--accent); color: var(--accent-text); background: var(--surface-2); }
.month-cell.active { background: var(--accent); border-color: var(--accent); color: #1B1310; }
.month-cell.future { opacity: .3; cursor: not-allowed; }

/* A handbook heading you were sent to by a link. Brief, and skipped entirely for
   anyone who's asked for reduced motion — the scroll already tells them where
   they are. */
@keyframes hb-flash-fade {
  0%, 55% { background: rgba(232,98,44,0.28); box-shadow: 0 0 0 6px rgba(232,98,44,0.28); }
  100%    { background: transparent; box-shadow: 0 0 0 6px transparent; }
}
/* Also used on the handbook block of the profile, when saving hands off to it.
   The scoped rule below keeps its tighter radius for text inside a document. */
.hb-flash { border-radius: 6px; animation: hb-flash-fade 2.2s ease-out 1; }
.handbook-doc .hb-flash { border-radius: 3px; animation: hb-flash-fade 2.2s ease-out 1; }
@media (prefers-reduced-motion: reduce) { .handbook-doc .hb-flash { animation: none; } }

/* --- Beginner banner (member Events page) ---
   Follows the .more-menu-hint shape (accent left rail, dismiss link at the end)
   so it reads as one of the same family of nudges rather than a new species. */
.beginner-hint { border-left: 3px solid var(--accent); }
.beginner-hint-title { font-weight: 700; font-size: var(--fs-title); color: var(--text); }
.beginner-hint-text { color: var(--muted); font-size: var(--fs-body); line-height: 1.55; margin: 4px 0 0; max-width: 62ch; }

/* --- Event badge picker (admin event form) ---------------------------------
   One panel replacing what used to be three places: a checkbox list on the
   event form, plus an "add option" form and a catalog table further down the
   page. The split mattered because the two zones below mean different things,
   and nothing used to say so: the top is THIS event's card, the bottom is a list
   shared by every event. Deleting or renaming in the bottom zone changes the
   club's badges everywhere — hence the different ground and the explicit note. */
.badge-picker { display: flex; flex-direction: column; gap: 12px; margin-top: 8px; }
.badge-zone { border: 1px solid var(--border); border-radius: 8px; padding: 12px 12px 10px; background: rgba(0, 0, 0, 0.12); }
/* The event's own card gets the accent rail; the club list stays neutral. */
.badge-zone:first-child { border-left: 3px solid var(--accent); }
.badge-zone-h { font-family: 'Barlow Condensed', sans-serif; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--text); font-size: var(--fs-body); margin-bottom: 8px; }
.badge-zone-note { color: var(--muted); text-transform: none; letter-spacing: normal; font-weight: normal;
  font-family: var(--font-body, inherit); font-size: var(--fs-meta); }
.badge-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 6px 0; border-bottom: 1px solid rgba(255, 255, 255, 0.04); }
.badge-row:last-child { border-bottom: none; }
/* Fixed width so the numbers form a column rather than shifting with each digit. */
.badge-pos { flex: 0 0 auto; width: 18px; text-align: right; font-family: var(--font-mono, monospace);
  font-size: var(--fs-meta); color: var(--muted); }
.badge-label { flex: 1 1 auto; min-width: 0; font-size: var(--fs-body); color: var(--text); overflow-wrap: anywhere; }
.badge-actions { flex: 0 0 auto; display: flex; gap: 4px; align-items: center; flex-wrap: wrap; }
.badge-actions button { width: auto; }
.badge-empty { color: var(--muted); font-size: var(--fs-meta); margin: 4px 0; }
.badge-add { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-top: 10px;
  padding-top: 10px; border-top: 1px solid var(--border); }
.badge-add input[type="text"], .badge-add > input { flex: 1 1 180px; min-width: 0; margin: 0; }
.badge-add-price { display: inline-flex; align-items: center; gap: 6px; margin: 0;
  font-size: var(--fs-meta); color: var(--muted); text-transform: none; letter-spacing: normal; white-space: nowrap; }
.badge-add-price input { width: auto; margin: 0; }
.badge-row-edit { gap: 8px; }
.badge-edit-input { flex: 1 1 160px; min-width: 0; margin: 0; }

/* Sits where a small action button would, for a row where that action isn't
   available. Reads as a status, not a control — no border, no pointer, so it
   never looks like something to click. */
.btn-sm-note { display: inline-flex; align-items: center; font-size: var(--fs-micro); color: var(--muted);
  white-space: nowrap; cursor: default; padding: 3px 2px; }

/* --- Flight-check list: a face next to the name -----------------------------
   An officer signing off who may fly alone should be able to match the person in
   front of them to the row. Reuses the interests-directory avatar markup, resized
   for the flight line and set on a neutral ground: an initials placeholder is the
   least informative thing in the row, and a column of orange discs would fight
   the "Needs a check" badge that IS trying to get attention.
   Note youth and covered children can't upload a photo at all (adults only, by
   policy), so their rows show initials permanently — by design, not by omission. */
.fc-row-head .member-int-avatar { width: 36px; height: 36px; background: var(--surface-2);
  box-shadow: 0 0 0 1px var(--border); }
.fc-row-head .member-int-avatar span { color: #9DB0C4; font-size: var(--fs-meta); }
/* The button carries both labels; CSS picks one. Below the breakpoint the short
   label frees the ~49px the avatar needs, so the name column keeps its width and
   rows stay exactly as tall as they are today. */
.fc-btn-short { display: none; }
@media (max-width: 560px) {
  .fc-btn-long { display: none; }
  .fc-btn-short { display: inline; }
}

/* ---- Classifieds ------------------------------------------------------- */
.listing-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; flex-wrap: wrap; }
.listing-filters { display: flex; gap: 8px; margin-top: 14px; flex-wrap: wrap; }
.listing-filters select, .listing-filters input { flex: 1 1 160px; min-width: 0; }

.listing-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 14px; }

.listing-card { border: 1px solid var(--border); border-radius: 8px; overflow: hidden; cursor: pointer;
  background: var(--panel); display: flex; flex-direction: column; transition: border-color .12s, transform .12s; }
.listing-card:hover, .listing-card:focus-visible { border-color: var(--accent); transform: translateY(-1px); }

/* Fixed height, not aspect-ratio: gear photos arrive in wildly different shapes
   and a ragged grid reads as broken. */
.listing-thumb { position: relative; height: 150px; background: var(--bg); display: flex; align-items: center; justify-content: center; }
.listing-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.listing-nophoto { color: var(--muted); font-size: var(--fs-meta); }
.listing-type { position: absolute; top: 6px; left: 6px; background: var(--accent); color: #1B1310;
  font-size: var(--fs-micro); letter-spacing: .04em; text-transform: uppercase; padding: 2px 6px; border-radius: 3px; }

.listing-body { padding: 10px 11px 12px; display: flex; flex-direction: column; gap: 4px; }
.listing-title { font-weight: 600; font-size: var(--fs-body); line-height: 1.3;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.listing-price { font-weight: 700; font-size: var(--fs-title); }
/* Every card puts the same facts in the same place, so the grid is scannable
   rather than something you have to read card by card. */
.listing-meta { display: flex; justify-content: space-between; gap: 8px; color: var(--muted);
  font-size: var(--fs-meta); white-space: nowrap; overflow: hidden; }

.listing-chip { display: inline-block; font-size: var(--fs-micro); padding: 2px 7px; border-radius: 999px;
  background: var(--bg); border: 1px solid var(--border); color: var(--muted); }
.listing-thumb .listing-chip { position: absolute; bottom: 6px; left: 6px; background: var(--panel); }
/* These were #b45309 and #b91c1c — colours picked for a white page, measuring
   2.7:1 and 2.1:1 here. Tokens instead, so they can't drift again. */
.listing-chip.warn { color: var(--accent-text); border-color: var(--accent); }
.listing-chip.danger { color: var(--bad-text); border-color: var(--bad); }

.listdetail-price { font-size: var(--fs-head); font-weight: 700; margin: 12px 0 0; }
.listdetail-photos { display: flex; gap: 8px; overflow-x: auto; margin-top: 14px; padding-bottom: 4px; }
.listdetail-photos img { height: 190px; border-radius: 6px; cursor: zoom-in; flex: 0 0 auto; }
.listdetail-owner { margin-top: 16px; border-top: 1px solid var(--border); padding-top: 14px; }
.listing-interest { border: 1px solid var(--border); border-radius: 6px; padding: 8px 10px; margin-bottom: 6px; }

.listform-photos { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
.listform-photo { position: relative; width: 84px; height: 84px; }
.listform-photo img { width: 100%; height: 100%; object-fit: cover; border-radius: 6px; border: 1px solid var(--border); }
.listform-photo button { position: absolute; top: -6px; right: -6px; width: 20px; height: 20px; padding: 0;
  border-radius: 50%; line-height: 1; font-size: var(--fs-body); }

@media (max-width: 520px) {
  .listing-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
  .listdetail-photos img { height: 140px; }
}

/* "3 new" on a seller's own card — the people who asked while they were away. */
.listing-new { position: absolute; top: 6px; right: 6px; background: var(--accent); color: #1B1310;
  font-size: var(--fs-micro); font-weight: 700; padding: 2px 7px; border-radius: 999px; }

/* The going-count became a <button> so it can open the names. It must still be
   the same pill it always was — .rsvp-count already styles it, and a class beats
   the bare `button` selector, so nothing here should reset background, border or
   padding (doing exactly that is what flattened it). Only the affordances. */
.rsvp-count-btn { font-family: inherit; cursor: pointer; }
.rsvp-count-btn:hover, .rsvp-count-btn:focus-visible {
  color: var(--text); border-color: var(--accent);
}

/* ---- Field conditions -------------------------------------------------- */
/* Verdict first, numbers second — the colour carries the answer at a glance. */
.conditions { border-left: 3px solid var(--border); }
.conditions-good { border-left-color: var(--good); }
.conditions-marginal { border-left-color: var(--accent); }
.conditions-poor { border-left-color: var(--bad); }
.conditions-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 14px; }
.conditions-verdict { font-weight: 700; font-size: var(--fs-title); }
/* The verdict is the one line on this panel a member actually reads, and it was
   the least legible thing on the site: 4.17 / 2.73 / 2.12 against the panel.
   Hardcoded light-theme colours that never got revisited when the palette did. */
.conditions-good .conditions-verdict { color: var(--good-text); }
.conditions-marginal .conditions-verdict { color: var(--accent-text); }
.conditions-poor .conditions-verdict { color: var(--bad-text); }
/* "for an advanced pilot", sitting with the verdict it qualifies. Reading size
   and muted, so a two-word verdict stays the thing the eye lands on — and
   inline-block so it wraps as a unit rather than breaking after "for". */
/* The audience tag rides the report's title now — same eyebrow voice,
   lighter weight, so "FIELD WEATHER REPORT · FOR AN ADVANCED PILOT" reads as
   one label whose second half is soft. It inherits the title's size and
   uppercase; on a phone the title simply wraps. */
.conditions-whose { font-weight: 400; }
.conditions-reason { color: var(--muted); font-size: var(--fs-body); margin-top: 2px; }
/* The "set your experience level" pointer shown when the verdict fell back to
   the middle band. A link, not a button — it's a footnote on the verdict, and
   it disappears forever once the profile has a level. */
.conditions-setlevel { color: var(--accent-text); text-decoration: none; }
.conditions-setlevel:hover { text-decoration: underline; }
/* Icon and temperature read as one unit — the sky and the number are the same
   answer — so they pair off at the right-hand end rather than drifting apart. */
.conditions-headright { display: flex; align-items: center; gap: 10px; flex: 0 0 auto; }
.conditions-temp { font-size: var(--fs-head); font-weight: 700; font-variant-numeric: tabular-nums; }
/* Tinted per condition rather than left grey: weather glyphs are one of the few
   places a reader decodes colour faster than shape. Kept desaturated so none of
   them shouts over the verdict, which is the thing that actually matters. */
.wx { width: 30px; height: 31px; flex: 0 0 auto; color: var(--muted); }
.wx-clear { color: #E8A33D; }
.wx-clear.wx-night { color: #93A3B8; }
.wx-cloudy { color: #93A3B8; }
.wx-rain, .wx-drizzle { color: #6FA1CE; }
.wx-snow { color: #A9C7E4; }
.wx-storm { color: #E8A33D; }
/* No room beside the verdict on a phone in portrait: the reason line already
   wraps to two lines at 375px and this would make it three. Landscape, tablets
   and desktop all have the width. */
@media (max-width: 600px) { .wx { display: none; } }
/* Exceptions to that, kept together so the next one is easy to spot: the
   welcome overlay's forecast (see .wc-weather above) and the event card's, both
   of which sit on their own row on a phone with room for the glyph. Without
   this the icon is there, sized, and invisible — the badge just loses its one
   piece of colour with nothing to say why. */
@media (max-width: 600px) { .event-fc .wx { display: flex; } }
/* Same treatment as the welcome overlay's forecast subhead: a label, not a
   heading. It has to sit under the verdict in the reading order even though it
   comes first on the page. */
.conditions-title {
  font-size: var(--fs-meta); color: var(--muted); font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.04em; margin: 0 0 8px;
}
.conditions-foot { color: var(--muted); font-size: var(--fs-micro); margin-top: 8px; }
/* "Good now, worse later" is the one outlook worth acting on today, so it comes
   out of the muted grey the rest of the caption sits in. Amber, not red — nothing
   has gone wrong; there's just a clock on it. */
.conditions-headsup { color: #d08a3a; }

/* ---- Dashboard stat drill-down ----------------------------------------- */
/* The panel is wider than a form modal and capped in height, because these are
   lists that can run to the whole roster. */
.statdetail-panel { max-width: 760px; width: 100%; display: flex; flex-direction: column; }
.statdetail-head { display: flex; align-items: flex-start; gap: 12px; }
.statdetail-head h2 { margin: 0; }
.statdetail-count {
  font-family: 'JetBrains Mono', monospace; font-variant-numeric: tabular-nums;
  font-size: var(--fs-panel); font-weight: 700; color: var(--accent-text);
}
.statdetail-lede { color: var(--muted); font-size: var(--fs-meta); margin: 6px 0 0; max-width: 68ch; }
.statdetail-tools { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-top: 14px; }
.statdetail-tools input { flex: 1 1 200px; min-width: 0; margin: 0; }
/* Scrolls inside the panel so the header, the action and the count stay put
   while a long roster moves under them. */
.statdetail-scroll { overflow-y: auto; margin-top: 12px; flex: 1 1 auto; min-height: 0; }
.statdetail-table { width: 100%; border-collapse: collapse; }
.statdetail-table th {
  position: sticky; top: 0; z-index: 1;
  background: var(--surface); text-align: left; padding: 8px 10px;
  font-family: 'JetBrains Mono', monospace; font-size: var(--fs-micro);
  text-transform: uppercase; letter-spacing: 0.08em; color: var(--muted);
  border-bottom: 1px solid var(--border);
}
.statdetail-table td { padding: 9px 10px; border-bottom: 1px solid var(--border); font-size: var(--fs-body); }
.statdetail-table tr:last-child td { border-bottom: 0; }
.statdetail-table tbody tr:hover { background: var(--surface-2); }
.statdetail-table .num { text-align: right; font-variant-numeric: tabular-nums; }
.statdetail-empty { color: var(--muted); font-size: var(--fs-body); padding: 18px 2px; }
@media (max-width: 620px) {
  /* Secondary columns fall away rather than squeezing the name to nothing. */
  .statdetail-table .col-optional { display: none; }
}

/* Brief highlight when a stat tile scrolls you to the panel that answers it. */
@keyframes panelFlash {
  0%   { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(232, 98, 44, 0.22); }
  100% { border-color: var(--border); box-shadow: none; }
}
.panel-flash { animation: panelFlash 1.4s ease-out; }
@media (prefers-reduced-motion: reduce) { .panel-flash { animation: none; } }

/* ---- The card body: now is the first hour ------------------------------- */
/* Sunrise, a sun track, and what's left of the day. Sits between the verdict
   and the row so the row reads as "the rest of this". */
.cond-day { display: flex; align-items: center; gap: 8px; margin: 10px 0 8px; }
.cond-k {
  font-family: 'JetBrains Mono', monospace; font-size: 10px; letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted); font-weight: 600; white-space: nowrap;
}
.cond-track { flex: 1 1 auto; height: 3px; background: #2F3C4D; border-radius: 2px; position: relative; }
.cond-track i { position: absolute; left: 0; top: 0; bottom: 0; background: #E9B54B; border-radius: 2px; }
.cond-track b {
  position: absolute; top: -4px; width: 11px; height: 11px; border-radius: 50%; background: #E9B54B;
  transform: translateX(-50%); box-shadow: 0 0 0 3px var(--surface);
}
/* One grid: the now cell takes a third, the hours share the rest equally.
   --hrs is the number of hour cells; with only two left in the day the now
   cell simply grows, which is the right thing for it to do. */
.cond-row {
  display: grid; grid-template-columns: minmax(230px, 4fr) repeat(var(--hrs, 8), minmax(0, 1fr));
  gap: 4px; align-items: stretch; position: relative;
}
.cond-row.has-tmrw { margin-top: 16px; }
.cond-row.cond-solo { grid-template-columns: minmax(0, 1fr); }
.cond-now {
  background: var(--surface-2); border: 1px solid var(--border); border-radius: 10px;
  padding: 10px 12px 8px; display: flex; flex-direction: column; gap: 4px; margin-right: 6px; min-width: 0;
}
.cond-solo .cond-now { flex-direction: row; flex-wrap: wrap; align-items: center; gap: 8px 22px; margin-right: 0; }
.cond-solo .cond-now .cond-k-now { flex-basis: 100%; }
.cond-solo .cond-now .rwy-click { flex: 1 1 220px; max-width: 430px; margin: 0; padding: 0; }
.cond-k-now { color: var(--accent-text); }
.conditions-good .cond-k-now { color: var(--good-text); }
.conditions-poor .cond-k-now { color: var(--bad-text); }
.cond-lines { display: flex; flex-direction: column; gap: 4px; }
.cond-big { display: flex; align-items: baseline; gap: 6px; line-height: 1; }
.cond-n {
  font-family: 'Barlow Condensed', 'Arial Narrow', sans-serif; font-weight: 700; font-size: 44px;
  font-variant-numeric: tabular-nums; letter-spacing: -.01em;
}
.cond-u { font-size: var(--fs-meta); color: var(--muted); }
.cond-u b { color: var(--text); font-weight: 600; }
.cond-line { font-size: var(--fs-meta); color: var(--muted); }
.cond-line b { color: var(--text); font-weight: 600; }
/* The runway is the floor of the now cell and as wide as the cell allows;
   fitRunwayBox trims the drawing's own margins so the strip really fills it. */
.cond-now .rwy-click { margin-top: auto; padding-top: 10px; }
.cond-now .rwy { width: 100%; max-width: none; align-self: stretch; }

/* An hour: time, arrow, verdict colour, speed, gust. A button so it can take
   focus and a tap; styled as a quiet cell rather than a control. */
.cond-hr {
  appearance: none; border: 0; font: inherit; color: inherit; background: rgba(255,255,255,.025);
  border-radius: 8px; padding: 8px 0 6px; display: flex; flex-direction: column; align-items: center;
  justify-content: space-between; gap: 4px; min-width: 0; cursor: default; position: relative;
}
.cond-hr:hover, .cond-hr.is-pinned { background: rgba(255,255,255,.09); box-shadow: inset 0 0 0 1px var(--border); }
.cond-hr:focus { outline: none; }
.cond-hr:focus-visible { box-shadow: 0 0 0 2px var(--accent); }
.cond-t { font-family: 'JetBrains Mono', monospace; font-size: 10px; color: var(--muted); white-space: nowrap; }
.cond-calm .cond-t { color: var(--text); font-weight: 700; }
.cond-arr { width: 14px; height: 14px; color: var(--muted); }
.cond-arr svg { display: block; width: 100%; height: 100%; }
.cond-blk { width: calc(100% - 10px); height: 10px; border-radius: 3px; background: var(--good); }
.cond-near .cond-blk { background: #d08a3a; }
.cond-no .cond-blk { background: var(--bad); }
/* The calmest window is outlined, not recoloured: it's still green, it's
   just the best of the green. */
.cond-calm .cond-blk { box-shadow: inset 0 0 0 1.5px rgba(237,239,242,.9); }
.cond-v { font-family: 'Barlow Condensed', 'Arial Narrow', sans-serif; font-weight: 700; font-size: 18px; line-height: 1; font-variant-numeric: tabular-nums; }
.cond-g { font-family: 'JetBrains Mono', monospace; font-size: 9px; color: var(--muted); opacity: .85; display: inline-flex; align-items: center; gap: 2px; }
/* Rain and snow marks sit in the gust's slot, in their own quiet colours. */
.cond-g svg { width: 10px; height: 10px; display: block; }
.cond-wet { opacity: 1; font-weight: 600; }
.cond-wet.cond-rain { color: #6FA1CE; }
.cond-wet.cond-snow { color: #A9C7E4; }
/* The hour's temperature: the second question after the wind, three characters. */
.cond-temp { font-family: 'JetBrains Mono', monospace; font-size: 10px; color: var(--muted); font-variant-numeric: tabular-nums; }
/* Where today's hours run out and tomorrow's begin. */
.cond-tmrw-first { margin-left: 6px; }
.cond-tmrw-first::before {
  content: 'tomorrow'; position: absolute; top: -14px; left: 2px;
  font-family: 'JetBrains Mono', monospace; font-size: 9px; letter-spacing: .06em; text-transform: uppercase; color: #E9B54B;
}
.cond-cap {
  margin-top: 8px; font-size: var(--fs-meta); color: var(--muted);
  display: flex; justify-content: space-between; gap: 6px 14px; flex-wrap: wrap;
}
.cond-cap b { color: var(--text); font-weight: 600; }
.cond-hint { white-space: nowrap; }
.cond-hint::before { content: 'Hover over an hour for its numbers'; }
@media (hover: none) { .cond-hint::before { content: 'Tap an hour for its numbers'; } }
/* One hour's numbers, above its cell. */
.cond-pop {
  position: absolute; z-index: 5; transform: translateX(-50%); background: var(--bg);
  border: 1px solid var(--border); border-radius: 8px; padding: 6px 10px; font-size: var(--fs-meta);
  white-space: nowrap; pointer-events: none; box-shadow: 0 6px 18px rgba(0,0,0,.35);
}
.cond-pop b { display: block; color: var(--text); }
.cond-pop span { color: var(--muted); font-size: var(--fs-micro); }
.cond-pop::after {
  content: ''; position: absolute; left: 50%; bottom: -5px; width: 8px; height: 8px; background: var(--bg);
  border-right: 1px solid var(--border); border-bottom: 1px solid var(--border); transform: translateX(-50%) rotate(45deg);
}

.rwy { height: auto; overflow: visible; }
.rwy-strip { fill: var(--surface-2); stroke: var(--border); stroke-width: 1; }
.rwy-centre { stroke: var(--muted); stroke-width: 1.4; stroke-dasharray: 5 5; opacity: .65; }
.rwy-num {
  fill: var(--muted); font-family: 'JetBrains Mono', monospace;
  font-size: 11px; font-weight: 700; letter-spacing: .06em;
}
/* The arrow takes the panel's own verdict colour, so the graphic agrees with the
   words beside it instead of introducing a fourth colour. */
.rwy-wind line { stroke: var(--accent); stroke-width: 3.4; stroke-linecap: round; }
.rwy-wind polygon { fill: var(--accent); }
.conditions-good .rwy-wind line { stroke: #16a34a; }
.conditions-good .rwy-wind polygon { fill: #16a34a; }
.conditions-marginal .rwy-wind line { stroke: #b45309; }
.conditions-marginal .rwy-wind polygon { fill: #b45309; }
.conditions-poor .rwy-wind line { stroke: #b91c1c; }
.conditions-poor .rwy-wind polygon { fill: #b91c1c; }

/* On a phone the now cell takes the whole width, the runway under its words,
   and the hours wrap beneath it as one row: the same design folded, not a
   different one. The arrow and gust go; the time, colour and speed stay. */
@media (max-width: 700px) {
  .cond-row { grid-template-columns: repeat(var(--hrs, 8), minmax(0, 1fr)); }
  .cond-now { grid-column: 1 / -1; margin: 0 0 6px; }
  .cond-hr { padding: 6px 0 4px; gap: 3px; }
  .cond-arr, .cond-g { display: none; }
  .cond-v { font-size: 16px; }
  .cond-temp { font-size: 9px; }
  .cond-t { font-size: 9px; }
  .cond-day .cond-k:last-child { white-space: normal; text-align: right; }
}

/* ---- Surveys ----
   The one page whose whole job is persuading a member to spend three minutes,
   so it borrows the public homepage's voice: mono uppercase eyebrows, Barlow
   Condensed display titles, safety-orange accents — the hangar-at-dusk system,
   turned up one notch. Type sizes stay on the app's scale; the polish is in
   hierarchy and materials, not in making anything shout. */

.survey-eyebrow {
  font-family: 'JetBrains Mono', monospace; font-size: var(--fs-micro);
  font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--accent); margin-bottom: 6px;
}
.survey-hero { padding: 6px 0 18px; border-bottom: 1px solid var(--border); margin-bottom: 4px; }
.survey-form .survey-hero, .survey-page .survey-hero { margin-top: 14px; }
.survey-page .survey-hero-title {
  font-size: clamp(24px, 4vw, 32px); color: var(--text);
  line-height: 1.08; margin: 0 0 8px; letter-spacing: 0.02em;
}
.survey-hero-sub { color: var(--muted); max-width: 62ch; margin: 0 0 12px; line-height: 1.55; }
.survey-thanks { border-bottom: none; }

.survey-meta-row { display: flex; flex-wrap: wrap; gap: 6px; }
.survey-meta-chip {
  font-family: 'JetBrains Mono', monospace; font-size: var(--fs-micro);
  letter-spacing: 0.05em; text-transform: uppercase; color: var(--muted);
  border: 1px solid var(--border); border-radius: 999px; padding: 4px 10px;
  background: var(--surface-2); white-space: nowrap;
}
.survey-meta-chip.done { color: var(--good); border-color: var(--good); }

/* The card a survey arrives as. A live, unanswered one carries the orange
   trim — the same "this needs you" cue the onboarding checklist uses. */
.survey-card {
  display: flex; gap: 16px; align-items: center; flex-wrap: wrap;
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 8px; padding: 18px 20px; margin-top: 14px;
  transition: border-color 0.15s ease, transform 0.15s ease;
}
.survey-card.live { border-left: 3px solid var(--accent); }
.survey-card:hover { border-color: var(--accent); transform: translateY(-1px); }
.survey-card-main { flex: 1; min-width: 260px; }
.survey-card-title {
  font-family: 'Barlow Condensed', sans-serif; font-weight: 700;
  letter-spacing: 0.02em; text-transform: uppercase;
  font-size: var(--fs-head); line-height: 1.1;
}
.survey-card-desc { margin: 6px 0 10px; color: var(--muted); max-width: 60ch; }
.survey-card .survey-meta-row { margin-top: 2px; }
/* A fixed-width action rail: every card reserves the same column on the
   right and every button fills it exactly, so all the buttons on the screen
   are the same size and share both edges — whether a card has one action or
   two stacked. Width sized to the longest label ("Change my answers") with
   room to breathe. */
.survey-card-actions { display: flex; flex-direction: column; gap: 8px; width: 210px; flex: none; }
.survey-card-actions button { width: 100%; }

.survey-empty {
  border: 1px dashed var(--border); border-radius: 8px;
  padding: 22px 20px; margin-top: 14px; color: var(--muted); max-width: 62ch;
}
.survey-empty p { margin: 0; line-height: 1.55; }

/* Questions as cards: one thought per surface, the eyebrow carrying the
   "2 of 3" thread so nobody wonders how much is left. */
.survey-q, .survey-result-q {
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 8px; padding: 16px 18px; margin-top: 14px;
}
.survey-q-prompt { font-weight: 600; font-size: 15px; line-height: 1.4; margin-bottom: 10px; }
.survey-optional { color: var(--muted); font-weight: normal; font-size: var(--fs-meta); }
.survey-note { color: var(--muted); font-size: var(--fs-meta); margin: 6px 0 0; }

.survey-choices { display: grid; gap: 8px; }
.survey-choice {
  display: flex; align-items: center; gap: 12px; padding: 12px 14px;
  border: 1px solid var(--border); border-radius: 6px; background: var(--surface);
  cursor: pointer; font-size: var(--fs-body); line-height: 1.35;
  transition: border-color 0.12s ease, background 0.12s ease;
}
.survey-choice:hover { border-color: var(--muted); }
.survey-choice:has(input:checked) {
  border-color: var(--accent); background: rgba(236, 123, 78, 0.09);
}
.survey-choice input { width: 20px; height: 20px; flex: none; accent-color: var(--accent); }
/* A choice is a sentence, not a form label: the global label rule's
   uppercase and tracking are undone here so answer options and audience
   picks read at the site's normal size and case. */
.survey-choice { text-transform: none; letter-spacing: 0; color: var(--text); font-weight: normal; margin: 0; }
/* The builder's and the newsletter's pickers: compact pills at the site's
   meta size, sentence case, quiet until ticked — the accent trim is the
   emphasis, the same way the rest of the site does it. */
.survey-audience .survey-choice, .survey-plain .survey-choice, .choice-inline {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 5px 10px 5px 8px; margin: 0;
  border: 1px solid var(--border); border-radius: 999px; background: var(--surface);
  font-size: var(--fs-meta); line-height: 1.3; color: var(--muted); cursor: pointer;
}
.survey-audience .survey-choice input, .survey-plain .survey-choice input, .choice-inline input { width: 15px; height: 15px; }
.survey-audience .survey-choice:has(input:checked), .survey-plain .survey-choice:has(input:checked), .choice-inline:has(input:checked) {
  color: var(--text); border-color: var(--accent); background: rgba(236, 123, 78, 0.09);
}
.survey-audience .survey-choice:has(input:disabled), .choice-inline:has(input:disabled) { opacity: 0.7; cursor: default; }
.survey-audience { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 6px; }
.survey-plain { display: flex; flex-wrap: wrap; gap: 8px; }

/* Standard-size buttons throughout — the survey pages get their emphasis from
   the type system and the accent trim, not from bigger controls than the rest
   of the site uses. */
.survey-submit-row { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-top: 20px; }

/* Results: the count reads like one of the dashboard gauges — big condensed
   numeral, mono label — so "31 answers" lands before any chart does. */
.survey-count-row { display: flex; align-items: center; gap: 12px; margin: 4px 0 12px; }
/* Same treatment as the dashboard gauges' big numbers — mono, tabular, capped
   at the same size — so "6 answered" here and "6 members expiring" there read
   as the same species of figure. */
.survey-count-num {
  font-family: 'JetBrains Mono', monospace; font-weight: 600;
  font-size: clamp(22px, 2.2vw, 28px); line-height: 1; color: var(--accent-text, var(--accent));
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.survey-count-of { font-size: 0.7em; color: var(--muted); }
.survey-count-label {
  font-family: 'JetBrains Mono', monospace; font-size: var(--fs-micro);
  letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); line-height: 1.6;
}

.survey-bars { margin-top: 10px; display: grid; gap: 8px; }
.survey-bar-row {
  display: grid; grid-template-columns: minmax(120px, 230px) 1fr 92px;
  gap: 12px; align-items: center;
}
.survey-bar-label { font-size: var(--fs-meta); overflow-wrap: anywhere; }
.survey-bar-track { background: var(--surface); border: 1px solid var(--border); border-radius: 5px; height: 22px; overflow: hidden; }
.survey-bar-fill { height: 100%; border-radius: 4px; background: rgba(236, 123, 78, 0.4); transition: width 0.5s ease; }
.survey-bar-fill.lead { background: var(--accent); }
.survey-bar-count {
  font-family: 'JetBrains Mono', monospace; font-size: var(--fs-meta);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.survey-bar-count span { color: var(--muted); }

.survey-pie-wrap { display: flex; gap: 24px; align-items: center; flex-wrap: wrap; margin-top: 10px; }
.survey-pie { width: 150px; height: 150px; flex: none; }
.survey-pie-legend { display: grid; gap: 6px; }
.survey-legend-row {
  display: flex; align-items: center; gap: 10px;
  font-family: 'JetBrains Mono', monospace; font-size: var(--fs-meta);
  font-variant-numeric: tabular-nums;
}
.survey-legend-row span { color: var(--muted); }
.survey-swatch { width: 13px; height: 13px; border-radius: 3px; flex: none; display: inline-block; }
.survey-text-answers { margin: 10px 0 0; padding-left: 20px; }
.survey-text-answers li { margin-top: 6px; line-height: 1.45; }

/* Builder */
.survey-picker { border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; margin-top: 8px; }
.survey-chip {
  display: inline-flex; align-items: center; gap: 6px; border: 1px solid var(--border);
  border-radius: 999px; padding: 3px 10px; margin: 2px 4px 2px 0;
  font-family: 'JetBrains Mono', monospace; font-size: var(--fs-micro); letter-spacing: 0.03em;
}
.survey-chip button {
  background: none; border: none; color: var(--muted); cursor: pointer;
  font-size: 1.1em; padding: 0 2px; line-height: 1;
}
.survey-builder-q {
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 8px; padding: 14px 16px; margin-top: 12px;
}
.survey-builder-q-head { display: flex; justify-content: space-between; align-items: center; gap: 10px; margin-bottom: 8px; }

/* Follow-up questions nest under their parent — indented with the accent
   spine, in the builder and on the member form alike, so the relationship is
   visible before a single word is read. */
.survey-q.survey-followup, .survey-result-q.survey-followup, .survey-builder-q.followup {
  margin-left: clamp(14px, 4vw, 34px); border-left: 3px solid var(--accent);
}
.survey-follow-note {
  font-family: 'JetBrains Mono', monospace; font-size: var(--fs-micro);
  letter-spacing: 0.05em; text-transform: uppercase; color: var(--muted);
  margin-right: auto;
}
.survey-follow-ctl {
  border: 1px dashed var(--border); border-radius: 6px;
  padding: 10px 12px; margin-top: 10px;
}
.survey-follow-sentence { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; font-size: var(--fs-meta); }
.survey-follow-sentence select { max-width: 100%; }

@media (max-width: 560px) {
  .survey-bar-row { grid-template-columns: 1fr; gap: 3px; }
  .survey-bar-count { text-align: right; }
  .survey-card-actions { width: 100%; }
}
@media (prefers-reduced-motion: reduce) {
  .survey-card, .survey-choice, .survey-bar-fill { transition: none; }
  .survey-card:hover { transform: none; }
}

/* ---- Rich text editors (Settings > Branding & Documents) ----
   Quill's snow theme dressed in the site's palette. Loaded only on the admin
   page, so members never download any of this. */
.rich-editor { background: var(--surface-2); }
.ql-toolbar.ql-snow {
  border: 1px solid var(--border); border-radius: 6px 6px 0 0;
  background: var(--surface-2); border-bottom: none;
}
.ql-container.ql-snow {
  border: 1px solid var(--border); border-radius: 0 0 6px 6px;
  background: var(--surface); color: var(--text);
  font-family: 'Inter', system-ui, sans-serif; font-size: var(--fs-body);
  min-height: 180px;
}
.ql-editor { min-height: 180px; line-height: 1.55; }
.ql-editor h2 { font-family: 'Barlow Condensed', sans-serif; letter-spacing: 0.02em; text-transform: uppercase; font-size: var(--fs-panel); margin: 10px 0 6px; }
.ql-editor a { color: var(--accent-text); }
/* Toolbar icons in the text color, not the muted grey — on the dark toolbar
   the grey icons were barely visible, so the formatting options looked absent. */
/* !important because Quill's own stylesheet is loaded on demand AFTER this
   one and sets the icons to #444 at the same specificity — ours lost. */
.ql-snow .ql-stroke { stroke: var(--text) !important; }
.ql-snow .ql-fill, .ql-snow .ql-stroke.ql-fill { fill: var(--text) !important; }
.ql-snow .ql-picker, .ql-snow .ql-picker-label { color: var(--text) !important; }
/* Readable, but not shouting: the icons rest at three-quarter strength and
   come up to full on hover or when active. */
.ql-snow.ql-toolbar button, .ql-snow .ql-picker-label { opacity: 0.72; }
.ql-snow.ql-toolbar button:hover, .ql-snow.ql-toolbar button.ql-active, .ql-snow .ql-picker-label:hover { opacity: 1; }
.ql-snow.ql-toolbar button:hover .ql-stroke, .ql-snow.ql-toolbar button.ql-active .ql-stroke,
.ql-snow.ql-toolbar button:focus .ql-stroke { stroke: var(--accent) !important; }
.ql-snow.ql-toolbar button:hover .ql-fill, .ql-snow.ql-toolbar button.ql-active .ql-fill,
.ql-snow.ql-toolbar button:focus .ql-fill { fill: var(--accent) !important; }
.ql-snow .ql-picker-options { background: var(--surface-2); border-color: var(--border) !important; }
.ql-snow.ql-toolbar button:hover .ql-stroke, .ql-snow.ql-toolbar button.ql-active .ql-stroke { stroke: var(--accent); }
.ql-snow.ql-toolbar button:hover .ql-fill, .ql-snow.ql-toolbar button.ql-active .ql-fill { fill: var(--accent); }
.ql-editor.ql-blank::before { color: var(--muted); font-style: normal; }

/* Placeholder chips: one solid token per fill-in — can't be misspelled,
   can't be half-deleted. */
.ql-ph {
  display: inline-block; background: rgba(236, 123, 78, 0.15);
  border: 1px solid var(--accent); border-radius: 10px;
  padding: 0 8px; margin: 0 1px; font-size: var(--fs-micro);
  font-family: 'JetBrains Mono', monospace; letter-spacing: 0.03em;
  color: var(--accent-text); white-space: nowrap; user-select: none; cursor: pointer;
}
.ql-ph:hover { background: rgba(236, 123, 78, 0.3); }
.ql-soft { display: inline; }

/* The admin survey list's actions: five fixed slots so the buttons form
   columns whatever each survey's state — the same never-ragged rule as the
   member cards' action rail. Publish and Close Now share slot four; a row
   without an action keeps an invisible spacer instead of shoving its
   neighbours out of line. */
.survey-actions-grid {
  display: grid; grid-template-columns: repeat(5, 92px); gap: 6px;
  justify-content: end; align-items: center;
}
.survey-actions-grid > * { width: 100%; }

/* Rating scale: five equal buttons, one tap. The radio hides; the box is the
   control. What the ends mean sits beneath them. */
.survey-rating { max-width: 460px; }
.survey-rating-row { display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; }
.survey-rating-btn { position: relative; cursor: pointer; }
.survey-rating-btn input { position: absolute; opacity: 0; pointer-events: none; }
.survey-rating-btn span {
  display: flex; align-items: center; justify-content: center;
  height: 52px; border: 1px solid var(--border); border-radius: 6px;
  background: var(--surface); font-family: 'JetBrains Mono', monospace;
  font-size: var(--fs-title); transition: border-color 0.12s ease, background 0.12s ease;
}
.survey-rating-btn:hover span { border-color: var(--muted); }
.survey-rating-btn input:checked + span {
  border-color: var(--accent); background: rgba(236, 123, 78, 0.15); color: var(--accent-text);
}
.survey-rating-btn input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: 2px; }
.survey-rating-ends {
  display: flex; justify-content: space-between; margin-top: 6px;
  font-size: var(--fs-meta); color: var(--muted);
}

/* ---- Grease It (the landing game behind the runway strip) ---- */
.rwy-click { cursor: pointer; }
.rwy-click:hover .rwy { filter: brightness(1.15); }
.greaseit-panel { max-width: 760px; }
#greaseit-canvas {
  width: 100%; max-width: 720px; border: 1px solid var(--border);
  border-radius: 6px; display: block; touch-action: none; cursor: pointer;
}
.greaseit-foot {
  /* nowrap, deliberately: a long verdict used to shove Fly Again / Close
     onto a second row, so the buttons lived in a different place depending
     on which game (and how chatty its verdict) — now the text wraps inside
     its own column and the buttons hold the same spot in every game. */
  display: flex; justify-content: space-between; align-items: center;
  gap: 10px; margin-top: 10px; flex-wrap: nowrap; min-height: 28px;
}
#greaseit-result { flex: 1; min-width: 0; }
.greaseit-foot > div:last-child { flex-shrink: 0; }
#greaseit-result b { color: var(--accent-text); }
.greaseit-table { width: 100%; margin-top: 6px; font-size: var(--fs-meta); }
.greaseit-table td { padding: 4px 8px 4px 0; border-bottom: 1px solid var(--border); }
.greaseit-table tr.you td { color: var(--accent-text); }
.greaseit-table .gi-score { font-family: 'JetBrains Mono', monospace; font-weight: 600; text-align: right; }
.greaseit-table .gi-wind, .greaseit-table .gi-date { color: var(--muted); font-size: var(--fs-micro); white-space: nowrap; }

/* The game tabs: one row, always. Four titles wrapped to a second line on a
   phone, which read as two unrelated rows of buttons — instead the row
   scrolls sideways (scrollbar hidden, the cut-off button is the affordance)
   and the active tab pulls itself into view. */
.gi-plane-row { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; margin: 0 0 8px; }
.gi-plane-label {
  font-family: 'JetBrains Mono', monospace; font-size: var(--fs-micro);
  font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--muted); margin-right: 4px;
}
/* On a phone the four ships must all fit one row: the text label yields
   (the buttons ARE the label) and padding tightens. */
@media (max-width: 480px) {
  .gi-plane-label { display: none; }
  .gi-plane-row .btn-sm { padding: 6px 10px; font-size: 11.5px; }
}
.greaseit-tabs-wrap { position: relative; }
.greaseit-tabs-more {
  position: absolute; top: 0; right: 0; bottom: 2px; width: 46px;
  display: none; align-items: center; justify-content: flex-end;
  background: linear-gradient(to right, rgba(35, 46, 61, 0), var(--surface) 70%);
  color: var(--accent-text); font: 700 20px 'JetBrains Mono', monospace;
  pointer-events: none; padding-right: 2px;
}
.greaseit-tabs {
  display: flex; gap: 6px; flex-wrap: nowrap; overflow-x: auto;
  scrollbar-width: none; -webkit-overflow-scrolling: touch; padding-bottom: 2px;
}
.greaseit-tabs::-webkit-scrollbar { display: none; }
.greaseit-tabs .btn-sm { white-space: nowrap; flex: 0 0 auto; }

/* ---- The Hangar on phones ---- */
/* A long-press must throttle, never start selecting the instructions. */
#greaseit-backdrop, .greaseit-panel {
  user-select: none; -webkit-user-select: none; -webkit-touch-callout: none;
}
#greaseit-backdrop { touch-action: none; }
/* Landscape phone: barely any height, so the chrome shrinks and the game
   takes what's left — instructions and the board yield to the flying (turn
   the phone upright to read the leaderboard). Height drives the canvas size;
   width follows to keep the picture honest. */
@media (max-height: 560px) {
  .greaseit-panel { padding: 10px 14px; }
  .greaseit-panel h2 { margin: 4px 0 2px !important; font-size: var(--fs-title); }
  #greaseit-howto { display: none; }
  #greaseit-board { display: none; }
  #greaseit-canvas { height: 56vh; height: 56dvh; width: auto; max-width: 100%; margin: 0 auto; }
  .greaseit-foot { margin-top: 6px; min-height: 24px; }
}

/* The import guide overlay: a document, not a form, so give its type room. */
.import-guide h2 { margin-top: 18px; }
.import-guide h3, .import-guide h4 { margin: 16px 0 6px; }
.import-guide p, .import-guide li { line-height: 1.5; }
.import-guide code { font-family: ui-monospace, Menlo, monospace; font-size: 0.92em; padding: 1px 5px; border-radius: 4px; background: rgba(127, 127, 127, 0.18); }
.import-guide table { margin: 8px 0 14px; }
.import-guide th, .import-guide td { vertical-align: top; }
.import-guide hr { border: 0; border-top: 1px solid rgba(127, 127, 127, 0.35); margin: 18px 0; }

/* A quiet inline spinner for "working on it" messages (import, dry run). */
.spinner { display: inline-block; width: 12px; height: 12px; margin-right: 6px; vertical-align: -1px; border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%; animation: spin 0.8s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .spinner { animation: none; border-right-color: currentColor; opacity: 0.5; } }

/* Fold-up panels (Reports → Finance): the heading is the handle, the caret
   says which way it will go, and a folded panel keeps only its heading. */
.panel.foldable > h2 { display: flex; align-items: center; justify-content: space-between; gap: 10px; user-select: none; }
.fold-toggle { background: none; border: 0; padding: 0 2px; margin: 0; color: var(--muted); font: inherit; line-height: 1; cursor: pointer; box-shadow: none; }
.fold-toggle:hover { color: var(--text); }
.fold-caret { display: inline-block; font-size: 1.1em; transition: transform 0.18s ease; }
.panel.collapsed .fold-caret { transform: rotate(-90deg); }
.panel.collapsed > .panel-body { display: none; }
.panel.collapsed > h2 { margin-bottom: 0; }
@media (prefers-reduced-motion: reduce) { .fold-caret { transition: none; } }

/* A quieter second line inside a gauge value (e.g. the reconciled part of the bank balance). */
.gauge .gauge-sub { font-family: 'JetBrains Mono', monospace; font-size: var(--fs-meta); font-weight: normal; color: var(--muted); margin-top: 2px; letter-spacing: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* The expense form in view mode: read-only fields shouldn't look broken, just quiet. */
.panel.view-only input:disabled, .panel.view-only select:disabled, .panel.view-only textarea:disabled { opacity: 1; color: var(--text); cursor: default; background: var(--surface); }

/* Newsletter editor: pictures placed in the body show at their sent size. */
#nl-quill { min-height: 260px; }
#nl-quill .ql-editor img { max-width: 100%; height: auto; display: block; }


/* ---- Money input: a fixed dollar sign, then the number ------------------ */
.money-field { position: relative; }
.money-field .money-sign {
  position: absolute; left: 12px; top: 50%; transform: translateY(-50%);
  color: var(--muted); font-variant-numeric: tabular-nums; pointer-events: none;
}
.money-field input { padding-left: 26px; font-variant-numeric: tabular-nums; }

/* ---- The expense filter: the control for the tables under it ------------ */
.filter-panel { background: var(--surface-2); }
.filter-panel-active { border-color: rgba(236, 123, 78, 0.45); }
.filter-note { margin: -4px 0 12px; }
.filtered-tag { vertical-align: middle; margin-left: 6px; }

/* ---- Square's card form ------------------------------------------------- */
/* Why the card box went white on 2026-09-14: the site declared
   `color-scheme: dark` (for Safari's date pickers), and a browser gives a
   cross-origin iframe an OPAQUE white backdrop when the embedding element's
   colour scheme differs from the embedded document's — Square's form is
   light. Declaring the iframe element light again keeps it transparent, so
   the dark wrapper below shows through as it always did.
   Square's SDK inserts that .sq-card-wrapper into OUR page and loads a
   stylesheet painting its container white; the fields inside the iframe are
   transparent, and the SDK ignores input.backgroundColor. So the dark box is
   ours to paint here. Same rules on every card box (dues, guests, public
   registration) so they never differ. !important: Square's sheet loads later. */
.sq-card-wrapper iframe { color-scheme: light; }
.sq-card-wrapper .sq-card-iframe-container {
  background-color: var(--surface-2, #2C3949) !important;
  border-color: var(--border, #33404F) !important;
  border-radius: 6px !important;
}
/* Focus and error are drawn by Square as a 2px ring on a ::before (blue and
   red by default); recolour the ring to the site's orange and red. */
.sq-card-wrapper.sq-focus .sq-card-iframe-container::before { border-color: var(--accent, #E8622C) !important; }
.sq-card-wrapper.sq-error .sq-card-iframe-container::before { border-color: var(--bad, #D9534F) !important; }
.sq-card-wrapper .sq-card-message { color: var(--muted, #92A3B5) !important; }
.sq-card-wrapper .sq-card-message-error { color: var(--bad-text, #E37E7B) !important; }
.sq-card-wrapper .sq-card-message-no-error::before { background-color: var(--muted, #92A3B5) !important; }
.sq-frame { max-width: 460px; }

/* ---- Expense category guide (overlay + printable) ------------------------ */
.cg-cat { margin-top: 14px; }
.cg-cat h3 { margin: 0 0 6px; padding-bottom: 4px; border-bottom: 1px solid var(--border); }
.cg-meta { font-family: 'JetBrains Mono', monospace; font-size: var(--fs-micro); color: var(--muted); font-weight: normal; letter-spacing: 0.02em; }
.cg-row { display: grid; grid-template-columns: 180px minmax(0, 1fr); gap: 6px 14px; padding: 6px 0; border-bottom: 1px dashed rgba(127, 127, 127, 0.25); align-items: start; }
.cg-sub { font-weight: 600; }
.cg-ex { font-size: var(--fs-meta); line-height: 1.5; }
.cg-payees { color: var(--muted); margin-top: 2px; }
@media (max-width: 560px) { .cg-row { grid-template-columns: 1fr; gap: 2px; } }
/* Print: only the guide, black on white, one clean sheet. */
@media print {
  body:has(#category-guide-backdrop) > *:not(#category-guide-backdrop) { display: none !important; }
  #category-guide-backdrop { position: static !important; background: #fff !important; padding: 0 !important; display: block !important; }
  #category-guide-backdrop .modal-panel { max-width: none !important; max-height: none !important; box-shadow: none !important; border: 0 !important; background: #fff !important; color: #000 !important; }
  #category-guide-backdrop .cg-body { overflow: visible !important; }
  #category-guide-backdrop .cg-buttons { display: none !important; }
  #category-guide-backdrop .cg-meta, #category-guide-backdrop .cg-payees, #category-guide-backdrop [style*="var(--muted)"] { color: #444 !important; }
  #category-guide-backdrop h2, #category-guide-backdrop h3 { color: #000 !important; }
  .cg-cat { break-inside: avoid; }
}
