/* ──────────────────────────────────────────────
   MonkeyGrade – Shared Styles
   Palette: sandstone orange + charcoal + off-white
   ────────────────────────────────────────────── */
:root {
  --primary:       #E8731A;
  --primary-dark:  #C85D0E;
  --primary-light: #FFF0E5;
  --dark:          #2B2D42;
  --mid:           #6B7280;
  --light:         #F7F4EF;
  --white:         #FFFFFF;
  --success:       #22C55E;
  --danger:        #EF4444;
  --border:        #DDD5C8;
  --shadow:        0 2px 10px rgba(0,0,0,.08);
  --radius:        12px;

  /* Dark "chrome" surface (header / side-nav / account menu / toast). Decoupled
     from --dark so dark mode can keep it dark while --dark flips to light text
     (#270). In light mode it follows the admin's --dark colour. */
  --chrome:        var(--dark);

  /* grade colours */
  --g1: #22C55E;   /* green  */
  --g2: #84CC16;   /* lime   */
  --g3: #EAB308;   /* yellow */
  --g4: #F97316;   /* orange */
  --g5: #EF4444;   /* red    */
  --g6: #B91C1C;   /* dark red */
  --g7: #7C3AED;   /* purple */

  /* ── 2026-07 design-standard tokens (epic #1410 W3) ──────────────────────
     Additive — nothing above is renamed or removed, so pages not touched in W3
     don't shift. New shared chrome uses the --r-*/--space-*/--night-* set;
     legacy --radius/--shadow stay as aliases. Values mirror the mobile design
     standard (docs/design/app-redesign-2026-07/design-system.md §1–3) so web +
     native night look like one brand. */

  /* Night skin — a first-class navy night (the party-room spike). Also the
     source of the --dk-* remap below, so web dark mode == the app's night. */
  --night-bg:      #232640;   /* app background (deep navy — LIGHTER than surface) */
  --night-surface: #191B2E;   /* card / sheet surface (recessed, darker than bg) */
  --night-high:    #2C3052;   /* raised surface / pressed / input fill */
  --night-stroke:  #33365A;   /* hairline / stroke on night */
  --chalk:         #F4F1EA;   /* primary text on night — warm off-white */
  --chalk-soft:    #B9B9CE;   /* secondary text on night */
  --brand-on-night:#F6904A;   /* brighter orange for AA text on navy */
  --online:        #35C46B;   /* presence / live / success on night */

  /* Support accents (rationed — accent-only, non-load-bearing). */
  --focus-violet:  #7C6BFF;
  --gold:          #E9B949;

  /* ── Tinted status-chip tokens (#1488) ──────────────────────────────────────
     The shared "tinted chip" language — a light hue-tinted background with dark
     same-hue text — used by the redesign's status badges (party role/state,
     .comp-status, .pending-badge). #1488 promotes the last few LEGACY saturated
     status badges (white-on-green/amber ~2.1–2.3:1) onto this same language so
     the whole app reads as one chip system and every pair clears WCAG AA (≥4.5:1).
     Values mirror the party-badge gold standard (Tailwind 100-tint / 800-text
     families). Each variant is a (bg, text) pair; the dark set below is SOLID
     (opaque) so the chip's contrast is deterministic on any night surface. */
  --chip-success-bg:  #DCFCE7;  --chip-success-fg:  #166534;   /* 6.49:1 */
  --chip-warn-bg:     #FEF3C7;  --chip-warn-fg:     #92400E;   /* 6.37:1 */
  --chip-info-bg:     #E0E7FF;  --chip-info-fg:     #3730A3;   /* 8.06:1 */
  --chip-neutral-bg:  #F3F4F6;  --chip-neutral-fg:  #374151;   /* 9.37:1 */

  /* Radii scale (web moves to ~1.5rem; concentric ladder). --radius stays a
     legacy alias so existing rules keep working. */
  --r-pill:  999px;
  --r-card:  24px;
  --r-inner: 16px;
  --r-chip:  12px;
  --r-sheet: 28px;

  /* Spacing — 4pt base. */
  --space-1: 4px;  --space-2: 8px;  --space-3: 12px; --space-4: 16px;
  --space-5: 20px; --space-6: 24px; --space-8: 32px; --space-10: 40px; --space-12: 48px;

  /* Floating-control depth (the one place web uses real elevation). */
  --shadow-float: 0 6px 20px rgba(43,45,66,.10);

  /* Display / body faces. Space Grotesk (self-hosted @font-face below) for
     display headings + hero numerals; body/UI stays the system stack. Both
     fall back to Inter → system so a missing glyph/font degrades cleanly. */
  --font-disp: "Space Grotesk", Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-body: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
}

/* ── Space Grotesk (self-hosted, SIL OFL — public/fonts/OFL.txt) ───────────
   Two static Latin subsets (Medium 500 + Bold 700, ~11KB each) instanced from
   the mobile VF (mobile/assets/fonts/SpaceGrotesk-VF.ttf). Same-origin only —
   CSP `font-src 'self' data:` already permits them, no external request.
   font-display:swap so text paints immediately in the fallback and swaps in. */
@font-face {
  font-family: "Space Grotesk";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("/fonts/SpaceGrotesk-Medium.woff2") format("woff2");
}
@font-face {
  font-family: "Space Grotesk";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("/fonts/SpaceGrotesk-Bold.woff2") format("woff2");
}

/* ── Dark mode (#270) ─────────────────────────────
   Remaps the NEUTRAL tokens (background / surface / text / borders / shadow)
   to a dark palette; the brand accent (--primary) is left untouched so per-gym
   theming still shows through. Applied when the user explicitly picks dark
   (html[data-theme="dark"]) OR when the OS prefers dark and the user hasn't
   forced light. Both selectors out-specify the plain `:root{…}` that the
   /style.css route appends for admin colours (so dark wins). The shared values
   live in --dk-* so the remap isn't duplicated. */
:root {
  /* Aligned with the mobile app's dark ColorScheme (app_theme.dart) so web +
     native dark look like one brand (#270). W3 (#1410): the generic near-black
     is upgraded to the party-room NAVY night skin — "immersive navy replaces
     generic near-black" (design-system.md §1.3) — so web night mode matches the
     app's night exactly. The --dk-* names are unchanged (the remap below still
     reads them); only their VALUES move to the --night-* set. */
  --dk-bg:var(--night-bg); --dk-surface:var(--night-surface); --dk-text:var(--chalk); --dk-mid:var(--chalk-soft);
  --dk-border:var(--night-stroke); --dk-shadow:0 2px 14px rgba(0,0,0,.55);
  --dk-primary-light:#2a1f16; --dk-chrome:var(--night-surface);
  /* Night tinted-chip pairs (#1488) — SOLID deep-hue fill + bright same-hue text
     so the chip clears AA on any night surface it lands on (surface, bg, raised).
     Contrast within the chip: success 7.72:1, warn 9.57:1, info 8.02:1,
     neutral 9.31:1. Names stay --dk-chip-* per the file's remap convention. */
  --dk-chip-success-bg:#123524; --dk-chip-success-fg:#4ADE80;
  --dk-chip-warn-bg:#3B2A0A;    --dk-chip-warn-fg:#FCD34D;
  --dk-chip-info-bg:#1E1B4B;    --dk-chip-info-fg:#A5B4FC;
  --dk-chip-neutral-bg:#2A2C3F; --dk-chip-neutral-fg:#D1D5DB;
}
:root[data-theme="dark"] {
  --light: var(--dk-bg);  --white: var(--dk-surface); --dark: var(--dk-text);
  --mid: var(--dk-mid);   --border: var(--dk-border); --shadow: var(--dk-shadow);
  --primary-light: var(--dk-primary-light); --chrome: var(--dk-chrome);
  --chip-success-bg:var(--dk-chip-success-bg); --chip-success-fg:var(--dk-chip-success-fg);
  --chip-warn-bg:var(--dk-chip-warn-bg);       --chip-warn-fg:var(--dk-chip-warn-fg);
  --chip-info-bg:var(--dk-chip-info-bg);       --chip-info-fg:var(--dk-chip-info-fg);
  --chip-neutral-bg:var(--dk-chip-neutral-bg); --chip-neutral-fg:var(--dk-chip-neutral-fg);
  color-scheme: dark;
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --light: var(--dk-bg);  --white: var(--dk-surface); --dark: var(--dk-text);
    --mid: var(--dk-mid);   --border: var(--dk-border); --shadow: var(--dk-shadow);
    --primary-light: var(--dk-primary-light); --chrome: var(--dk-chrome);
    --chip-success-bg:var(--dk-chip-success-bg); --chip-success-fg:var(--dk-chip-success-fg);
    --chip-warn-bg:var(--dk-chip-warn-bg);       --chip-warn-fg:var(--dk-chip-warn-fg);
    --chip-info-bg:var(--dk-chip-info-bg);       --chip-info-fg:var(--dk-chip-info-fg);
    --chip-neutral-bg:var(--dk-chip-neutral-bg); --chip-neutral-fg:var(--dk-chip-neutral-fg);
    color-scheme: dark;
  }
}

/* ── Frosted glass — the floating-control layer (epic #1410 W3) ────────────
   For FLOATING controls only (chips, the hub customize chip, the mobile bottom
   bar, floating actions) — never content cards, per the standard's restraint
   rule. backdrop-filter with a solid fallback: browsers without backdrop-filter
   support, and users who ask to reduce transparency, get an opaque surface so
   text stays legible (CSP/perf-safe, no external anything). */
.mg-frost {
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  background: rgba(255,255,255,.82);
  border: 1px solid rgba(0,0,0,.06);
  box-shadow: var(--shadow-float);
}
:root[data-theme="dark"] .mg-frost {
  background: rgba(25,27,46,.70);   /* --night-surface @ 70% */
  border-color: var(--night-stroke);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .mg-frost {
    background: rgba(25,27,46,.70);
    border-color: var(--night-stroke);
  }
}
/* Solid fallbacks — no blur available, or the user reduces transparency. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .mg-frost { background: var(--white); }
}
@media (prefers-reduced-transparency: reduce) {
  .mg-frost { backdrop-filter: none; -webkit-backdrop-filter: none; background: var(--white); }
}

/* ── Reset ───────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

/* ── Reduced motion (#1325 W1) ───────────────────────────────────────────────
   Blanket a11y override — the app has 65+ discrete `transition:` declarations
   plus @keyframes and JS-driven inline transitions (e.g. the More-sheet
   swipe-to-close in public-shell.js sets `sheet.style.transition` directly);
   auditing each one individually doesn't scale, so collapse them all here.
   0.01ms, not 0 — a strictly-zero duration can skip firing `transitionend`
   entirely in some engines, which would break the CSS-only affordances that
   lean on it (e.g. .member-menu-panel transitions `visibility` so the closed
   panel drops out of the tab order only once its fade finishes; a repo-wide
   grep confirms no JS `transitionend`/`animationend` listener exists to break,
   but keep the near-zero duration anyway so any future one stays safe).
   `!important` is required to also win over the JS-set inline transition
   above (importance outranks specificity/origin, inline or not). */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  background: var(--light);
  color: var(--dark);
  min-height: 100vh;
  line-height: 1.5;
}

/* ── Header ──────────────────────────────────── */
.header {
  background: var(--chrome);
  color: #fff;
  padding: 14px 20px;
  display: flex;
  align-items: center;
  gap: 10px;
  position: sticky;
  top: 0;
  z-index: 10;
  box-shadow: 0 2px 8px rgba(0,0,0,.2);
}
.header .logo {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  text-decoration: none;
  line-height: 1;
  flex-shrink: 0;
}
.header .logo-img { width: 24px; height: 24px; border-radius: 6px; display: block; }
.header .logo-name { font-family: var(--font-disp); font-size: 1.2rem; font-weight: 700; color: #fff; letter-spacing: -.2px; }
/* Page title sits to the right of the brand, visually subordinate to it. */
.header h1    { font-size: 1.05rem; font-weight: 600; color: rgba(255,255,255,.92); }
.header .subtitle {
  font-size: .78rem;
  color: rgba(255,255,255,.55);
  margin-left: 2px;
}
.header-nav { margin-left: auto; display: flex; gap: 16px; }
.header-nav a {
  color: rgba(255,255,255,.65);
  text-decoration: none;
  font-size: .85rem;
  transition: color .2s;
}
.header-nav a:hover { color: #fff; }

/* ── Layout ──────────────────────────────────── */
.container {
  max-width: 760px;
  margin: 0 auto;
  padding: 24px 16px 60px;
}
/* Data-heavy pages (e.g. the boulder list) use more of a wide screen; forms
   stay at the narrow reading width. Narrow screens are unaffected (content
   already fills the width below this cap). */
.container.wide { max-width: 1200px; }

/* ── Cards ───────────────────────────────────── */
/* W3 (#1410): the single most visible "premium" dial, and it lands site-wide
   because .card is shared. Bigger --r-card radius, a 1px hairline instead of a
   drop shadow (calm = restraint), and in night mode a lighter-navy surface with
   a --night-stroke rim so it reads as raised. */
.card {
  background: var(--white);
  border-radius: var(--r-card);
  padding: 24px;
  border: 1px solid var(--border);
  box-shadow: none;
  margin-bottom: 20px;
}
:root[data-theme="dark"] .card { background: var(--night-high); border-color: var(--night-stroke); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .card { background: var(--night-high); border-color: var(--night-stroke); }
}
.card-title {
  font-family: var(--font-disp);   /* W3: display face on shared card titles */
  font-size: 1rem;
  font-weight: 700;
  color: var(--dark);
  margin-bottom: 18px;
  padding-bottom: 10px;
  border-bottom: 2px solid var(--primary);
  display: flex;
  align-items: center;
  gap: 8px;
}
.card-title .icon { font-size: 1.1rem; }

/* ── Themed inline link (#1480) ──────────────────────────────────────────────
   style.css has no global `a{}` colour, so a bare inline <a> (e.g. the auth
   consent "Privacy Policy" / "choose your gym" links) falls to the UA default
   blue #0000EE — ~1.35:1 on the night card, invisible in dark mode. .mg-link is
   the shared brand-orange treatment for such body-text links: brand orange +
   underline in light, and the AA-legible orange for navy (--brand-on-night,
   ≥5.4:1) on both night surfaces. Both dark paths, per the file's convention. */
.mg-link { color: var(--primary-dark); text-decoration: underline; }
.mg-link:hover { color: var(--primary); }
:root[data-theme="dark"] .mg-link,
:root[data-theme="dark"] .mg-link:hover { color: var(--brand-on-night); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .mg-link,
  :root:not([data-theme="light"]) .mg-link:hover { color: var(--brand-on-night); }
}

/* ── Super-admin broadcast banner (#1480) ────────────────────────────────────
   Was inline `background:#FFFBEB` with `#broadcastText { color:var(--dark) }` —
   in night mode --dark flips to chalk while the pale-yellow bg stayed put →
   ≈1.09:1, invisible (and a light-glare box). This banner is shown to ANY viewer
   (public news API), so it must read on both skins. Amber identity in light, a
   warm-dark surface with chalk text + brand-orange rule in dark. */
.broadcast-card { border: 2px solid #D97706; background: #FFFBEB; color: var(--dark); }
:root[data-theme="dark"] .broadcast-card {
  background: var(--primary-light); border-color: var(--brand-on-night); color: var(--chalk);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .broadcast-card {
    background: var(--primary-light); border-color: var(--brand-on-night); color: var(--chalk);
  }
}

/* ── Gym-map wall hover tooltip (#1480) ──────────────────────────────────────
   Was inline `background:#fff` with the name `color:var(--dark)` — in night mode
   --dark flips to chalk on the still-white bg → ≈1.13:1, the wall name invisible
   over its thumbnail. Anon-visible. Flip the surface + text together. */
.wall-tooltip { background: var(--white); color: var(--dark); }
:root[data-theme="dark"] .wall-tooltip { background: var(--night-surface); color: var(--chalk); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .wall-tooltip { background: var(--night-surface); color: var(--chalk); }
}

/* ── Forms ───────────────────────────────────── */
.form-group { margin-bottom: 16px; }
.form-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

label {
  display: block;
  font-weight: 600;
  font-size: .85rem;
  margin-bottom: 5px;
  color: var(--dark);
}
label .req { color: var(--primary); margin-left: 2px; }

input[type=text],
input[type=number],
input[type=password],
input[type=date],
input[type=email],
select,
textarea {
  width: 100%;
  padding: 10px 13px;
  border: 2px solid var(--border);
  border-radius: 8px;
  font-size: .95rem;
  background: var(--white);
  color: var(--dark);
  transition: border-color .2s, box-shadow .2s;
  /* Normalise across browsers (Safari keeps its own look without this) */
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  font-family: inherit;
  box-sizing: border-box;
}
/* Keep native calendar/spinner controls for date inputs */
input[type=date]::-webkit-calendar-picker-indicator {
  opacity: .6;
  cursor: pointer;
}
select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%236B7280' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  padding-right: 36px;
}
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--primary);
  box-shadow: 0 0 0 3px rgba(232,115,26,.15);
}
textarea { resize: vertical; min-height: 70px; }

.char-row {
  display: flex;
  justify-content: flex-end;
  margin-top: 3px;
}
.char-count {
  font-size: .75rem;
  color: var(--mid);
  transition: color .2s;
}
.char-count.warn  { color: #F97316; }
.char-count.limit { color: var(--danger); font-weight: 700; }

/* ── Buttons ─────────────────────────────────── */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 11px 22px;
  border: none;
  border-radius: 8px;
  font-size: .95rem;
  font-weight: 600;
  cursor: pointer;
  transition: background .2s, transform .15s, box-shadow .2s;
  text-decoration: none;
}
.btn:active { transform: scale(.97); }
/* Disabled buttons read clearly as unavailable (no hover/press effects). */
.btn:disabled, .btn[disabled] {
  opacity: .45;
  cursor: not-allowed;
  box-shadow: none;
  filter: grayscale(40%);
}
.btn:disabled:active { transform: none; }
.btn-primary:disabled:hover   { background: var(--primary); box-shadow: none; }
.btn-secondary:disabled:hover { background: transparent; color: var(--primary); }
.btn-ghost:disabled:hover     { border-color: var(--border); color: var(--mid); }
.btn-danger:disabled:hover    { background: var(--danger); }
.btn-primary {
  background: var(--primary);
  color: #fff;
  width: 100%;
}
.btn-primary:hover { background: var(--primary-dark); box-shadow: 0 4px 12px rgba(232,115,26,.35); }
.btn-sm {
  padding: 5px 12px;
  font-size: .8rem;
  border-radius: 6px;
}
.btn-danger  { background: var(--danger);  color: #fff; }
.btn-secondary { background: transparent; border: 2px solid var(--primary); color: var(--primary); }
.btn-secondary:hover { background: var(--primary); color: #fff; }
.btn-ghost   { background: transparent; border: 2px solid var(--border); color: var(--mid); }
.btn-ghost:hover { border-color: var(--primary); color: var(--primary); }
.btn-icon    { width: 36px; height: 36px; padding: 0; font-size: 1.05rem; flex-shrink: 0; }

/* ── Grade selector ──────────────────────────── */
.grade-selector {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.grade-btn {
  width: 50px;
  height: 50px;
  border: 2px solid var(--border);
  border-radius: 10px;
  font-size: 1.15rem;
  font-weight: 800;
  cursor: pointer;
  background: var(--white);
  color: var(--dark);
  transition: all .18s;
  position: relative;
}
.grade-btn:hover { transform: translateY(-2px); box-shadow: 0 4px 10px rgba(0,0,0,.12); }
/* Dynamic grade buttons use --btn-grade-color set via inline style */
.grade-btn { --gc: var(--btn-grade-color, var(--primary)); }
.grade-btn.active {
  background: var(--gc);
  border-color: var(--gc);
  color: #fff;
  box-shadow: 0 4px 14px rgba(0,0,0,.2);
  transform: translateY(-2px) scale(1.06);
}
.grade-btn[data-g="3"].active { color: var(--dark); }  /* yellow contrast */

.grade-legend {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: 8px;
}
/* Honor the `hidden` attribute: the `display:flex` above is author CSS and would
   otherwise override the UA `[hidden]{display:none}` rule, so a hidden legend
   (feature off / no equivalents — the #942 grade-key legend on index.html) would
   render as an empty bordered bar. Mirrors `.filter-fields[hidden]` below. */
.grade-legend[hidden] { display: none; }
.grade-legend-item {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: .72rem;
  color: var(--mid);
}
.grade-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
}

/* ── Captcha ─────────────────────────────────── */
.captcha-box {
  background: var(--light);
  border: 2px dashed var(--border);
  border-radius: 10px;
  padding: 16px 20px;
  display: flex;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}
.captcha-question {
  font-size: 1.4rem;
  font-weight: 800;
  color: var(--dark);
  min-width: 100px;
}
.captcha-input {
  width: 90px !important;
  text-align: center;
  font-size: 1.1rem !important;
  font-weight: 700 !important;
}
.captcha-refresh {
  background: none;
  border: none;
  cursor: pointer;
  font-size: 1.1rem;
  color: var(--mid);
  transition: color .2s, transform .3s;
}
.captcha-refresh:hover { color: var(--primary); transform: rotate(180deg); }

/* ── Photo drop-zone (add boulder form) ─────── */
.photo-drop-zone {
  display: block;
  border: 2px dashed var(--border);
  border-radius: 10px;
  background: var(--light);
  cursor: pointer;
  overflow: hidden;
  transition: border-color .2s, background .2s;
  min-height: 110px;
  position: relative;
}
.photo-drop-zone:hover,
.photo-drop-zone.drag-over {
  border-color: var(--primary);
  background: var(--primary-light);
}
.photo-drop-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 24px 16px;
  text-align: center;
}
.photo-drop-icon { font-size: 1.8rem; }
.photo-drop-text { font-size: .83rem; color: var(--mid); }
.photo-preview {
  width: 100%;
  max-height: 220px;
  object-fit: cover;
  display: block;
  border-radius: 8px;
}

/* ── Alerts ──────────────────────────────────── */
.alert {
  padding: 12px 16px;
  border-radius: 8px;
  font-size: .88rem;
  margin-bottom: 14px;
  display: flex;
  align-items: center;
  gap: 8px;
  animation: fadeIn .3s ease;
}
.alert-success { background: #DCFCE7; color: #15803D; border: 1px solid #BBF7D0; }
.alert-error   { background: #FEE2E2; color: #991B1B; border: 1px solid #FECACA; }
@keyframes fadeIn { from { opacity:0; transform:translateY(-4px);} to {opacity:1; transform:translateY(0);} }

/* ── List controls (filter + sort) ──────────────────────────────────────────
   W4 (#1416) content-first polish: the search + filter block is the page's
   floating CONTROL layer, not content — so it gets the frosted-glass treatment
   (the standard reserves frost for floating controls, never content cards). It
   ships on Boulders (index) and Community (shared class), lifting both at once.
   Frost + float-shadow + the W3 radius/spacing scale; the .mg-frost rules
   (dark / no-blur / reduced-transparency fallbacks) already cover the surface. */
.list-controls {
  display: flex;
  flex-direction: column;   /* the always-visible bar, then the collapsible fields */
  align-items: stretch;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}
/* When the block also carries .card (both pages do), override the flat card
   look with the frosted control surface. Kept scoped to .list-controls so no
   other .card is affected. */
.card.list-controls {
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  background: rgba(255,255,255,.82);
  border: 1px solid rgba(0,0,0,.06);
  box-shadow: var(--shadow-float);
  border-radius: var(--r-card);
  padding: var(--space-4);
}
:root[data-theme="dark"] .card.list-controls {
  background: rgba(25,27,46,.70);   /* --night-surface @ 70% */
  border-color: var(--night-stroke);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .card.list-controls {
    background: rgba(25,27,46,.70);
    border-color: var(--night-stroke);
  }
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .card.list-controls { background: var(--white); }
}
@media (prefers-reduced-transparency: reduce) {
  .card.list-controls {
    backdrop-filter: none; -webkit-backdrop-filter: none; background: var(--white);
  }
}
/* Always-visible bar: Filters toggle + one-line Sort. */
.lc-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
}
.filters-toggle {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font: inherit;
  font-size: .85rem;
  font-weight: 600;
  padding: 8px 14px;
  border: 1.5px solid var(--border);
  border-radius: var(--r-chip);   /* W4: W3 radius scale on the floating control */
  background: var(--white, #fff);
  color: var(--dark);
  cursor: pointer;
}
.filters-toggle:hover { border-color: var(--primary); }
.filters-toggle.open { border-color: var(--primary); color: var(--primary); }
.filters-count { color: var(--primary); font-weight: 700; }
.ft-caret { transition: transform .15s; font-size: .8rem; }
.filters-toggle.open .ft-caret { transform: rotate(180deg); }

/* Collapsible filter fields — wrap onto multiple lines (no horizontal scroll). */
.filter-fields {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding-bottom: 2px;
}
.filter-fields[hidden] { display: none; }
.filter-fields .filter-row { flex: 0 1 auto; }   /* each control may share a line / wrap */

/* Each control + its ✕ stays together (nowrap). */
.filter-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: nowrap;
}
.filter-select {
  padding: 6px 10px;
  padding-right: 30px;
  border: 2px solid var(--border);
  border-radius: 8px;
  font-size: .82rem;
  background: var(--white);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%236B7280' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  color: var(--dark);
  cursor: pointer;
  min-width: 0;
  flex: 1;
  max-width: 220px;
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  font-family: inherit;
}
.filter-select:focus { outline: none; border-color: var(--primary); }

/* ── #282 search + multi-criteria filters ───────────────────────────────── */
.lc-search {
  display: flex;
  align-items: center;
  gap: 8px;
  border: 1.5px solid var(--border);
  border-radius: var(--r-pill);   /* W4: the floating "search pill" (wave §5.1) */
  background: var(--white, #fff);
  padding: 2px 14px;
}
.lc-search:focus-within { border-color: var(--primary); }
.lc-search-icon { font-size: .9rem; opacity: .7; }
.lc-search-input {
  flex: 1;
  min-width: 0;
  border: none;
  outline: none;
  background: transparent;
  font: inherit;
  font-size: .9rem;
  color: var(--dark);
  padding: 9px 0;
}
.lc-search-clear,
.lc-search-input::-webkit-search-cancel-button { /* use our own ✕ */ }
.lc-search-clear {
  border: none;
  background: transparent;
  color: var(--mid);
  cursor: pointer;
  font-size: .85rem;
  padding: 4px 6px;
  border-radius: 6px;
}
.lc-search-clear:hover { color: var(--danger); }

/* Suggestion chips parsed from the typed phrase */
.search-suggest { display: flex; flex-wrap: wrap; gap: 8px; }
.search-suggest[hidden] { display: none; }
.suggest-chip {
  font: inherit;
  font-size: .8rem;
  font-weight: 600;
  padding: 5px 11px;
  border: 1.5px dashed var(--border);
  border-radius: 999px;
  background: var(--white, #fff);
  color: var(--dark);
  cursor: pointer;
}
.suggest-chip:hover { border-style: solid; border-color: var(--primary); color: var(--primary); }

/* Active-filter chips (removable) */
.active-filters { display: flex; flex-wrap: wrap; gap: 8px; }
.active-filters[hidden] { display: none; }
.afchip {
  font: inherit;
  font-size: .8rem;
  font-weight: 700;
  padding: 5px 11px;
  border: none;
  border-radius: 999px;
  background: var(--primary);
  color: #fff;
  cursor: pointer;
}
.afchip:hover { filter: brightness(.94); }
.afchip-clear {
  background: transparent;
  color: var(--danger);
  border: 1.5px solid color-mix(in srgb, var(--danger) 40%, transparent);
}

/* Collapsible groups of multi-select chips */
.filter-groups { flex-direction: column; align-items: stretch; gap: 14px; }
.filter-group { display: flex; flex-direction: column; gap: 8px; }
.filter-group[hidden] { display: none; }
.fg-head { display: flex; align-items: center; gap: 10px; }
.fg-title {
  font-size: .72rem;
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--mid);
}
.fg-modes { display: inline-flex; gap: 10px; margin-left: auto; }
.fg-mode {
  font: inherit;
  font-size: .75rem;
  font-weight: 700;
  border: none;
  background: transparent;
  color: var(--mid);
  cursor: pointer;
  padding: 0;
}
.fg-mode.active { color: var(--primary); text-decoration: underline; }
.fg-hint { font-size: .72rem; color: var(--mid); }
.fg-hint[hidden] { display: none; }
.fg-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.fchip {
  font: inherit;
  font-size: .8rem;
  font-weight: 600;
  padding: 6px 12px;
  border: 1.5px solid var(--border);
  border-radius: 999px;
  background: var(--white, #fff);
  color: var(--dark);
  cursor: pointer;
}
.fchip:hover { border-color: var(--primary); }
.fchip.on { background: var(--primary); border-color: var(--primary); color: #fff; }

/* ── Shared send-row presentation (My Sends + public profile, #18/#20) ── */
.send-row { border-bottom: 1px solid var(--border); margin: 0 -6px; }
.send-row:last-child { border-bottom: none; }
.send-main {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 12px 6px; cursor: pointer; border-radius: 6px;
  transition: background .15s;
}
.send-main:hover { background: var(--light); }
.send-row-icon {
  width: 38px; height: 38px; border-radius: 6px; background: var(--light);
  display: flex; align-items: center; justify-content: center;
  font-size: 1.2rem; flex-shrink: 0; border: 1px solid var(--border);
}
.send-row-info { flex: 1; min-width: 0; }
.send-row-title { font-weight: 600; font-size: .92rem; color: var(--dark); }
.send-row-sub { font-size: .78rem; color: var(--mid); margin-top: 2px; }
.send-row-meta { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-top: 4px; }
.color-dot {
  width: 10px; height: 10px; border-radius: 50%; display: inline-block;
  margin-right: 4px; border: 1px solid rgba(0,0,0,.12); flex-shrink: 0;
}
.send-date-badge {
  font-size: .74rem; color: var(--mid); background: var(--light);
  border: 1px solid var(--border); border-radius: 10px; padding: 1px 8px; white-space: nowrap;
}
.send-note-preview { font-size: .78rem; color: var(--mid); font-style: italic; margin-top: 3px; }
.send-video-badge {
  font-size: .72rem; color: var(--mid); background: var(--light);
  border: 1px solid var(--border); border-radius: 10px; padding: 1px 7px; white-space: nowrap;
}
.archived-badge {
  font-size: .72rem; color: #92400e; background: #fef3c7;
  border: 1px solid #fde68a; border-radius: 10px; padding: 1px 7px; white-space: nowrap;
}
.send-row.is-archived .send-main { cursor: default; }
.send-row.is-archived .send-main:hover { background: none; }
.send-action-btn {
  background: none; border: none; cursor: pointer; font-size: .95rem; color: var(--mid);
  padding: 5px 7px; flex-shrink: 0; border-radius: 4px; transition: background .12s, color .12s;
  align-self: flex-start; line-height: 1; display: inline-flex; align-items: center;
}
.send-action-btn:hover { background: var(--border); color: var(--dark); }

/* ── In-page nav (member Settings, #18) — button-styled, matches the page CTAs ── */
.profile-tabs {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  margin: 0 0 18px;
}
.profile-tab {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font: inherit;
  font-size: .85rem;
  font-weight: 600;
  white-space: nowrap;
  padding: 8px 14px;
  border: 1.5px solid var(--border);
  border-radius: var(--r-pill);   /* W3: pill chips for the scoped sub-navs */
  background: var(--white, #fff);
  color: var(--dark);
  text-decoration: none;
  cursor: pointer;
  transition: background .15s, color .15s, border-color .15s;
}
.profile-tab:hover { border-color: var(--primary); }
.profile-tab.active {
  background: var(--primary);
  border-color: var(--primary);
  color: #fff;
}

.filter-input {
  padding: 7px 12px;
  border: 2px solid var(--border);
  border-radius: 8px;
  font-size: .85rem;
  background: var(--white);
  color: var(--dark);
  min-width: 0;
  flex: 1;
  max-width: 320px;
  font-family: inherit;
  -webkit-appearance: none;
  appearance: none;
  transition: border-color .15s;
}
.filter-input:focus { outline: none; border-color: var(--primary); }
.filter-input::placeholder { color: var(--mid); }
.clear-filter-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border: 2px solid var(--border);
  border-radius: 50%;
  background: var(--white);
  color: var(--mid);
  font-size: .75rem;
  cursor: pointer;
  flex-shrink: 0;     /* never collapse or wrap */
  transition: border-color .15s, color .15s;
}
.clear-filter-btn:hover { border-color: var(--danger); color: var(--danger); }
/* Sort group: stays on ONE line (scrolls horizontally if ever too narrow). */
.sort-group {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: nowrap;
  overflow-x: auto;
  scrollbar-width: none;
  margin-left: auto;        /* push Sort to the right of the Filters toggle */
}
.sort-group::-webkit-scrollbar { display: none; }
.sort-label {
  font-size: .78rem;
  color: var(--mid);
  font-weight: 600;
  white-space: nowrap;
}
.sort-btn {
  padding: 5px 12px;
  border: 2px solid var(--border);
  border-radius: 20px;
  background: var(--white);
  color: var(--mid);
  font-size: .78rem;
  font-weight: 600;
  cursor: pointer;
  transition: all .15s;
  white-space: nowrap;
}
.sort-btn:hover { border-color: var(--primary); color: var(--primary); }
.sort-btn.active { background: var(--primary); border-color: var(--primary); color: #fff; }

/* ── Boulder table (home page overview) ─────────── */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.boulder-table { width: 100%; border-collapse: collapse; font-size: .88rem; min-width: 380px; }
.boulder-table th {
  background: var(--light);
  padding: 9px 12px;
  text-align: left;
  font-size: .72rem;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--mid);
  font-weight: 700;
  white-space: nowrap;
}
.boulder-table td {
  padding: 11px 12px;
  border-top: 1px solid var(--border);
  vertical-align: middle;
}
.clickable-row { cursor: pointer; }
.clickable-row:hover td { background: var(--primary-light); }
.row-photo-icon {
  display: inline-block;
  font-size: .85rem;
  margin-left: 6px;
  opacity: .55;
  cursor: pointer;
  vertical-align: middle;
  transition: opacity .15s, transform .15s;
}
.row-photo-icon:hover { opacity: 1; transform: scale(1.2); }
/* ── Back-to-top button ──────────────────────── */
.back-to-top {
  position: fixed;
  bottom: 24px;
  right: 20px;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--primary);
  color: #fff;
  border: none;
  font-size: 1.2rem;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 4px 14px rgba(0,0,0,.25);
  opacity: 0;
  pointer-events: none;
  transform: translateY(10px);
  transition: opacity .25s, transform .25s;
  z-index: 90;
}
.back-to-top.visible {
  opacity: 1;
  pointer-events: auto;
  transform: translateY(0);
}
/* The mobile "More" sheet (z-index 61) must sit above the back-to-top button —
   hide the button whenever the sheet is open so it can't overlap it. */
body.pub-more-open .back-to-top {
  opacity: 0 !important;
  pointer-events: none !important;
}

/* ── Infinite-scroll sentinel ────────────────── */
.scroll-sentinel {
  text-align: center;
  padding: 14px 0 4px;
  font-size: .8rem;
  color: var(--mid);
  font-style: italic;
}

/* ── Mobile-only hint (hidden on wider screens) ── */
.mobile-hint {
  display: none;
  font-size: .75rem;
  color: var(--mid);
  font-style: italic;
  margin: -10px 0 12px;
}
@media (max-width: 480px) {
  /* Portrait reflow: each boulder is a tidy 2-row card showing ALL data
     (location, Set, Avg, Votes) — no "rotate to landscape". A FIXED-width left
     column keeps every row's columns aligned; the colour pill collapses to its
     dot so it fits. */
  .mobile-hint                    { display: none; }
  /* Tight viewport: drop the "Sort:" label so the sort buttons stay one line. */
  .sort-label                     { display: none; }
  .boulder-table                  { min-width: 0; width: 100%; }
  .boulder-table thead            { display: none; }
  .boulder-table, .boulder-table tbody { display: block; width: 100%; }
  .boulder-table tr {
    display: grid;
    grid-template-columns: 58px 1fr auto;          /* fixed left col → aligned rows */
    grid-template-areas: "id loc avg" "id set votes";
    column-gap: 12px; row-gap: 2px; align-items: center;
    padding: 11px 2px; border-bottom: 1px solid var(--border);
  }
  .boulder-table td               { display: block; border: none !important; padding: 0; min-width: 0; }
  .boulder-table td:first-child   { grid-area: id; align-self: center; display: flex; flex-wrap: wrap;
                                     align-items: center; gap: 4px; font-weight: 800; }
  .boulder-table td:first-child strong { display: block; width: 100%; font-size: 1rem; }
  /* Colour pill → just the dot (name hidden) so the left column stays narrow */
  .boulder-table .color-chip      { font-size: 0; gap: 0; margin: 0; padding: 0;
                                     border: none; background: transparent; }
  .boulder-table .row-photo-icon,
  .boulder-table .row-video-icon  { font-size: .8rem; margin: 0; }
  .boulder-table .col-location    { grid-area: loc; font-weight: 600; align-self: center;
                                     overflow-wrap: break-word; word-break: break-word; }
  .boulder-table td:nth-child(4)  { grid-area: avg; justify-self: end; align-self: center; }  /* Avg badge */
  .boulder-table .col-set         { grid-area: set; font-size: .76rem; color: var(--mid); }
  .boulder-table .col-set::before { content: "Set "; }
  .boulder-table .col-votes       { grid-area: votes; justify-self: end; font-size: .74rem; color: var(--mid); white-space: nowrap; }
  .boulder-table .special-note    { display: block; white-space: normal; margin: 3px 0 0; font-size: .8rem; }
}

/* ── Boulder cards (admin list) ───────────────── */
.boulder-cards { display: flex; flex-direction: column; gap: 10px; }
.boulder-card {
  padding: 14px 16px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--light);
  transition: border-color .2s;
}
.boulder-card:hover  { border-color: var(--primary); }
.bc-body             { display: flex; flex-direction: column; gap: 8px; }
.bc-top-row          { display: flex; align-items: flex-start;
                       justify-content: space-between; gap: 10px; }
.bc-top-row > div:first-child { min-width: 0; flex: 1; }
.bc-actions          { display: flex; gap: 8px; flex-shrink: 0; flex-wrap: wrap;
                       justify-content: flex-end; }
.bc-num              { font-size: 1.1rem; font-weight: 800; color: var(--dark); margin-bottom: 2px; }
.bc-loc              { font-size: .88rem; color: var(--mid);
                       white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
                       max-width: 180px; }
.bc-meta             { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
.bc-img-wrap         { margin-top: 4px; }
.bc-thumb            { width: 100%; max-height: 200px; object-fit: cover;
                       border-radius: 8px; cursor: zoom-in;
                       transition: opacity .2s; }
.bc-thumb:hover      { opacity: .88; }

/* Upload label styled as a button */
.upload-label        { cursor: pointer; }
.upload-label input  { display: none; }

/* ── Boulder photo on grade page ──────────────── */
.boulder-photo {
  width: 100%;
  max-height: 280px;
  object-fit: cover;
  border-radius: 10px;
  cursor: zoom-in;
  transition: opacity .2s;
  display: block;
}
.boulder-photo:hover { opacity: .88; }

/* ── Grade badge (small pill) ────────────────── */
.g-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border-radius: 7px;
  font-weight: 800;
  font-size: .85rem;
  color: #fff;
}
.g1 { background: var(--g1); }
.g2 { background: var(--g2); }
.g3 { background: var(--g3); color: var(--dark); }
.g4 { background: var(--g4); }
.g5 { background: var(--g5); }
.g6 { background: var(--g6); }
.g7 { background: var(--g7); }

/* ── Accessibility: visually-hidden label (#1449) ────────────────────────────
   Keeps text in the a11y tree (screen readers) while removing it from the visual
   layout. Used by colour-only grade chips to carry the circuit name without
   painting it on the swatch. Standard clip-rect pattern. */
.sr-only {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ── #1449: circuit-style COLOUR-ONLY grade chips ────────────────────────────
   A gym on a colour-only scale renders every grade badge/pill as a bare colour
   swatch (no number/label on the face). The `--co` modifier drops the text glyph
   and adds a non-colour cue so the chip satisfies WCAG 1.4.1 (colour is never the
   SOLE carrier): the label is still exposed as the accessible name + hover title
   (see .sr-only above), AND a deterministic per-step PATTERN in the chip FRAME
   (`data-gp` 0..3) keeps adjacent circuits distinguishable in greyscale / for
   colour-blind users. The swatch face stays clean; the cue lives on the border.
   `--co-ring` derives from the chip's own background so the frame reads on any hue. */
.g-badge--co, .grade-pill--co {
  color: transparent;            /* no glyph paints on the swatch face */
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .18);  /* base edge on light swatches */
}
/* Four frame treatments, cycled by ladder position. Kept subtle (a border cue,
   not a loud overlay) but individually recognisable without relying on hue. */
.g-badge--co[data-gp="0"], .grade-pill--co[data-gp="0"] {
  outline: 2px solid rgba(0, 0, 0, .35); outline-offset: -2px;          /* solid ring */
}
.g-badge--co[data-gp="1"], .grade-pill--co[data-gp="1"] {
  outline: 2px dashed rgba(0, 0, 0, .45); outline-offset: -2px;         /* dashed ring */
}
.g-badge--co[data-gp="2"], .grade-pill--co[data-gp="2"] {
  outline: 2px double rgba(0, 0, 0, .5); outline-offset: -3px;          /* double ring */
}
.g-badge--co[data-gp="3"], .grade-pill--co[data-gp="3"] {
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .18),
              inset 0 0 0 6px rgba(255, 255, 255, .55);                 /* inner dot/inset */
}

/* ── Stats row ───────────────────────────────── */
.stats-row {
  display: flex;
  gap: 28px;
  padding: 14px 0 6px;
  flex-wrap: wrap;
}
.stat { text-align: center; }
.stat-value { font-family: var(--font-disp); font-size: 1.9rem; font-weight: 700; letter-spacing: -.5px; color: var(--primary); line-height: 1; }
.stat-label { font-size: .7rem; color: var(--mid); text-transform: uppercase; letter-spacing: .06em; margin-top: 2px; }

/* ── Distribution bar ────────────────────────── */
.dist-bar   { display: flex; gap: 5px; margin: 12px 0 4px; }
.dist-col   { display: flex; flex-direction: column; align-items: center; gap: 3px; flex: 1; }
.dist-track { display: flex; align-items: flex-end; width: 100%; height: 56px; }
.dist-fill  { width: 100%; border-radius: 4px 4px 0 0; min-height: 3px; transition: height .4s ease; }
.dist-label { font-size: .7rem; color: var(--mid); font-weight: 700; }

/* ── Grade comments list ─────────────────────── */
.grade-list { display: flex; flex-direction: column; gap: 8px; }
.grade-item { display: flex; align-items: flex-start; gap: 10px; padding: 8px 0; border-bottom: 1px solid var(--border); }
.grade-item:last-child { border-bottom: none; }
.grade-item-name    { font-size: .82rem; font-weight: 600; color: var(--dark); }
.grade-item-comment { font-size: .85rem; color: var(--mid); font-style: italic; }
.grade-item-time    { font-size: .72rem; color: #B0A898; margin-top: 2px; }
.grade-moderated    { font-size: .82rem; color: #B0A898; font-style: italic; }
.btn-moderate {
  flex-shrink: 0;
  background: none;
  border: none;
  cursor: pointer;
  font-size: .9rem;
  opacity: .35;
  padding: 2px 4px;
  border-radius: 4px;
  transition: opacity .15s;
  align-self: center;
}
.btn-moderate:hover { opacity: 1; }

/* ── Empty state ─────────────────────────────── */
.empty { text-align: center; padding: 36px 20px; color: var(--mid); }
.empty .empty-icon { font-size: 2.8rem; margin-bottom: 10px; }
.empty p { font-size: .9rem; }

/* ── Home page ───────────────────────────────── */
.hero { text-align: center; padding: 24px 20px 20px; }
.action-cards {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  max-width: 560px;
  margin: 0 auto;
}
.action-cards-solo {
  grid-template-columns: 1fr;
  max-width: 300px;
}
.action-card-compact {
  flex-direction: row;
  padding: 14px 20px;
  gap: 12px;
  justify-content: center;
  text-align: left;
}
.action-card-compact .ac-icon {
  font-size: 1.5rem;
  margin-bottom: 0;
  flex-shrink: 0;
}
.action-card-compact h3 { margin-bottom: 0; }
.action-card {
  background: var(--white);
  border-radius: var(--radius);
  padding: 28px 20px;
  text-align: center;
  text-decoration: none;
  color: var(--dark);
  box-shadow: var(--shadow);
  transition: all .2s;
  border: 2px solid transparent;
}
.action-card:hover { border-color: var(--primary); transform: translateY(-4px); box-shadow: 0 8px 22px rgba(0,0,0,.12); }
.action-card .ac-icon { font-size: 2.4rem; margin-bottom: 10px; }
.action-card h3 { font-size: 1rem; margin-bottom: 6px; font-weight: 700; }
.action-card p  { font-size: .8rem; color: var(--mid); }

/* ── Admin tiles ─────────────────────────────── */
.admin-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 16px;
  margin-bottom: 24px;
}

/* ── Settings page section nav ───────────────── */
/* Sticky wrapper: keeps header + admin nav together at all viewport sizes */
.admin-sticky-bar {
  position: sticky;
  top: 0;
  z-index: 10;
}
.admin-sticky-bar .header {
  position: static;
  box-shadow: none;
}
.settings-nav {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-wrap: nowrap;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  margin-bottom: 0;
  background: var(--white);
  padding: 8px 16px;
  box-shadow: 0 2px 6px rgba(0,0,0,.15);
}
.settings-nav::-webkit-scrollbar { display: none; }
.nav-group-label {
  font-size: .7rem;
  font-weight: 700;
  color: var(--mid);
  text-transform: uppercase;
  letter-spacing: .06em;
  white-space: nowrap;
  padding: 4px 4px 4px 0;
  user-select: none;
  flex-shrink: 0;
}
.nav-group-sep {
  width: 1px;
  height: 18px;
  background: var(--border);
  flex-shrink: 0;
  margin: 0 8px;
}
.settings-nav-link {
  display: inline-flex;
  align-items: center;
  padding: 5px 11px;
  border-radius: 6px;
  font-size: .82rem;
  font-weight: 600;
  color: var(--mid);
  text-decoration: none;
  transition: background .15s, color .15s;
  white-space: nowrap;
  flex-shrink: 0;
}
.settings-nav-link:hover,
.settings-nav-link.active { background: var(--primary); color: #fff; }
/* #1227 — the page-level tabs (Members / Boulders on community.html) are
   <button>s, not <a>s (no href to navigate), so strip the native button chrome. */
button.settings-nav-link {
  border: none;
  background: transparent;
  font-family: inherit;
  cursor: pointer;
}

.settings-section { scroll-margin-top: 130px; } /* corrected dynamically by JS */
.admin-tile {
  background: var(--white);
  border-radius: var(--radius);
  padding: 24px 18px;
  text-align: center;
  text-decoration: none;
  color: var(--dark);
  box-shadow: var(--shadow);
  border: 2px solid transparent;
  transition: border-color .2s, transform .2s, box-shadow .2s;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}
.admin-tile:hover {
  border-color: var(--primary);
  transform: translateY(-4px);
  box-shadow: 0 8px 22px rgba(0,0,0,.12);
}
.admin-tile-icon  { font-size: 2rem; }
.admin-tile-title { font-size: .95rem; font-weight: 700; }
.admin-tile-desc  { font-size: .78rem; color: var(--mid); line-height: 1.4; }

/* ── Admin sub-page header back link ─────────── */
.header-back {
  color: rgba(255,255,255,.7);
  text-decoration: none;
  font-size: .85rem;
  white-space: nowrap;
  transition: color .2s;
  flex-shrink: 0;
}
.header-back:hover { color: #fff; }

/* ── Manage rows (walls & setters pages) ─────── */
.manage-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 4px;
  border-bottom: 1px solid var(--border);
  flex-wrap: wrap;
}
.manage-row:last-child { border-bottom: none; }
.manage-row-name  { flex: 1; font-weight: 600; font-size: .93rem; min-width: 100px; }
.manage-row-input { flex: 1; min-width: 100px; }
.manage-row-actions {
  display: flex;
  gap: 6px;
  flex-shrink: 0;
  flex-wrap: wrap;
  justify-content: flex-end;
}

/* ── Setter inline-add (create boulder page) ─── */
.setter-inline-toggle {
  background: none;
  border: none;
  color: var(--primary);
  font-size: .82rem;
  font-weight: 600;
  cursor: pointer;
  padding: 4px 0;
  margin-top: 6px;
  display: block;
}
.setter-inline-toggle:hover { text-decoration: underline; }
.setter-inline-add {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
  padding: 10px 12px;
  background: var(--light);
  border: 1px solid var(--border);
  border-radius: 8px;
  flex-wrap: wrap;
}
.setter-inline-add input { flex: 1; min-width: 140px; }

/* ── Boulder color chip ──────────────────────── */
.color-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: .72rem;
  font-weight: 600;
  color: var(--dark);
  background: var(--white);
  border: 1.5px solid var(--border);
  border-radius: 20px;
  padding: 1px 8px 1px 5px;
  margin-left: 6px;
  vertical-align: middle;
  white-space: nowrap;
}
.color-chip::before {
  content: '';
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--chip-color, #9CA3AF);
  border: 1px solid rgba(0,0,0,.12);
  flex-shrink: 0;
}

/* ── Status badge (admin toggles) ────────────── */
.status-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-weight: 600;
  font-size: .88rem;
  padding: 4px 10px;
  border-radius: 14px;
  background: var(--light);
}

/* ── Shared tinted status chip (#1488) ───────────────────────────────────────
   The one place the tinted-chip language is codified as a reusable class, so
   call sites (boards benchmark/community, competition state, finalist banner,
   any future status label) share ONE definition instead of forking colours per
   page. Base carries the shape; a `--success | --warn | --info | --neutral`
   modifier fills the (bg, text) pair from the --chip-* tokens, which the dark
   remap swaps to the SOLID night pairs — so both themes clear WCAG AA (≥4.5:1)
   with no per-selector dark override needed. Matches the existing party
   role/state badges in weight, radius and hairline. Sizing is intentionally
   compact (chip-sized); a caller that needs a larger banner sets its own
   font-size/padding (e.g. .finalist-badge) while inheriting the colours. */
.mg-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: .68rem;
  font-weight: 700;
  line-height: 1;
  padding: 3px 8px;
  border-radius: var(--r-pill);
  border: 1px solid transparent;
  white-space: nowrap;
}
.mg-chip--success { background: var(--chip-success-bg); color: var(--chip-success-fg); }
.mg-chip--warn    { background: var(--chip-warn-bg);    color: var(--chip-warn-fg); }
.mg-chip--info    { background: var(--chip-info-bg);    color: var(--chip-info-fg); }
.mg-chip--neutral { background: var(--chip-neutral-bg); color: var(--chip-neutral-fg); }

/* ── Spray Wall ─────────────────────────────── */
.spray-picker-container {
  --zoom:  1;
  --pan-x: 0px;
  --pan-y: 0px;
  position: relative;
  display: block;
  width: 100%;
  cursor: crosshair;
  border-radius: 8px;
  overflow: hidden;
  border: 2px solid var(--border);
  user-select: none;
  -webkit-user-select: none;
  touch-action: none;
}
.spray-picker-container.pannable    { cursor: grab; }
.spray-picker-container.panning,
.spray-picker-container.panning *   { cursor: grabbing !important; }
/* Inner frame – owns the image + overlay so they always have matching dims.
   In the picker we also use it as the zoom/pan transform target. */
.spray-picker-frame {
  position: relative;
  display: block;
  line-height: 0;
  transform: translate(var(--pan-x, 0px), var(--pan-y, 0px)) scale(var(--zoom, 1));
  transform-origin: center;
  transition: transform .12s ease-out;
}
.spray-picker-container.panning .spray-picker-frame { transition: none; }
/* Dots scale inversely so they stay readable when zoomed in or out */
.spray-picker-container .spray-dot {
  transform: translate(-50%, -50%)
             scale(clamp(.5, calc(.5 + .5 / var(--zoom, 1)), 1.8));
}
.spray-picker-img {
  display: block;
  width: 100%;
  height: auto;
  pointer-events: none;
}
.spray-holds-overlay {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

/* Gym-map SVG overlay (admin editor + visitor view) — sits on top of the
   image inside .spray-picker-frame, sized to the frame via inset:0. */
.gym-map-overlay {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
  pointer-events: none;
}
.gym-map-overlay * { pointer-events: auto; }

/* Segmented Wall areas / Boulders toggle on the visitor gym-map page (#212).
   Mutually-exclusive views: the highlighted button is the active layer. */
.map-view-toggle {
  display: inline-flex;
  /* Solid surface so the toggle stays legible over a light OR dark map image
     (it floats on the map; a transparent background made it unreadable). */
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: 20px;
  overflow: hidden;
  flex-shrink: 0;
  box-shadow: 0 1px 5px rgba(0, 0, 0, .22);
}
.map-view-toggle button {
  background: none;
  border: none;
  cursor: pointer;
  padding: 6px 14px;
  font-size: .85rem;
  font-weight: 600;
  color: var(--mid);
  white-space: nowrap;
}
.map-view-toggle button.on { background: var(--primary); color: #fff; }

/* Level switcher on the visitor gym-map page (#159) — multi-floor gyms only.
   Shares the segmented look of the walls/boulders toggle; wraps to a second row
   when a gym has many levels. Hidden entirely for single-level gyms. */
.map-level-switch {
  display: inline-flex;
  flex-wrap: wrap;
  /* Same solid surface as the walls/boulders toggle — both float on the map. */
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: 20px;
  overflow: hidden;
  box-shadow: 0 1px 5px rgba(0, 0, 0, .22);
}
.map-level-switch button {
  background: none;
  border: none;
  cursor: pointer;
  padding: 6px 14px;
  font-size: .85rem;
  font-weight: 600;
  color: var(--mid);
  white-space: nowrap;
  border-left: 1px solid var(--border);
}
.map-level-switch button:first-child { border-left: none; }
.map-level-switch button.on { background: var(--primary); color: #fff; }

/* Walls grid on the visitor gym-map page */
.walls-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: 12px;
}
.wall-card {
  background: var(--light);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 8px;
  text-align: center;
  cursor: pointer;
  text-decoration: none;
  color: var(--dark);
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 6px;
  transition: transform .15s, border-color .15s, box-shadow .15s;
}
.wall-card:hover {
  transform: translateY(-2px);
  border-color: var(--primary);
  box-shadow: 0 4px 12px rgba(232,115,26,.18);
}
.wall-card-img {
  width: 100%;
  aspect-ratio: 4 / 3;
  border-radius: 6px;
  object-fit: cover;
  background: #ECE6DA;
}
.wall-card-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 2rem;
  color: var(--mid);
}
.wall-card-name {
  font-size: .88rem;
  font-weight: 600;
  line-height: 1.2;
  padding: 2px 4px 4px;
}

/* Wall-gallery tile (#1304) — the "Walls" tab tiles are <button>s (they open a
   gallery, not a link), so reset the button chrome to match the .wall-card look
   used elsewhere on this page. */
button.wall-gallery-tile {
  font-family: inherit;
  width: 100%;
  appearance: none;
  -webkit-appearance: none;
}

/* Prev / next arrows on the per-wall photo gallery lightbox (#1304). Fixed on
   the left/right edges, above the image, below the close button's z-index. */
.gallery-nav {
  position: fixed;
  top: 50%;
  transform: translateY(-50%);
  width: 48px;
  height: 48px;
  border: none;
  border-radius: 50%;
  background: rgba(0, 0, 0, .55);
  color: #fff;
  font-size: 2rem;
  line-height: 1;
  cursor: pointer;
  z-index: 302;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: inherit;
  padding: 0 0 4px;   /* nudge the chevron glyph optically centred */
  -webkit-appearance: none;
  appearance: none;
}
.gallery-nav:hover  { background: rgba(0, 0, 0, .75); }
.gallery-nav:active { background: rgba(0, 0, 0, .9); }
.gallery-nav-prev { left: calc(10px + env(safe-area-inset-left)); }
.gallery-nav-next { right: calc(10px + env(safe-area-inset-right)); }

/* Floating fullscreen-toggle button (top-right corner of image) */
.spray-fs-btn {
  position: absolute;
  top: 8px;
  right: 8px;
  width: 38px;
  height: 38px;
  border-radius: 8px;
  border: none;
  background: rgba(0,0,0,.55);
  color: #fff;
  cursor: pointer;
  font-size: 1.15rem;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 2;
  font-family: inherit;
  transition: background .15s;
}
.spray-fs-btn:hover { background: rgba(0,0,0,.85); }

/* Toolbar inside fullscreen — hidden in normal mode */
.spray-fs-toolbar { display: none; }

/* ── Fullscreen mode ── */
.spray-picker-container.fullscreen {
  position: fixed;
  inset: 0;
  z-index: 200;
  width: 100vw;
  height: 100vh;
  background: rgba(0,0,0,.95);
  border: none;
  border-radius: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.spray-picker-container.fullscreen .spray-picker-frame {
  display: inline-block;
  max-width: 100vw;
  max-height: 100vh;
}
.spray-picker-container.fullscreen .spray-picker-img {
  display: block;
  width: auto;
  height: auto;
  max-width: 100vw;
  max-height: 100vh;
}
/* Hide the enter-fullscreen button while in fullscreen */
.spray-picker-container.fullscreen .spray-fs-btn { display: none; }
/* Show the fullscreen toolbar */
.spray-picker-container.fullscreen .spray-fs-toolbar {
  display: flex;
  align-items: center;
  gap: 10px;
  position: absolute;
  /* Clear the iOS status bar / notch in PWA standalone fullscreen (#125, #126) */
  top: calc(12px + env(safe-area-inset-top));
  left: calc(12px + env(safe-area-inset-left));
  right: calc(12px + env(safe-area-inset-right));
  padding: 8px 10px;
  background: rgba(255,255,255,.95);
  border-radius: 10px;
  box-shadow: 0 4px 14px rgba(0,0,0,.4);
  z-index: 1;
  flex-wrap: wrap;
}
/* ══════════════════════════════════════════════════════════════════════════
   W5 (#1428) — GYM MAP: full-bleed canvas + floating frosted control overlay.

   The map fills the viewport under the shell chrome (the mobile "worked
   example", screen-redesigns.md §1: ~+500px of live map); the mode controls
   float as ONE frosted overlay chip row (.mg-frost) instead of four stacked
   bars consuming layout above the map. Mirrors the mobile #1424 G-series (F2):
   the mode segmented controls always FULLY FIT at narrow widths (no horizontal
   scroll), and the Level switcher sits on its OWN row below them so it can
   never overlap the mode pills at any width.

   Structure is unchanged for JS: the same #mapContainer / #levelControls /
   #boulderControls / #viewSwitchWrap elements keep their IDs and their JS-driven
   style.display values — this is CSS + a wrapper. The `.gym-map-immersive` scope
   on #mapCard opts the page into the full-bleed treatment. ════════════════════ */
.gym-map-immersive.card {
  /* Shed the card chrome — the map IS the content, edge-to-edge. Break out of
     the .container gutters so the canvas is truly full-bleed within the column,
     and stand a tall live map up under the sticky header. */
  position: relative;
  padding: 0;
  border: none;
  background: none;
  box-shadow: none;
  border-radius: var(--r-card);
  overflow: hidden;
  margin: 0 0 20px;
  /* Fill the viewport below the ~56px sticky header; a floor keeps it usable on
     short screens. dvh where supported so mobile browser chrome doesn't clip it. */
  height: calc(100vh - 148px);
  min-height: 420px;
}
@supports (height: 100dvh) {
  .gym-map-immersive.card { height: calc(100dvh - 148px); }
}
/* The map canvas fills the immersive shell. The picker container was width:100%
   / height:auto (its height followed the image); here it fills the tall region
   and CENTRES the frame, while the frame SHRINK-WRAPS a contained image (same
   technique as the existing .fullscreen rules) — crucially so the frame box
   still exactly matches the rendered image, keeping the inset:0 SVG overlays
   (wall polygons + boulder dots, preserveAspectRatio="none") aligned and the
   pan/zoom math (which reads #mapFrame's box) unchanged. */
.gym-map-immersive #mapContainer {
  height: 100%;
  border: none;
  border-radius: var(--r-card);
  display: flex;
  align-items: center;
  justify-content: center;
}
.gym-map-immersive #mapFrame {
  display: inline-block;
  max-width: 100%;
  max-height: 100%;
}
.gym-map-immersive #mapImg {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
}
/* The floating control overlay: a top-leading frosted stack over the map. Row 1
   is the mode controls (they fully fit / wrap, never scroll); row 2 is the level
   switcher on its OWN line so it can never overlap the mode pills (G-series F2).
   pointer-events pass through the gaps to the map; only the chips catch clicks. */
.map-float-controls {
  position: absolute;
  top: calc(10px + env(safe-area-inset-top));
  left: calc(10px + env(safe-area-inset-left));
  right: calc(56px + env(safe-area-inset-right)); /* clear the ⛶ fullscreen btn */
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  pointer-events: none;
}
.map-float-controls > * { pointer-events: auto; }
/* Row 1 — the mode controls wrap onto multiple lines if narrow (never a
   horizontal scroll); each segmented pill stays intact via its own nowrap. */
.map-float-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  max-width: 100%;
}
/* The frosted chrome behind each floating cluster. .mg-frost already carries the
   dark / no-blur / reduced-transparency fallbacks; we add the pill radius + a
   little padding so the segmented controls read as one floating chip. */
.map-float-chip {
  border-radius: var(--r-pill);
  padding: 4px;
}
/* Compact, non-scrolling segmented controls (G-series F2): tighten the segments
   so Gym Map|Walls and Zones|Boulders always fully fit at 320–430px without a
   scroll, and drop the now-redundant solid surface (the frost provides it). */
.map-float-controls .map-view-toggle,
.map-float-controls .map-level-switch {
  background: none;
  border: none;
  box-shadow: none;
  max-width: 100%;
}
.map-float-controls .map-view-toggle button,
.map-float-controls .map-level-switch button {
  padding: 6px 11px;
  font-size: .82rem;
}
/* The grade/style filter selects (shown only in the Boulders view) sit inline in
   the mode row on their own frosted chip so they stay reachable over the map. */
.map-float-controls .filter-inline {
  display: flex;
  gap: 8px;
  align-items: center;
  flex-wrap: wrap;
}
.map-float-controls select {
  font-size: .82rem;
  padding: 5px 9px;
  border-radius: var(--r-chip);
  border: 1px solid var(--border);
  background: var(--white);
  color: var(--dark);
}
/* Bottom-leading hint pill — "tap a wall …" as a frosted caption over the map
   (replaces the in-body hint paragraph the immersive layout drops). */
.map-hint-pill {
  position: absolute;
  left: calc(10px + env(safe-area-inset-left));
  bottom: calc(10px + env(safe-area-inset-bottom));
  z-index: 3;
  max-width: min(320px, calc(100% - 20px));
  padding: 8px 13px;
  border-radius: var(--r-pill);
  font-size: .78rem;
  font-weight: 600;
  color: var(--dark);
}
/* The map legend, when shown, floats bottom-trailing as a frosted chip instead
   of pushing layout below the (now full-bleed) map. */
.gym-map-immersive .spray-legend {
  position: absolute;
  right: calc(10px + env(safe-area-inset-right));
  bottom: calc(10px + env(safe-area-inset-bottom));
  z-index: 3;
  max-width: min(60%, 340px);
  margin: 0;
  padding: 8px 12px;
  border-radius: var(--r-inner);
  box-shadow: var(--shadow-float);
}
/* Top-level Gym Map | Walls switch — a compact frosted chip that overlaps the
   top of the full-bleed map. It stays a page-level tab (visible in both the map
   and walls panes), so it is NOT inside the map overlay. */
.map-top-switch {
  margin-bottom: 12px;
}
.map-view-toggle--frost {
  background: none;   /* the .mg-frost provides the surface */
  border: none;
  box-shadow: none;
}
.map-view-toggle--frost.mg-frost {
  padding: 3px;
  border-radius: var(--r-pill);
}
.map-view-toggle--frost button { padding: 6px 13px; }
/* Narrow screens: the map fills more of the shorter viewport; keep the floor. */
@media (max-width: 760px) {
  .gym-map-immersive.card { height: calc(100vh - 128px); }
  @supports (height: 100dvh) {
    .gym-map-immersive.card { height: calc(100dvh - 128px); }
  }
}

.spray-dot {
  position: absolute;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 2.5px solid rgba(255,255,255,.9);
  transform: translate(-50%, -50%);
  pointer-events: all;
  cursor: pointer;
  box-shadow: 0 1px 5px rgba(0,0,0,.55);
  transition: transform .1s;
}
.spray-dot:hover { transform: translate(-50%,-50%) scale(1.3); }
.spray-dot-start { background: #22C55E; }
.spray-dot-feet  { background: #60A5FA; }
.spray-dot-hand  { background: #F59E0B; }
.spray-dot-top   { background: #EF4444; }
/* All dots are slightly transparent so the hold underneath shows through —
   in the picker, hovering returns the dot to full opacity for confirmation. */
.spray-dot { opacity: .72; }
.spray-picker-container .spray-dot:hover { opacity: 1; }
/* Smaller dots for list thumbnails and lightbox view */
.spray-dot-sm {
  width: 12px;
  height: 12px;
  border-width: 1.5px;
  pointer-events: none;
  cursor: default;
}
.spray-dot-sm:hover { transform: translate(-50%,-50%); }

/* Hold type selector buttons */
.hold-type-btns {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}
.hold-type-btn {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 7px 14px;
  border-radius: 20px;
  border: 2px solid var(--border);
  font-size: .88rem;
  font-weight: 600;
  cursor: pointer;
  background: var(--light);
  color: var(--dark);
  transition: border-color .15s, color .15s, background .15s;
  font-family: inherit;
  -webkit-appearance: none;
  appearance: none;
}
.hold-type-btn .hold-dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  flex-shrink: 0;
  border: 1.5px solid rgba(0,0,0,.1);
}
.hold-type-btn[data-type="start"] .hold-dot { background: #22C55E; }
.hold-type-btn[data-type="feet"]  .hold-dot { background: #60A5FA; }
.hold-type-btn[data-type="hand"]  .hold-dot { background: #F59E0B; }
.hold-type-btn[data-type="top"]   .hold-dot { background: #EF4444; }
.hold-type-btn[data-type="start"].active { color: #16A34A; border-color: #22C55E; background: #F0FDF4; }
.hold-type-btn[data-type="feet"].active  { color: #2563EB; border-color: #60A5FA; background: #EFF6FF; }
.hold-type-btn[data-type="hand"].active  { color: #D97706; border-color: #F59E0B; background: #FFFBEB; }
.hold-type-btn[data-type="top"].active   { color: #DC2626; border-color: #EF4444; background: #FEF2F2; }

/* Hold summary row */
.hold-summary {
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
  font-size: .82rem;
  color: var(--mid);
  margin: 10px 0 4px;
}
.hold-summary-item { display: flex; align-items: center; gap: 5px; }
.hold-summary-dot  { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }

/* Toggle row (checkbox with label, used in the spray boulder form) */
.toggle-row {
  display: flex;
  align-items: center;
  gap: 10px;
  cursor: pointer;
  user-select: none;
}
.toggle-row input[type="checkbox"] {
  width: 18px;
  height: 18px;
  accent-color: var(--primary);
  flex-shrink: 0;
  cursor: pointer;
}
.toggle-row .toggle-text { font-weight: 500; }
.toggle-row .toggle-hint { font-size: .78rem; color: var(--mid); margin-left: 4px; }

/* "Feet follow hands" badge — focusable, shows tooltip on hover/tap */
.ffh-badge {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border: 1.5px solid #60A5FA;
  background: #EFF6FF;
  border-radius: 50%;
  font-size: .95rem;
  cursor: pointer;
  padding: 0;
  font-family: inherit;
  line-height: 1;
  -webkit-appearance: none;
  appearance: none;
}
.ffh-badge:hover,
.ffh-badge:focus {
  background: #DBEAFE;
  outline: 2px solid #60A5FA;
  outline-offset: 1px;
}
.ffh-badge::after {
  /* #1227: dynamic, not hardcoded — this class is shared by every rule badge
     (Feet follow hands 👣, No matching 🙅); each instance's own aria-label
     is the source of truth for its tooltip. A static string here would show
     the wrong rule's name on every OTHER badge sharing the class. */
  content: attr(aria-label);
  position: absolute;
  bottom: 130%;
  left: 50%;
  transform: translateX(-50%);
  background: rgba(0,0,0,.92);
  color: #fff;
  padding: 4px 9px;
  border-radius: 5px;
  font-size: .72rem;
  font-weight: 500;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity .15s;
  z-index: 10;
}
.ffh-badge:hover::after,
.ffh-badge:focus::after { opacity: 1; }

/* Colour legend (shown above the active boulder list + in the lightbox) */
.spray-legend {
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
  font-size: .85rem;
  font-weight: 500;
  align-items: center;
  padding: 8px 12px;
  background: var(--light);
  border-radius: 8px;
  margin-bottom: 12px;
}
.spray-legend-label {
  font-size: .78rem;
  color: var(--mid);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .04em;
  margin-right: 2px;
}
.spray-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}
.spray-legend-dot {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  display: inline-block;
  border: 1.5px solid rgba(255,255,255,.95);
  box-shadow: 0 0 0 1px rgba(0,0,0,.18);
}

/* ── Style-icon legend (collapsible, collapsed by default) ──
   Shared by the Spray Wall + My Stats pages: maps each style icon to its name.
   Built dynamically from /api/boulder-styles. Uses the native <details>/<summary>
   disclosure so it's keyboard-accessible with no JS toggle. */
.style-legend {
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--light);
  margin-bottom: 12px;
  font-size: .85rem;
}
.style-legend > summary {
  cursor: pointer;
  list-style: none;
  padding: 8px 12px;
  font-size: .78rem;
  font-weight: 600;
  color: var(--mid);
  text-transform: uppercase;
  letter-spacing: .04em;
  display: flex;
  align-items: center;
  gap: 6px;
}
.style-legend > summary::-webkit-details-marker { display: none; }
.style-legend > summary::before {
  content: "▸";
  font-size: .8rem;
  transition: transform .15s ease;
}
.style-legend[open] > summary::before { transform: rotate(90deg); }
.style-legend-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 14px;
  padding: 2px 12px 12px;
}
.style-legend-grid .style-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  color: var(--dark);
  font-weight: 500;
}
.style-legend-grid .style-legend-item .ic { font-size: 1.05rem; }

/* Row holding a primary action on the left and the style-icon legend button on
   the right (spray wall Create row + boulder-list control bar). */
.list-head-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 12px; }
.list-head-row .style-legend.legend-pop { margin-left: auto; }

/* Compact, button-styled style-icon legend that opens as a dropdown popover.
   A modifier on .style-legend so the member-stats inline box is untouched. */
.style-legend.legend-pop { position: relative; border: none; background: none; margin: 0; flex-shrink: 0; }
.style-legend.legend-pop > summary {
  border: 1.5px solid var(--border);
  border-radius: 10px;
  background: var(--white, #fff);
  color: var(--dark);
  padding: 8px 14px;
  white-space: nowrap;
}
.style-legend.legend-pop > summary:hover { border-color: var(--primary); }
.style-legend.legend-pop[open] > summary { border-color: var(--primary); color: var(--primary); }
/* Belt-and-braces: keep the panel hidden while closed (some browsers render a
   child with an explicit `display` even on a closed <details>). */
.style-legend.legend-pop:not([open]) > .style-legend-grid { display: none; }
.style-legend.legend-pop > .style-legend-grid {
  position: absolute;
  right: 0;
  top: calc(100% + 6px);
  z-index: 40;
  flex-direction: column;
  flex-wrap: nowrap;
  gap: 8px;
  min-width: 170px;
  max-width: 280px;
  max-height: 60vh;
  overflow: auto;
  padding: 12px 14px;
  background: var(--white, #fff);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 10px 28px rgba(0,0,0,.14);
}

/* Lightbox – fullscreen image area + caption strip at the bottom */
#lightbox { --zoom: 1; --pan-x: 0px; --pan-y: 0px; }

.lightbox-img-area {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 8px;
  position: relative;
  overflow: hidden;
  cursor: zoom-out;
  touch-action: none;
}
/* When zoomed in, the area becomes pan-able */
.lightbox-img-area.pannable    { cursor: grab; }
.lightbox-img-area.panning,
.lightbox-img-area.panning *   { cursor: grabbing !important; }

.lightbox-img-frame {
  position: relative;
  display: block;
  line-height: 0;
  max-width: 100%;
  max-height: 100%;
  /* aspect-ratio is set from JS once the image is loaded (#lightbox.--img-aspect),
     which lets the frame size to the image's true ratio while honouring
     max-width / max-height — no circular sizing dependency. */
  aspect-ratio: var(--img-aspect, 1);
  transform: translate(var(--pan-x, 0px), var(--pan-y, 0px)) scale(var(--zoom, 1));
  transform-origin: center;
  transition: transform .12s ease-out;
}
/* No transition while actively dragging — keeps panning snappy */
.lightbox-img-area.panning .lightbox-img-frame { transition: none; }
.lightbox-img-frame img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: fill;   /* frame already matches the image's aspect ratio */
}
#lightboxDots {
  position: absolute;
  inset: 0;
  pointer-events: none;
}
/* Dots partially compensate for the frame scale so they stay legible
   without dominating or disappearing.  At zoom 1 they are full-size;
   at zoom 2 ≈ 0.75×; at zoom 0.5 ≈ 1.5× (clamped to [0.5, 1.8]). */
.lightbox-img-frame .spray-dot {
  transform: translate(-50%, -50%)
             scale(clamp(.5, calc(.5 + .5 / var(--zoom, 1)), 1.8));
}

/* Zoom controls (top-left, mirroring the close button) */
.lightbox-zoom-controls {
  position: fixed;
  /* PWA standalone: clear the iOS status bar / notch (0 on non-notched). */
  top: calc(14px + env(safe-area-inset-top));
  left: calc(16px + env(safe-area-inset-left));
  z-index: 302;
  display: flex;
  align-items: center;
  gap: 4px;
  background: rgba(0,0,0,.55);
  padding: 4px;
  border-radius: 10px;
}
.lightbox-zoom-controls button {
  width: 36px;
  height: 36px;
  border: none;
  border-radius: 7px;
  background: rgba(255,255,255,.12);
  color: #fff;
  cursor: pointer;
  font-size: 1.1rem;
  font-weight: 600;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: inherit;
  padding: 0;
  line-height: 1;
}
.lightbox-zoom-controls button:hover  { background: rgba(255,255,255,.28); }
.lightbox-zoom-controls button:active { background: rgba(255,255,255,.4); }
.lightbox-zoom-controls .zoom-reset   { font-size: .82rem; padding: 0 10px; width: auto; min-width: 48px; }
.lightbox-zoom-controls #zoomLevel {
  color: #fff;
  font-size: .82rem;
  font-weight: 600;
  min-width: 48px;
  text-align: center;
  padding: 0 4px;
}

.lightbox-caption {
  flex-shrink: 0;
  background: rgba(255,255,255,.97);
  /* Extra bottom padding clears the home indicator (PWA); background still
     reaches the screen edge, so there's no gap below the caption. */
  padding: 10px 16px calc(12px + env(safe-area-inset-bottom));
  font-size: .88rem;
  color: var(--dark);
  display: flex;
  flex-direction: column;
  gap: 4px;
  align-items: center;
  text-align: center;
  box-shadow: 0 -4px 14px rgba(0,0,0,.35);
  z-index: 301;
}
.lightbox-caption .spray-legend {
  margin: 4px 0 0;
  padding: 6px 12px;
  background: transparent;
}

/* #1451 W2 — dark / night skin: the caption's near-white base bg stayed while
   `color: var(--dark)` flips to chalk in night mode → near-white text on near-white
   (≈1.13:1, invisible, most visible in the gym-map gallery). Pin the caption to a
   raised night surface with chalk text (mirrors the .lightbox-sheet override). */
:root[data-theme="dark"] .lightbox-caption {
  background: var(--night-surface);
  color: var(--chalk);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .lightbox-caption {
    background: var(--night-surface);
    color: var(--chalk);
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   Image-first boulder lightbox (#1439)
   ──────────────────────────────────────────────────────────────────────────
   The wall photo + holds overlay is the hero. On portrait phones the image
   area takes the majority of the viewport and the metadata drops into a lower
   SHEET that scrolls on its own — it never squeezes the image into a strip
   (the bug this wave fixes). On wide screens the layout flips to side-by-side
   (image left, metadata panel right). A fullscreen affordance collapses to just
   the image filling the screen; the zoom/pan/pinch math (lightbox.js) is reused
   verbatim. Scoped to `.lightbox-imgfirst` so it only touches this lightbox.
   ══════════════════════════════════════════════════════════════════════════ */

/* — Portrait / narrow (default): image is the hero, sheet scrolls below — */
.lightbox-imgfirst .lightbox-img-area {
  /* Grow to take the majority of the viewport; the sheet is capped below so
     the image is never letterboxed into a strip. */
  flex: 1 1 auto;
  min-height: 55vh;
}
@supports (min-height: 55dvh) {
  .lightbox-imgfirst .lightbox-img-area { min-height: 55dvh; }
}

/* The metadata SHEET — an independently-scrolling panel with a capped height,
   rounded top corners and a grab handle, matching the W3 sheet standard. */
.lightbox-imgfirst .lightbox-sheet {
  flex: 0 0 auto;
  max-height: 44vh;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  border-radius: var(--r-sheet) var(--r-sheet) 0 0;
  padding-top: 4px;
  gap: 6px;
}
@supports (max-height: 44dvh) {
  .lightbox-imgfirst .lightbox-sheet { max-height: 44dvh; }
}
.lightbox-sheet-grab {
  flex: 0 0 auto;
  width: 40px;
  height: 4px;
  border-radius: var(--r-pill);
  background: var(--border);
  margin: 6px auto 8px;
}
.lightbox-imgfirst .lightbox-sheet #lightboxInfo { width: 100%; }

/* — Fullscreen affordance — an expand control pinned to the image corner. It
   sits opposite the close button; the zoom controls stay top-left. — */
.lightbox-fs-btn {
  position: absolute;
  top: calc(10px + env(safe-area-inset-top));
  right: calc(66px + env(safe-area-inset-right));   /* clear of the ✕ close btn */
  width: 40px;
  height: 40px;
  border-radius: 50%;
  border: none;
  background: rgba(0,0,0,.55);
  color: #fff;
  cursor: pointer;
  font-size: 1.3rem;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 302;
  font-family: inherit;
  transition: background .15s;
}
.lightbox-fs-btn:hover  { background: rgba(0,0,0,.8); }
.lightbox-fs-btn:active { background: rgba(0,0,0,.9); }
/* Exit-fullscreen pill — hidden until fullscreen is active. */
.lightbox-fs-exit {
  display: none;
  position: fixed;
  left: calc(16px + env(safe-area-inset-left));
  bottom: calc(16px + env(safe-area-inset-bottom));
  z-index: 303;
  padding: 9px 16px;
  border-radius: var(--r-pill);
  border: none;
  background: rgba(0,0,0,.62);
  color: #fff;
  font-size: .9rem;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
}
.lightbox-fs-exit:hover { background: rgba(0,0,0,.82); }

/* — Fullscreen state — the image fills the whole viewport; the sheet, legend
   and enter-fullscreen button hide. The transform math is untouched, so the
   holds overlay stays projected on the frame at the current zoom. — */
.lightbox-imgfirst.lightbox-fs .lightbox-sheet { display: none; }
.lightbox-imgfirst.lightbox-fs .lightbox-img-area {
  min-height: 0;
  padding: 0;
}
.lightbox-imgfirst.lightbox-fs .lightbox-fs-btn { display: none; }
.lightbox-imgfirst.lightbox-fs .lightbox-fs-exit { display: block; }

/* — Wide screens (landscape phones, tablets, desktop): side-by-side. Image on
   the left takes the remaining width; the metadata panel is a fixed column on
   the right that scrolls independently and is full-height (no sheet cap). — */
@media (min-width: 900px) {
  .lightbox-imgfirst { flex-direction: row; }
  .lightbox-imgfirst .lightbox-img-area { min-height: 0; }
  .lightbox-imgfirst .lightbox-sheet {
    flex: 0 0 380px;
    max-height: none;
    height: 100%;
    border-radius: 0;
    box-shadow: -4px 0 14px rgba(0,0,0,.35);
    align-items: stretch;
    text-align: left;
  }
  .lightbox-imgfirst .lightbox-sheet-grab { display: none; }
  /* In side-by-side the exit-fullscreen pill can stay bottom-left; the enter
     button remains at the image corner. */
}

/* — Compact grade pills (#1439): the two big stacked CREATOR / COMMUNITY cards
   become the app's compact grade-pill pattern — a horizontal pill per grade
   (kind label · badge · votes) that stays side-by-side and only wraps when
   genuinely out of room, instead of dominating the sheet. — */
.lightbox-imgfirst .wallb-grades {
  gap: 8px;
  margin: 10px 0;
  width: 100%;
}
.lightbox-imgfirst .wallb-grade-col {
  flex: 1 1 auto;
  flex-direction: row;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: 6px;
  padding: 8px 10px;
  border-radius: var(--r-chip);
  text-align: left;
}
.lightbox-imgfirst .wallb-grade-kind {
  flex: 0 0 auto;
  font-size: .62rem;
}
.lightbox-imgfirst .wallb-grade-col .g-badge { height: 26px; min-width: 30px; font-size: .92rem; }
.lightbox-imgfirst .wallb-grade-none { font-size: 1.05rem; }
.lightbox-imgfirst .wallb-grade-votes { flex-basis: 100%; text-align: center; }
/* The "vs" separator is redundant once the cards are compact pills. */
.lightbox-imgfirst .wallb-grade-sep { display: none; }

/* — Collapsible legend (#1439): a <details> so it stays available but never
   crowds the sheet by default. — */
.lightbox-legend-collapse {
  width: 100%;
  display: block;
  margin-top: 6px;
}
.lightbox-legend-collapse > summary {
  cursor: pointer;
  list-style: none;
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.lightbox-legend-collapse > summary::-webkit-details-marker { display: none; }
.lightbox-legend-collapse > summary::after {
  content: "▾";
  font-size: .7rem;
  opacity: .6;
  transition: transform .15s;
}
.lightbox-legend-collapse[open] > summary::after { transform: rotate(180deg); }
.lightbox-legend-collapse[open] {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 10px;
}

/* Dark / night skin — the sheet reads as a raised night surface, not white. */
:root[data-theme="dark"] .lightbox-imgfirst .lightbox-sheet {
  background: var(--night-surface);
  color: var(--chalk);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .lightbox-imgfirst .lightbox-sheet {
    background: var(--night-surface);
    color: var(--chalk);
  }
}

/* Reduced-motion: no rotate animation on the legend caret or sheet transitions. */
@media (prefers-reduced-motion: reduce) {
  .lightbox-legend-collapse > summary::after { transition: none; }
  .lightbox-fs-btn { transition: none; }
}

/* Spray boulder list cards */
/* Mirror the boulder list (#191): a responsive grid of compact cards rather
   than one full-width column of large photos. Every spray boulder shares the
   same wall photo with percentage-positioned hold dots, so the preview keeps
   its natural aspect ratio (no object-fit cropping, which would shift the
   dots) — the narrow column width is what turns it into a thumbnail. */
.spray-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 12px; }
/* On phones a single column would still render the wall photo full-width and
   tall (it keeps its aspect ratio for dot alignment). Two columns keeps the
   previews genuinely thumbnail-sized — the whole point of #191. */
@media (max-width: 560px) { .spray-list { grid-template-columns: repeat(2, 1fr); gap: 10px; } }
.spray-card {
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
  background: var(--white);
  cursor: pointer;
  transition: border-color .2s;
}
.spray-card:hover { border-color: var(--primary); }
/* #1227: a fixed aspect ratio (not "auto height = the photo's own ratio")
   so every card's image area — and therefore its footer — sits at the SAME
   height across the grid, whether or not a photo exists. The holds dots/SVG
   (viewBox 0 0 100 100, preserveAspectRatio="none") already stretch to fill
   whatever box they're given, so making the PHOTO do the same (object-fit:
   fill, not cover/contain) keeps every hold's position accurate relative to
   the photo beneath it — no cropping, so nothing is ever hidden, just a
   mild proportional stretch for a photo whose natural ratio isn't 4:3. */
.spray-preview-wrap {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 3;
  background: #111;
  line-height: 0;
  overflow: hidden;
}
.spray-preview-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: fill;
  opacity: .82;
}
/* Shown in place of the photo when a card has none, at the SAME fixed box
   size, so the card footer still lands in the same place as a photo card. */
.spray-preview-placeholder {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 2rem;
  opacity: .35;
}
.spray-card-body  { padding: 10px 14px 12px; }
.spray-card-name  { font-weight: 700; font-size: .95rem; color: var(--dark); }
.spray-card-meta  { font-size: .78rem; color: var(--mid); margin-top: 3px; }

/* ── Redesigned spray card layout (per-line) ───────── */
/* Line 1: name + grade badge + FFH icon, forced onto a single line. The name
   truncates with an ellipsis inside the card (shown in full in the lightbox). */
.spray-card-title {
  display: flex; align-items: center; gap: 8px;
  min-width: 0;                /* allow the name to shrink so ellipsis kicks in */
  flex-wrap: nowrap;
}
.spray-card-title .spray-card-name {
  flex: 1 1 auto; min-width: 0;
  text-overflow: ellipsis; white-space: nowrap; overflow: hidden;
}
.spray-card-title .g-badge,
.spray-card-title .ffh-badge { flex: 0 0 auto; }
/* Line 2: "Set by …" in smaller text so it stays compact. */
.spray-card-setter { font-size: .72rem; color: var(--mid); margin-top: 2px; }
/* Lines 3 & 4: a control on the left, a control/button on the right. */
.spray-card-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px; margin-top: 8px; flex-wrap: nowrap;
}
.spray-card-row-left  { display: flex; align-items: center; gap: 6px; min-width: 0; flex-wrap: wrap; }
.spray-card-row-right { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
.spray-card-row .spray-styles { margin-top: 0; }

/* Spray boulder style icons (shown under the name) */
.spray-styles { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
.spray-styles .spray-style {
  width: 24px; height: 24px; display: inline-flex; align-items: center;
  justify-content: center; font-size: .95rem; line-height: 1;
  border: 1px solid var(--border); border-radius: 6px; background: var(--light);
}
/* Spray style picker (create form) */
.spray-style-pick { display: flex; flex-wrap: wrap; gap: 6px; }
.spray-style-pick .style-pick-chip {
  padding: 6px 12px; border: 2px solid var(--border); border-radius: 6px;
  background: none; cursor: pointer; font-size: .85rem; transition: all .15s;
  display: inline-flex; align-items: center; gap: 5px;
}
.spray-style-pick .style-pick-chip .ic { font-size: 1.05rem; line-height: 1; }
.spray-style-pick .style-pick-chip.on {
  border-color: var(--primary); background: var(--primary-light); color: var(--primary);
}

/* ── Special note chip ───────────────────────── */
.special-note {
  display: inline-block;
  background: var(--primary-light);
  color: var(--primary-dark);
  font-size: .72rem;
  font-style: italic;
  border-radius: 5px;
  padding: 1px 7px;
  margin-left: 6px;
  vertical-align: middle;
  white-space: nowrap;
}

/* ── Member header button ────────────────────────────────────── */
.member-header-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  background: rgba(255,255,255,.12);
  border: 1.5px solid rgba(255,255,255,.22);
  border-radius: 20px;
  padding: 5px 12px 5px 7px;
  color: #fff;
  font-size: .82rem;
  font-weight: 600;
  cursor: pointer;
  text-decoration: none;
  transition: background .15s, border-color .15s;
  white-space: nowrap;
  flex-shrink: 0;
  line-height: 1;
}
.member-header-btn:hover {
  background: rgba(255,255,255,.22);
  border-color: rgba(255,255,255,.5);
  color: #fff;
}
.member-header-avatar {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  object-fit: cover;
  border: 1.5px solid rgba(255,255,255,.5);
  display: block;
  flex-shrink: 0;
  background: var(--mid);
}
.member-header-icon { font-size: 1rem; line-height: 1; }

/* ── Logged-in account menu (member-menu.js) ─────────────────── */
.member-menu-wrap { position: relative; display: inline-block; flex-shrink: 0; }
.member-menu-trigger { font: inherit; }
.member-menu-caret { font-size: .7rem; opacity: .8; transition: transform .15s; }
.member-menu-wrap.open .member-menu-caret { transform: rotate(180deg); }
.member-menu-name {
  max-width: 140px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.member-menu-panel {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  min-width: 200px;
  background: var(--white);
  border: 1px solid var(--border, #e5e7eb);
  border-radius: var(--r-card);   /* W3: softer panel corners */
  box-shadow: var(--shadow-float);
  padding: 6px;
  z-index: 1000;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-6px);
  transition: opacity .14s, transform .14s, visibility .14s;
  /* On short viewports (e.g. a phone in landscape) the menu can be taller than
     the screen — cap it to what's visible and let it scroll instead of spilling
     off-screen / getting clipped. */
  max-height: calc(100vh - 64px);
  max-height: calc(100dvh - 64px);
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}
.member-menu-wrap.open .member-menu-panel {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}
/* Scoped under .member-menu-panel so these beat `.header-nav a` (which is
   near-white for the dark header) — otherwise the menu text is white-on-white. */
.member-menu-panel .member-menu-item {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 9px 12px;
  border-radius: 8px;
  color: var(--dark, #1f2937);
  font-size: .88rem;
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
  transition: background .12s, color .12s;
}
.member-menu-panel .member-menu-item:hover { background: var(--light, #f3f4f6); color: var(--dark, #1f2937); }
.member-menu-panel .member-menu-logout { color: #DC2626; }
.member-menu-panel .member-menu-logout:hover { background: #FEF2F2; color: #DC2626; }
.member-menu-sep { height: 1px; background: var(--border, #e5e7eb); margin: 6px 8px; }
/* Aggregate Social badge on the single account-menu Social link (epic #1410).
   Tokenized here so it's night-safe; member-menu.js just adds the class. */
.member-menu-panel .member-menu-badge {
  margin-left: auto; display: inline-flex; align-items: center; justify-content: center;
  min-width: 18px; height: 18px; padding: 0 6px; border-radius: var(--r-pill);
  background: #EF4444; color: #fff; font-size: .7rem; font-weight: 700; line-height: 1;
}
/* Active-gym chip + switcher in the header (P4.5) — shown only when multi-gym is on. */
.mg-gym-chip-wrap { position: relative; display: inline-flex; margin-right: 8px; }
/* Note: rules are scoped under .mg-gym-* (and use !important on colour) so they
   beat the generic `.header-nav a { color:#fff }` the chip lives inside. */
.header-nav .mg-gym-chip { display: inline-flex; align-items: center; gap: 5px; font-size: .8rem;
  font-weight: 600; color: var(--dark, #1f2937) !important; background: var(--light, #f3f4f6);
  border: 1px solid var(--border, #e5e7eb); border-radius: 999px; padding: 3px 10px;
  max-width: 40vw; cursor: default; font-family: inherit; }
.mg-gym-chip-wrap.switchable .mg-gym-chip { cursor: pointer; }
.mg-gym-chip .mg-gym-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 30vw; }
.mg-gym-logo { width: 18px; height: 18px; border-radius: 4px; object-fit: cover; vertical-align: middle; }
.mg-gym-chip .mg-gym-caret { font-size: .7rem; color: var(--mid, #6b7280) !important; }
.mg-gym-panel { position: absolute; top: calc(100% + 6px); left: 0; min-width: 200px; z-index: 60;
  background: var(--white); border: 1px solid var(--border, #e5e7eb); border-radius: 10px;
  box-shadow: 0 8px 24px rgba(0,0,0,.12); padding: 5px; display: none;
  /* Scroll the gym list rather than overflowing the viewport on short screens. */
  max-height: calc(100vh - 64px); max-height: calc(100dvh - 64px);
  overflow-y: auto; overscroll-behavior: contain; -webkit-overflow-scrolling: touch; }
.mg-gym-chip-wrap.open .mg-gym-panel { display: block; }
.mg-gym-panel .mg-gym-opt { display: flex; align-items: center; gap: 7px; padding: 7px 10px; border-radius: 7px;
  font-size: .87rem; color: var(--dark, #1f2937) !important; text-decoration: none; white-space: nowrap; }
.mg-gym-panel .mg-gym-opt:hover { background: var(--light, #f3f4f6); }
.mg-gym-panel .mg-gym-opt.is-active { font-weight: 700; }
.mg-gym-panel .mg-gym-opt .gym-active-tick { color: var(--primary, #2563eb) !important; margin-left: auto; }
/* Per-row gym badge on My Sends / My Favorites (P4.5) — shows which gym a climb is in. */
.row-gym-badge { display: inline-block; font-size: .68rem; font-weight: 700; vertical-align: middle;
  background: var(--light, #f3f4f6); color: var(--mid, #6b7280);
  border: 1px solid var(--border, #e5e7eb); border-radius: 999px; padding: 1px 7px; margin-left: 6px; }
/* Gym switcher (P4.5) — a labelled group at the top of the account menu. */
.member-menu-gymlabel { font-size: .68rem; text-transform: uppercase; letter-spacing: .04em;
  color: var(--muted, #9ca3af); padding: 4px 14px 2px; font-weight: 700; }
.member-menu-panel .member-gym-item.is-active { font-weight: 700; cursor: default; }
.member-menu-panel .member-gym-item .gym-active-tick { color: var(--primary, #2563eb); margin-left: 4px; }
/* A small hover bridge so the menu doesn't close in the 8px gap. */
.member-menu-wrap::after { content: ""; position: absolute; top: 100%; right: 0; left: 0; height: 10px; }

/* ── Member auth page ────────────────────────────────────────── */
.auth-tabs {
  display: flex;
  gap: 0;
  border-bottom: 2px solid var(--border);
  margin-bottom: 24px;
}
.auth-tab {
  padding: 10px 22px;
  font-size: .95rem;
  font-weight: 600;
  color: var(--mid);
  border: none;
  background: none;
  cursor: pointer;
  border-bottom: 2px solid transparent;
  margin-bottom: -2px;
  transition: color .15s;
  font-family: inherit;
}
.auth-tab.active { color: var(--primary); border-bottom-color: var(--primary); }

.oauth-divider {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 20px 0;
  color: var(--mid);
  font-size: .82rem;
  font-weight: 600;
}
.oauth-divider::before,
.oauth-divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--border);
}
/* ── Branded social sign-in buttons (#1070) ──────────────────────────────────
   Each provider follows its own brand guidelines: Apple's "Sign in with Apple"
   HIG (black variant, since the page is light-themed) and Google's Sign-In
   branding (white button, thin grey border, the 4-colour "G" mark). They share
   one height / corner radius so the stack reads as a balanced set. The official
   logos ship as inline SVGs in the markup (.oauth-logo); we never recolour the
   Google mark. The disabled "(coming soon)" state (dark-until-configured, D5)
   is rendered as a neutral greyed pill so it doesn't masquerade as a live
   branded button before the owner configures the provider. */
.oauth-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  width: 100%;
  min-height: 44px;
  padding: 0 16px;
  border: 1px solid transparent;
  border-radius: 8px;
  font-size: .95rem;
  font-weight: 600;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica,
               Arial, sans-serif;
  letter-spacing: .01em;
  cursor: pointer;
  margin-bottom: 10px;
}
/* Apple — black button, white wordmark + logo (HIG). */
.oauth-btn[data-provider="apple"] {
  background: #000;
  color: #fff;
  border-color: #000;
}
.oauth-btn[data-provider="apple"]:hover:not(:disabled) { background: #1a1a1a; }
/* Google — white button, thin grey border, dark text. */
.oauth-btn[data-provider="google"] {
  background: #fff;
  color: #1f1f1f;
  border-color: #dadce0;
}
.oauth-btn[data-provider="google"]:hover:not(:disabled) { background: #f8f9fa; }
/* Disabled (coming soon) — neutral, never branded. */
.oauth-btn:disabled {
  background: var(--white);
  color: var(--mid);
  border-color: var(--border);
  cursor: not-allowed;
  opacity: .55;
}
/* The greyed mark inherits the disabled text colour; the live mark is brand. */
.oauth-btn:disabled .oauth-logo { opacity: .55; }
.oauth-logo {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  display: block;
}
.oauth-coming-soon { font-weight: 500; opacity: .85; }

/* ── Member profile page ─────────────────────────────────────── */
.avatar-edit-wrap {
  display: flex;
  align-items: center;
  gap: 16px;
  margin-bottom: 16px;
}
.avatar-lg {
  width: 72px;
  height: 72px;
  border-radius: 50%;
  border: 3px solid var(--border);
  background: var(--light);
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 2rem;
  color: var(--mid);
  overflow: hidden;
}
.avatar-lg img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 50%;
  display: block;
}

.send-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 0;
  border-bottom: 1px solid var(--border);
  font-size: .88rem;
  flex-wrap: wrap;
}
.send-item:last-child { border-bottom: none; }
.send-item-date  { font-size: .75rem; color: var(--mid); flex-shrink: 0; }
.send-item-label { flex: 1; min-width: 0; }
.send-item-type  { font-size: .72rem; font-weight: 600; color: var(--mid);
                   background: var(--light); border-radius: 4px; padding: 1px 6px; }

/* Send video player (profile page) */
.send-video-row {
  width: 100%;
  padding: 6px 0 4px;
}
.send-video-player {
  width: 100%;
  max-width: 420px;
  max-height: 260px;
  border-radius: 8px;
  background: #000;
  display: block;
}
.send-video-actions {
  display: flex;
  gap: 8px;
  align-items: center;
  margin-top: 6px;
  flex-wrap: wrap;
}

/* Member video cards (grade page / spray lightbox) */
.member-videos-section {
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
}
.member-videos-title {
  font-weight: 700;
  font-size: .9rem;
  color: var(--dark);
  margin-bottom: 12px;
  display: flex;
  align-items: center;
  gap: 6px;
}
.member-video-card {
  background: var(--light);
  border-radius: 10px;
  overflow: hidden;
  margin-bottom: 12px;
}
.member-video-card video {
  width: 100%;
  max-height: 320px;
  display: block;
  background: #000;
}
.member-video-footer {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  font-size: .82rem;
  color: var(--dark);
}
.member-video-avatar {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  object-fit: cover;
}
.member-video-name {
  font-weight: 600;
  flex: 1;
}
.member-video-date {
  color: var(--mid);
  font-size: .76rem;
}

/* Social media badges */
.social-links {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 8px;
}
.social-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 4px 10px;
  border-radius: 20px;
  font-size: .8rem;
  font-weight: 600;
  text-decoration: none;
  color: #fff;
}
.social-badge.instagram { background: #E1306C; }
.social-badge.youtube   { background: #FF0000; }
.social-badge.tiktok    { background: #010101; }
.social-badge.x         { background: #000000; }
.social-badge.facebook  { background: #1877F2; }

.toggle-switch {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 0;
}
.toggle-switch input[type=checkbox] {
  width: 42px;
  height: 22px;
  appearance: none;
  -webkit-appearance: none;
  background: var(--border);
  border-radius: 11px;
  cursor: pointer;
  position: relative;
  transition: background .2s;
  flex-shrink: 0;
}
.toggle-switch input[type=checkbox]:checked { background: var(--primary); }
.toggle-switch input[type=checkbox]::after {
  content: '';
  position: absolute;
  top: 3px;
  left: 3px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--white);
  transition: transform .2s;
  box-shadow: 0 1px 3px rgba(0,0,0,.2);
}
.toggle-switch input[type=checkbox]:checked::after { transform: translateX(20px); }
.toggle-switch label {
  margin: 0;
  cursor: pointer;
  font-size: .9rem;
  font-weight: 500;
}

/* ── Community page ──────────────────────────────────────────── */
.member-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 14px;
}
.member-card {
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 16px 12px 14px;
  text-align: center;
  text-decoration: none;
  color: var(--dark);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  transition: all .2s;
  cursor: pointer;
}
.member-card:hover {
  border-color: var(--primary);
  transform: translateY(-3px);
  box-shadow: 0 6px 18px rgba(232,115,26,.15);
}
.member-card-avatar {
  width: 60px;
  height: 60px;
  border-radius: 50%;
  background: var(--light);
  border: 2px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.8rem;
  overflow: hidden;
  flex-shrink: 0;
}
.member-card-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 50%;
  display: block;
}
.member-card-name  { font-size: .88rem; font-weight: 700; line-height: 1.2; }

/* #25 presence dot — small green (online) / grey (offline) indicator shown next
 * to a member's name in the community grid, conversation list, chat header and
 * public profile. Visibility is server-enforced (connections + show_online), so
 * the markup is identical everywhere; only the colour modifier differs. */
.presence-dot {
  display: inline-block;
  width: .55em;
  height: .55em;
  border-radius: 50%;
  margin-right: .4em;
  vertical-align: middle;
  flex: none;
  background: var(--mid, #8a8f98);          /* offline (grey) — default */
}
.presence-dot.is-online {
  background: #22c55e;                       /* online (green) */
  box-shadow: 0 0 0 2px rgba(34, 197, 94, .22);
}
.presence-dot.is-offline { background: var(--mid, #8a8f98); opacity: .55; }
.member-card-sends { font-size: .78rem; color: var(--mid); }
.member-card-bio   { font-size: .75rem; color: var(--mid);
                     overflow: hidden; display: -webkit-box;
                     -webkit-line-clamp: 2; -webkit-box-orient: vertical;
                     line-height: 1.35; }

/* ── Beta videos grid (#234) ───────────────────────────────────── */
.beta-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 16px;
}
.beta-card {
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  transition: all .2s;
}
.beta-card:hover {
  border-color: var(--primary);
  transform: translateY(-3px);
  box-shadow: 0 6px 18px rgba(232,115,26,.15);
}
.beta-thumb {
  position: relative;
  aspect-ratio: 4 / 3;
  background: #000;
  overflow: hidden;
}
.beta-thumb video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  pointer-events: none;
}
.beta-play {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 2.4rem;
  color: #fff;
  text-shadow: 0 2px 10px rgba(0,0,0,.6);
  opacity: .92;
}
.beta-badge {
  position: absolute;
  top: 8px;
  left: 8px;
  font-size: .68rem;
  font-weight: 700;
  padding: 3px 8px;
  border-radius: 999px;
  color: #fff;
  backdrop-filter: blur(2px);
}
.beta-badge-staff  { background: rgba(43,45,66,.92); }
.beta-badge-member { background: rgba(232,115,26,.92); }
.beta-info { padding: 10px 12px 12px; display: flex; flex-direction: column; gap: 5px; }
.beta-line1 { display: flex; align-items: center; gap: 7px; }
.beta-color-dot {
  width: 13px; height: 13px; border-radius: 50%;
  border: 1px solid var(--border); flex-shrink: 0;
}
.beta-num { font-weight: 800; font-size: .95rem; color: var(--dark); }
.beta-grade {
  margin-left: auto; font-size: .72rem; font-weight: 700;
  background: var(--light); color: var(--mid);
  padding: 2px 8px; border-radius: 999px;
}
.beta-meta { font-size: .78rem; color: var(--mid); }
.beta-styles { display: flex; flex-wrap: wrap; gap: 5px; }
.beta-style {
  font-size: .68rem; color: var(--mid);
  background: var(--light); border-radius: 6px; padding: 2px 7px;
}
.beta-author { font-size: .76rem; color: var(--mid); }
.beta-author-link { color: var(--primary); text-decoration: none; font-weight: 600; }
.beta-author-link:hover { text-decoration: underline; }

/* ── Public member profile ───────────────────────────────────── */
.profile-hero {
  display: flex;
  align-items: flex-start;
  gap: 18px;
  margin-bottom: 6px;
}
.profile-hero-info { flex: 1; min-width: 0; }
.profile-name  { font-size: 1.3rem; font-weight: 800; margin-bottom: 4px; }
.profile-bio   { font-size: .88rem; color: var(--mid); line-height: 1.5; }
.profile-since { font-size: .75rem; color: var(--mid); margin-top: 6px; }

/* ── Send button (grade + spray pages) ──────────────────────── */
.btn-send {
  background: var(--success);
  color: #fff;
}
.btn-send:hover { background: #16a34a; box-shadow: 0 4px 12px rgba(34,197,94,.35); }
.btn-send.sent  { background: var(--mid); cursor: default; }
.btn-send.sent:hover { background: var(--mid); box-shadow: none; transform: none; }
/* Reversed spray send emphasis (request): NOT sent reads as a quiet neutral
   button; SENT becomes the bright green "validated" state. Scoped to the
   spray-specific selector so other .btn-send users keep the default emphasis. */
.spray-send-btn        { background: var(--mid); }
.spray-send-btn:hover  { background: var(--chrome); box-shadow: none; }
.spray-send-btn.sent       { background: var(--success); cursor: default; }
.spray-send-btn.sent:hover { background: #16a34a; box-shadow: 0 4px 12px rgba(34,197,94,.35); transform: none; }

.btn-fav {
  background: #FEF3C7;
  color: #92400E;
  border: 1px solid #FDE68A;
}
.btn-fav:hover { background: #FDE68A; box-shadow: 0 4px 12px rgba(217,119,6,.20); }
.btn-fav.faved { background: #F59E0B; color: #fff; border-color: #D97706; }
.btn-fav.faved:hover { background: #D97706; box-shadow: 0 4px 12px rgba(217,119,6,.35); }

/* Spray favourite-star convention: NOT favourited is a quiet outline ☆ on a
   muted chip; once favourited it becomes the full-colour yellow ⭐ on an amber
   chip. Scoped to the spray selector so the boulder-tile star (.bt-fav) is
   untouched. */
.spray-fav-btn {
  background: var(--light);
  color: var(--mid);
  border: 1px solid var(--border);
}
.spray-fav-btn:hover { background: #FEF3C7; box-shadow: none; }
.spray-fav-btn.faved {
  background: #FEF3C7;
  color: #92400E;
  border: 1px solid #FDE68A;
}
.spray-fav-btn.faved:hover { background: #FDE68A; box-shadow: 0 4px 12px rgba(217,119,6,.20); }

/* ── Responsive ──────────────────────────────── */
@media (max-width: 600px) {
  .admin-tiles { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 400px) {
  .admin-tiles { grid-template-columns: 1fr; }
}
@media (max-width: 500px) {
  .form-row      { grid-template-columns: 1fr; }
  .action-cards  { grid-template-columns: 1fr; }
  .grade-btn     { width: 44px; height: 44px; font-size: 1rem; }
  .captcha-box   { flex-direction: column; align-items: flex-start; }
  .manage-row    { flex-direction: column; align-items: flex-start; }
  .manage-row-actions { width: 100%; }
  .bc-loc        { max-width: 100%; white-space: normal; overflow: visible; }
  .boulder-card .special-note { white-space: normal; display: block; margin: 3px 0 0; }
}

/* ───────────────────── Admin shell (admin-shell.js) ─────────────────────
   Shared left sidebar (desktop) / slide-in drawer (mobile) for all admin
   pages. Built at runtime by admin-shell.js, which removes the legacy
   .admin-sticky-bar and re-parents .container into <main id="admin-main">. */
.adm-skip { position:absolute; left:-9999px; top:0; z-index:200; background:var(--primary); color:#fff;
            padding:10px 16px; border-radius:0 0 8px 0; font-weight:700; }
.adm-skip:focus { left:0; }

.adm-side { position:fixed; top:0; left:0; bottom:0; width:240px; z-index:60;
            background:var(--chrome); color:#fff; display:flex; flex-direction:column; overflow:hidden;
            padding-top:env(safe-area-inset-top); }   /* clear the status bar in PWA standalone (#58) */
.adm-brand { display:flex; align-items:center; gap:10px; padding:16px 18px; font-weight:800; font-size:1.05rem;
             background:var(--chrome); flex-shrink:0; }
.adm-brand-logo { font-size:1.4rem; }
.adm-brand-link { display:flex; align-items:center; gap:10px; text-decoration:none; color:inherit; min-width:0; }
.adm-brand-link:hover { opacity:.85; }
.adm-brand-img { width:22px; height:22px; border-radius:5px; display:block; flex-shrink:0; }
.adm-close { margin-left:auto; background:none; border:none; color:rgba(255,255,255,.7);
             font-size:1.1rem; cursor:pointer; display:none; }
/* Pinned Dashboard (top) — always visible above the scrollable groups. */
.adm-pinned-top { flex-shrink:0; padding:0 10px 4px;
                  border-bottom:1px solid rgba(255,255,255,.08); margin-bottom:4px; }
/* Only the middle groups scroll. */
.adm-nav { flex:1; min-height:0; overflow-y:auto; padding:4px 10px 10px; }
.adm-nav::-webkit-scrollbar { width:8px; }
.adm-nav::-webkit-scrollbar-thumb { background:rgba(255,255,255,.15); border-radius:4px; }
/* Collapsible section header (#427) — a full-width button that folds the links
   under it; fold state is remembered per user in localStorage. */
.adm-group { display:flex; align-items:center; gap:7px; width:100%; background:none;
             border:none; text-align:left; cursor:pointer; font-family:inherit;
             font-size:.66rem; font-weight:800; text-transform:uppercase; letter-spacing:.07em;
             color:rgba(255,255,255,.4); padding:14px 10px 5px; }
.adm-group:hover { color:rgba(255,255,255,.72); }
.adm-group:focus-visible { outline:2px solid #fff; outline-offset:-2px; border-radius:6px; }
.adm-group-label { flex:1; }
.adm-fold-ic { font-size:.6rem; line-height:1; transition:transform .15s ease; }
.adm-group[aria-expanded="false"] .adm-fold-ic { transform:rotate(-90deg); }
/* Foldable parent entry (Settings, #553): the link flexes, a small disclosure
   button on the right folds the sub-links beneath it. */
.adm-parent { display:flex; align-items:center; }
.adm-parent .adm-link { flex:1; }
.adm-fold-toggle { background:none; border:none; cursor:pointer; flex-shrink:0;
                   color:rgba(255,255,255,.5); padding:6px 10px; line-height:1; }
.adm-fold-toggle:hover { color:#fff; }
.adm-fold-toggle:focus-visible { outline:2px solid #fff; outline-offset:-2px; border-radius:6px; }
.adm-fold-toggle .adm-fold-ic { display:inline-block; }
.adm-fold-toggle[aria-expanded="false"] .adm-fold-ic { transform:rotate(-90deg); }
.adm-link { display:flex; align-items:center; gap:11px; padding:9px 11px; border-radius:8px;
            color:rgba(255,255,255,.82); text-decoration:none; font-size:.9rem; font-weight:600; line-height:1.15; }
.adm-link:hover { background:rgba(255,255,255,.08); color:#fff; }
.adm-link.on { background:var(--primary); color:#fff; }
.adm-link:focus-visible { outline:2px solid #fff; outline-offset:-2px; }
/* Indented section deep-links under a parent entry (e.g. Gym settings, #549). */
.adm-link-sub { padding-left:30px; font-size:.84rem; font-weight:500; color:rgba(255,255,255,.66); }
.adm-link-sub .adm-ic { font-size:.9em; }
.adm-ic { width:20px; text-align:center; flex-shrink:0; }
.adm-back { margin:6px 10px 16px; background:rgba(255,255,255,.06); flex-shrink:0; }

.adm-main-wrap { margin-left:240px; min-height:100vh; display:flex; flex-direction:column; }
.adm-top { position:sticky; top:0; z-index:40; background:var(--chrome); color:#fff;
           display:flex; align-items:center; gap:12px; box-shadow:0 2px 8px rgba(0,0,0,.2);
           /* clear the iOS status bar / notch in PWA standalone mode (#58) —
              env() insets are 0 off-iOS, so desktop/Android browser is unchanged. */
           padding:calc(12px + env(safe-area-inset-top)) calc(18px + env(safe-area-inset-right))
                   12px calc(18px + env(safe-area-inset-left)); }
.adm-title { font-size:1.1rem; font-weight:700; }
.adm-top .header-nav { margin-left:auto; }
.adm-burger { display:none; background:none; border:none; color:#fff; font-size:1.4rem;
              cursor:pointer; line-height:1; padding:2px 4px; }
.adm-burger:focus-visible { outline:2px solid #fff; outline-offset:2px; }
.adm-main-wrap > #admin-main { flex:1; }
.adm-main-wrap .container { max-width:1040px; }
.adm-backdrop { display:none; }

@media (max-width:900px){
  .adm-side { transform:translateX(-100%); transition:transform .22s ease; box-shadow:2px 0 18px rgba(0,0,0,.35); }
  body.adm-drawer-open .adm-side { transform:translateX(0); }
  .adm-close { display:block; }
  .adm-main-wrap { margin-left:0; }
  .adm-burger { display:inline-flex; }
  .adm-backdrop { position:fixed; inset:0; background:rgba(0,0,0,.45); z-index:55; }
  body.adm-drawer-open .adm-backdrop { display:block; }
}

/* ───────────────────── Public shell (public-shell.js) ─────────────────────
   Mobile-first frame for public pages: top bar (desktop links) + bottom tab
   bar & "More" sheet (mobile). The legacy .admin-sticky-bar this shell used to
   hide was deleted from every page in W1 (#1413), so there's nothing to hide. */
.pub-skip { position:absolute; left:-9999px; top:0; z-index:200; background:var(--primary); color:#fff;
            padding:10px 16px; border-radius:0 0 8px 0; font-weight:700; }
.pub-skip:focus { left:0; }

/* "Powered by MonkeyGrade" footer (#53) — injected by public-shell.js /
   admin-shell.js into the page's main content. */
.app-footer { text-align:center; font-size:.78rem; color:var(--mid);
              padding:26px 16px 18px; margin-top:28px; }
.app-footer a { color:var(--mid); font-weight:600; text-decoration:none; }
.app-footer a:hover { color:var(--primary); text-decoration:underline; }
/* Standardized 3-line footer (#243): brand · archive note · cookie links. */
.app-footer p { margin:5px 0; line-height:1.4; }
.app-footer .app-footer-note { opacity:.9; }
/* Colour-scheme toggle (#270). */
.app-footer .app-footer-scheme { background:none; border:0; padding:2px 4px; cursor:pointer;
  font:inherit; font-size:.78rem; color:var(--mid); font-weight:600; text-decoration:underline; }
.app-footer .app-footer-scheme:hover { color:var(--primary); }
.app-footer .app-footer-links a { text-decoration:underline; }
/* On mobile the public bottom tab bar is fixed — clear it so the footer shows. */
@media (max-width:760px){
  .pub-shell .app-footer { margin-bottom:calc(64px + env(safe-area-inset-bottom)); }
}

/* Aggregate Social badge (epic #1410) — the "things addressed to me I haven't
   seen" count carried on the Social nav item (top bar + bottom tab). Numeric,
   capped 9+; red = a notification that decrements as items are read. */
.pub-badge { display:inline-flex; align-items:center; justify-content:center;
             min-width:16px; height:16px; padding:0 4px; margin-left:5px; border-radius:var(--r-pill);
             background:#EF4444; color:#fff; font-size:.64rem; font-weight:800; line-height:1;
             vertical-align:middle; }
.pub-tab { position:relative; }                 /* anchor the corner badge on the bottom tab */
.pub-tab .pub-badge { position:absolute; top:4px; left:calc(50% + 5px); margin-left:0; }

.pub-top { position:sticky; top:0; z-index:50; background:var(--chrome); color:#fff;
           display:flex; align-items:center; gap:14px; box-shadow:0 2px 8px rgba(0,0,0,.2);
           /* viewport-fit=cover: keep the bar clear of the notch / side safe areas
              (env() insets are 0 on non-notched screens, so desktop is unchanged). */
           padding:calc(10px + env(safe-area-inset-top)) calc(18px + env(safe-area-inset-right))
                   10px calc(18px + env(safe-area-inset-left)); }
.pub-logo { display:flex; align-items:center; gap:8px; text-decoration:none; color:#fff; min-width:0; flex-shrink:0; }
.pub-logo-img { width:1.4rem; height:1.4rem; border-radius:5px; display:block; flex-shrink:0; }
/* W3: the brand wordmark carries the display face (Space Grotesk). */
.pub-brand { font-family:var(--font-disp); font-size:1.05rem; font-weight:700; letter-spacing:-.2px;
             overflow:hidden; text-overflow:ellipsis; white-space:nowrap; min-width:0; }
/* Bare brand bar (auth/landing pages before a gym is chosen): no nav menu. */
body.pub-bare .pub-links,
body.pub-bare .pub-tabs,
body.pub-bare .pub-more,
body.pub-bare .pub-more-backdrop,
body.pub-bare .pub-top .header-nav { display:none !important; }
.pub-links { display:flex; align-items:center; gap:4px; flex:1; flex-wrap:wrap; }
/* W3: 4pt spacing, --r-inner radius on the active-link pill. */
.pub-link { display:inline-flex; align-items:center; gap:8px; padding:8px 12px; border-radius:var(--r-inner);
            color:rgba(255,255,255,.82); text-decoration:none; font-size:.88rem; font-weight:600; white-space:nowrap;
            transition:background .15s, color .15s; }
.pub-link:hover { background:rgba(255,255,255,.10); color:#fff; }
.pub-link.on { background:var(--primary); color:#fff; }
.pub-link:focus-visible, .pub-tab:focus-visible { outline:2px solid #fff; outline-offset:-2px; }
.pub-top .header-nav { margin-left:auto; flex-shrink:0; }
.pub-link .pub-ic { font-size:1rem; }

/* Classic-menu collapse (landscape phones). When the desktop link row would
   wrap to 2+ lines on a short viewport, public-shell.js adds
   `.pub-links-collapsed` to the bar: the row hides behind a "Menu ▾" button and
   reopens as a vertical, scrollable dropdown so it never eats the limited
   vertical space. The toggle is hidden (display:none) in the normal layout. */
.pub-links-toggle { display:none; align-items:center; gap:7px; padding:7px 12px;
  border-radius:8px; background:rgba(255,255,255,.10); color:#fff; border:none;
  cursor:pointer; font:inherit; font-size:.88rem; font-weight:600; white-space:nowrap; }
.pub-links-toggle:hover { background:rgba(255,255,255,.18); }
.pub-links-toggle:focus-visible { outline:2px solid #fff; outline-offset:-2px; }
.pub-links-toggle .pub-links-caret { font-size:.7rem; opacity:.85; }
.pub-top.pub-links-collapsed .pub-links-toggle { display:inline-flex; }
.pub-top.pub-links-collapsed .pub-links {
  position:absolute; top:100%; left:calc(18px + env(safe-area-inset-left)); right:auto;
  display:none; flex:none; flex-direction:column; flex-wrap:nowrap; align-items:stretch;
  gap:2px; min-width:210px; padding:8px; background:var(--chrome);
  border-radius:0 0 12px 12px; box-shadow:0 8px 24px rgba(0,0,0,.3); z-index:55;
  max-height:calc(100vh - 56px); max-height:calc(100dvh - 56px);
  overflow-y:auto; overscroll-behavior:contain; -webkit-overflow-scrolling:touch; }
.pub-top.pub-links-collapsed .pub-links.open { display:flex; }

/* Bottom tab bar (mobile) — W3: frosted floating bar, active tab gets a
   brand-tint pill behind the icon (mirrors the mobile mockup's .nav .n.on .pill). */
.pub-tabs { display:none; }
.pub-tab { flex:1; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:2px;
           min-height:54px; background:none; border:none; cursor:pointer; text-decoration:none;
           color:var(--mid); font-size:.66rem; font-weight:700; padding:6px 2px; }
.pub-tab .pub-ic { font-size:1.25rem; line-height:1; display:inline-flex; align-items:center; justify-content:center;
           width:40px; height:26px; border-radius:var(--r-pill); transition:background .15s; }
.pub-tab.on { color:var(--primary); }
.pub-tab.on .pub-ic { background:var(--primary-light); }

/* More sheet — W3: --r-sheet top corners + grab handle (the standard's sheet
   language). backdrop-filter on the dim scrim so it reads as a real sheet. */
.pub-more-backdrop { display:none; position:fixed; inset:0; background:rgba(20,18,40,.45); z-index:60;
                     backdrop-filter:blur(2px); -webkit-backdrop-filter:blur(2px); }
.pub-more { display:none; position:fixed; left:0; right:0; bottom:0; z-index:61; background:var(--white);
            border-radius:var(--r-sheet) var(--r-sheet) 0 0; box-shadow:0 -4px 24px rgba(20,18,40,.25);
            padding:8px 16px max(16px,env(safe-area-inset-bottom));
            touch-action:none; }   /* let the swipe-to-dismiss gesture drive the sheet */
.pub-more-grab { width:40px; height:4px; border-radius:var(--r-pill); background:var(--border); margin:8px auto 12px; cursor:grab; }
.pub-more-grid { display:grid; grid-template-columns:1fr 1fr; gap:8px; }
.pub-more-item { display:flex; align-items:center; gap:10px; padding:14px; border-radius:var(--r-inner);
                 text-decoration:none; color:var(--dark); font-weight:600; font-size:.92rem; background:var(--light); }
.pub-more-item.on { background:var(--primary); color:#fff; }
.pub-more-item .pub-ic { font-size:1.2rem; }
body.pub-more-open .pub-more, body.pub-more-open .pub-more-backdrop { display:block; }

@media (max-width:760px){
  .pub-links { display:none; }
  /* Keep the brand (top-left) and the account button (top-right) both on-screen:
     the gym chip collapses to its logo only, and the brand wordmark truncates
     before it can ever push the account button past the right edge. */
  .pub-logo { flex-shrink:1; overflow:hidden; }
  .mg-gym-chip .mg-gym-name, .mg-gym-chip .mg-gym-caret { display:none; }
  .mg-gym-chip-wrap { margin-right:4px; }
  /* W3: frosted floating bar. backdrop-filter over a translucent surface, with
     a solid fallback (@supports below) so no-blur browsers stay legible. */
  .pub-tabs { display:flex; position:fixed; left:0; right:0; bottom:0; z-index:50;
              background:rgba(255,255,255,.82); backdrop-filter:blur(14px); -webkit-backdrop-filter:blur(14px);
              border-top:1px solid var(--border); box-shadow:var(--shadow-float);
              padding-bottom:env(safe-area-inset-bottom); }
  :root[data-theme="dark"] .pub-tabs { background:rgba(25,27,46,.82); border-top-color:var(--night-stroke); }
  body.pub-shell { padding-bottom:calc(66px + env(safe-area-inset-bottom)); }   /* clear the fixed bottom bar (incl. home-indicator inset) */
  /* Lift the back-to-top button above the bottom tab bar so it never overlaps
     the "More" tab. (Only on public-shell pages — the admin pages have no bar.) */
  body.pub-shell .back-to-top { bottom: calc(74px + env(safe-area-inset-bottom)); }
}
/* Bottom-bar frost fallbacks (the bar is only visible ≤760px; these selectors
   are inert on desktop where .pub-tabs is display:none). Solid surface when
   blur is unsupported or the user reduces transparency. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .pub-tabs { background:rgba(25,27,46,.82); border-top-color:var(--night-stroke); }
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .pub-tabs { background:var(--white); }
}
@media (prefers-reduced-transparency: reduce) {
  .pub-tabs { backdrop-filter:none; -webkit-backdrop-filter:none; background:var(--white); }
}

/* ── Boulder tiles (shared by the public list + admin tile editor) ─────────── */
/* BOTW card — collapsed-by-default disclosure (#224: minimise on the boulder page) */
.botw-summary { cursor:pointer; list-style:none; display:flex; align-items:center; }
.botw-summary::-webkit-details-marker { display:none; }
.botw-card:not([open]) .botw-summary { margin-bottom:0; padding-bottom:0; border-bottom:none; }
.botw-caret { margin-left:auto; font-size:.8rem; color:var(--mid); transition:transform .15s; }
.botw-card[open] .botw-caret { transform:rotate(180deg); }
/* Boulder of the Week body: the featured boulder shown as a normal list tile,
   with the member's reward progress beside it (stacking on narrow screens). */
.botw-layout { display:flex; gap:16px; align-items:flex-start; flex-wrap:wrap; }
.botw-tile { width:240px; max-width:100%; flex:0 0 auto; }
.botw-progress { flex:1 1 220px; min-width:200px; }
.boulder-tiles { display:grid; grid-template-columns:repeat(auto-fill,minmax(230px,1fr)); gap:12px; }
.boulder-tile { border:1px solid var(--border); border-radius:12px; padding:12px 14px;
                background:var(--white); cursor:pointer; display:flex; flex-direction:column; gap:7px;
                transition:border-color .15s, box-shadow .15s; }
.boulder-tile:hover { border-color:var(--primary); box-shadow:0 4px 14px rgba(0,0,0,.08); }
.bt-row { display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
.bt-photo { width:100%; height:150px; object-fit:cover; border-radius:8px; display:block; }
/* Reserved slots so tiles line up even when a boulder lacks the data. */
.bt-photo-empty { display:flex; align-items:center; justify-content:center; height:150px;
                  background:var(--light); border:1px dashed var(--border);
                  color:var(--mid); font-size:1.5rem; opacity:.45; }
.bt-empty { visibility:hidden; }
.bt-num { font-weight:800; font-size:1.05rem; }
/* #927: a community wall-boulder shows its NAME (not a #number) — let it wrap/
   ellipsize so a long name never blows out the tile. */
.bt-num-community { font-size:.98rem; overflow:hidden; text-overflow:ellipsis;
                    white-space:nowrap; max-width:62%; }
/* "Community" badge — visually distinct from gym-set tiles (themed purple pill). */
.bt-community-badge { display:inline-flex; align-items:center; gap:3px; font-size:.66rem;
                      font-weight:700; line-height:1; padding:3px 7px; border-radius:999px;
                      background:#EDE9FE; color:#5B21B6; border:1px solid #DDD6FE;
                      white-space:nowrap; }
.bt-icons { margin-left:auto; display:inline-flex; gap:6px; }
.bt-wall { font-size:.9rem; font-weight:600; color:var(--dark); }
.bt-note { font-size:.8rem; color:var(--primary); font-style:italic;
           white-space:nowrap; overflow:hidden; text-overflow:ellipsis; max-width:100%; }
.bt-styles { display:flex; flex-wrap:wrap; gap:4px; font-size:1rem; }
.bt-styles .bt-style { width:26px; height:26px; display:inline-flex; align-items:center;
                       justify-content:center; border:1px solid var(--border); border-radius:50%;
                       background:var(--light); }
.bt-g { display:flex; align-items:center; gap:5px; font-size:.78rem; color:var(--mid); }
.bt-setter { font-size:.78rem; color:var(--mid); }
/* #218/#225: favourite star on a tile. Floats to the right of its row; click
   is handled separately from the tile-wide grade link (see
   actToggleBoulderFav). Emphasis is INVERTED (#225): the default (not-yet-
   favourited) state is the eye-catching full-colour yellow ⭐ that invites the
   member to favourite; once favourited it relaxes to a quiet themed outline ☆.
   Scoped to .bt-fav so it never collides with spray's .spray-fav-btn/.btn-fav. */
/* Favourite group sits at the right end of its row: [send-status icon] [star]. */
.bt-fav-wrap { margin-left:auto; display:inline-flex; align-items:center; gap:5px; }
/* Non-clickable send-status icon shown left of the star (⚡ flashed / ✅ sent). */
.bt-send-status { font-size:1rem; line-height:1; cursor:default; user-select:none; }
/* Identical to the spray favourite chip (.btn.btn-sm.spray-fav-btn): a small
   pill — quiet outline ☆ on a muted chip when not favourited, amber-on-fave ⭐ —
   so the boulder tile and spray boulder use the exact same favourite affordance. */
.bt-fav { display:inline-flex; align-items:center; justify-content:center;
          padding:5px 12px; border-radius:6px; font-size:.8rem; font-weight:600;
          line-height:1; cursor:pointer; background:var(--light); color:var(--mid);
          border:1px solid var(--border); transition:background .2s, box-shadow .2s; }
.bt-fav:hover { background:#FEF3C7; }
.bt-fav.faved { background:#FEF3C7; color:#92400E; border:1px solid #FDE68A; }
.bt-fav.faved:hover { background:#FDE68A; box-shadow:0 4px 12px rgba(217,119,6,.20); }

/* ── #1253 thumbs up/down reactions ─────────────────────────────────────────
   One shared widget used on the boulder tile, the grade.html detail page, and
   the spray/community lightboxes: two small pill buttons (👍 count / 👎 count)
   with an "on" state when it's the caller's own reaction. Mirrors .bt-fav's
   quiet-chip-until-active convention (not the inverted favourite emphasis —
   thumbs read fine as neutral until picked). View-only (no buttons) renders as
   plain .reaction-counts text for anonymous visitors. */
.reaction-bar { display:inline-flex; align-items:center; gap:6px; }
.reaction-btn { display:inline-flex; align-items:center; gap:4px; justify-content:center;
                padding:5px 10px; border-radius:6px; font-size:.8rem; font-weight:600;
                line-height:1; cursor:pointer; background:var(--light); color:var(--mid);
                border:1px solid var(--border); transition:background .2s, box-shadow .2s; }
.reaction-btn:hover { background:#F3F4F6; }
.reaction-btn.up.on   { background:#DCFCE7; color:#15803D; border-color:#86EFAC; }
.reaction-btn.up.on:hover   { background:#BBF7D0; box-shadow:0 4px 12px rgba(34,197,94,.20); }
.reaction-btn.down.on { background:#FEE2E2; color:#B91C1C; border-color:#FCA5A5; }
.reaction-btn.down.on:hover { background:#FECACA; box-shadow:0 4px 12px rgba(239,68,68,.20); }
.reaction-btn:disabled { opacity:.6; cursor:not-allowed; }
/* Anonymous view-only counts (no button chrome, not clickable). */
.reaction-counts { display:inline-flex; align-items:center; gap:8px; font-size:.8rem; color:var(--mid); }

/* ── Mobile: two columns for both lists, with tightened tile text so the denser
      grid stays coherent. Placed after the base rules so it wins on source
      order. (Spray-list grid itself is set in its own 560px block above.) ──── */
@media (max-width: 560px) {
  /* Boulder list mirrors the spray wall: two tiles per row on phones.
     min-width:0 lets each 1fr column shrink below its content's intrinsic
     width so the badge rows wrap instead of forcing horizontal overflow. */
  .boulder-tiles { grid-template-columns: repeat(2, 1fr); gap: 10px; }
  .boulder-tile  { padding: 9px 10px; gap: 5px; min-width: 0; }
  .spray-card    { min-width: 0; }
  .bt-row    { gap: 6px; }
  .bt-photo, .bt-photo-empty { height: 104px; }
  .bt-num    { font-size: .9rem; }
  .bt-wall   { font-size: .76rem; }
  .bt-note   { font-size: .72rem; }
  .bt-g, .bt-setter { font-size: .7rem; }
  .bt-fav { font-size: .72rem; padding: 4px 9px; }   /* match the spray chip's mobile size */
  /* Colour: show only the coloured dot, hide the name + pill chrome (like the
     table view). The ::before dot is a fixed 10px, so font-size:0 keeps it. */
  .boulder-tile .color-chip { font-size: 0; gap: 0; margin: 0; padding: 0;
                              border: none; background: transparent; }
  /* Set / Community each take a full line: label on the left, grade on the right. */
  .bt-grade { flex-basis: 100%; justify-content: space-between; }
  .bt-styles { font-size: .85rem; gap: 3px; }
  .bt-styles .bt-style { width: 22px; height: 22px; }

  /* Spray wall cards (already two columns) — match the tighter scale. */
  .spray-card-body { padding: 8px 10px 10px; }
  .spray-card-name { font-size: .82rem; }
  .spray-card-meta { font-size: .7rem; }
  .spray-styles .spray-style { width: 20px; height: 20px; font-size: .82rem; }
  .spray-card .btn-sm, .spray-card .btn-moderate { font-size: .72rem; padding: 5px 8px; }
}

/* ── PWA install affordances (#58) ───────────────────────────────────────── */
/* Discreet install button — sits in the header, just left of Sign in / the name. */
.mg-install { background:transparent; color:rgba(255,255,255,.78); border:1px solid rgba(255,255,255,.32);
  border-radius:8px; padding:5px 9px; font-size:.8rem; font-weight:600; cursor:pointer;
  white-space:nowrap; line-height:1; flex-shrink:0; }
.mg-install:hover { background:rgba(255,255,255,.12); color:#fff; }
.mg-ios-tip { position:fixed; left:10px; right:10px; bottom:calc(72px + env(safe-area-inset-bottom)); z-index:60;
  background:var(--chrome); color:#fff; border-radius:10px; padding:10px 12px; display:flex; gap:10px;
  align-items:center; font-size:.85rem; line-height:1.35; box-shadow:0 4px 14px rgba(0,0,0,.28); }
.mg-ios-tip b { color:#fff; }
.mg-ios-tip button { margin-left:auto; flex-shrink:0; background:none; border:none; color:#fff;
  font-size:1.3rem; cursor:pointer; line-height:1; padding:0 4px; }

/* ── Admin "How it works" help (#174) ────────────────────────────────────────
   An always-present info icon pinned to the RIGHT of the admin top bar (the same
   spot on every admin page). Clicking it opens the page's guidance as an OVERLAY
   on top of the content (dialog popover + backdrop) — never inline. Role-gated in
   admin-help.js: super-admin notes + the Learn-more links are super-admin only.
   Inherits the dark-mode token remap above. */
.adm-help-btn {
  margin-left: auto;             /* pin the icon to the right group of the top bar */
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  border-radius: 50%;
  background: rgba(255,255,255,.12);
  border: 1px solid rgba(255,255,255,.3);
  color: #fff;
  cursor: pointer;
  line-height: 1;
}
.adm-help-btn:hover { background: rgba(255,255,255,.22); }
.adm-help-btn:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }
.adm-help-btn svg { display: block; }
/* The icon owns the right-push; keep the account menu snug to its right. */
.adm-top .adm-help-btn ~ .header-nav { margin-left: 0; }

.adm-help-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,.35); z-index: 90; }
.adm-help-pop {
  position: fixed;
  top: 64px;
  right: 16px;
  width: min(420px, calc(100vw - 32px));
  max-height: min(74vh, 660px);
  overflow: auto;
  z-index: 91;
  background: var(--white);
  color: var(--dark);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 14px 44px rgba(0,0,0,.3);
}
.adm-help-pop-head {
  position: sticky;
  top: 0;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 13px 16px;
  background: var(--white);
  border-bottom: 1px solid var(--border);
  font-weight: 800;
  font-size: .95rem;
}
.adm-help-ic { font-size: 1.05rem; flex-shrink: 0; }
.adm-help-pop-title { color: var(--primary-dark); }
:root[data-theme="dark"] .adm-help-pop-title { color: var(--primary); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .adm-help-pop-title { color: var(--primary); }
}
.adm-help-pop-close {
  margin-left: auto;
  flex-shrink: 0;
  background: none;
  border: none;
  cursor: pointer;
  font-size: 1.2rem;
  line-height: 1;
  color: var(--mid);
  padding: 2px 4px;
}
.adm-help-pop-close:hover { color: var(--dark); }
@media (max-width: 560px) {
  .adm-help-pop { top: 58px; right: 10px; left: 10px; width: auto; max-height: 78vh; }
}
.adm-help-body {
  padding: 12px 16px 16px;
  font-size: .9rem;
  line-height: 1.55;
  color: var(--dark);
}
.adm-help-intro { color: var(--mid); margin-bottom: 12px; }
ol.adm-help-steps { margin: 0 0 4px; padding-left: 22px; }
ol.adm-help-steps li { margin-bottom: 6px; }
.adm-help-steps li::marker { color: var(--primary); font-weight: 700; }
.adm-help-note {
  margin: 12px 0 4px;
  padding: 9px 12px;
  border-radius: 8px;
  background: rgba(0,0,0,.04);
  border-left: 3px solid var(--primary);
  font-size: .86rem;
  color: var(--mid);
}
.adm-help-tips { margin-top: 14px; }
.adm-help-tips-h {
  display: block;
  font-weight: 700;
  font-size: .8rem;
  text-transform: uppercase;
  letter-spacing: .3px;
  color: var(--mid);
  margin-bottom: 4px;
}
ul.adm-help-tip-list { margin: 0; padding-left: 20px; }
ul.adm-help-tip-list li { margin-bottom: 5px; }
.adm-help-tip-list li::marker { content: "💡 "; }
.adm-help-docs {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
  font-size: .85rem;
}
.adm-help-docs-label { color: var(--mid); font-weight: 700; }
.adm-help-docs a { color: var(--primary-dark); font-weight: 600; text-decoration: none; }
.adm-help-docs a:hover { text-decoration: underline; }
:root[data-theme="dark"] .adm-help-docs a { color: var(--primary); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .adm-help-docs a { color: var(--primary); }
}
.adm-help-dot { color: var(--mid); margin: 0 7px; }
.adm-help-body strong { color: var(--dark); font-weight: 700; }
.adm-help-body code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: .85em;
  background: rgba(0,0,0,.06);
  border-radius: 4px;
  padding: 1px 5px;
}

/* ════════════════════════════════════════════════════════════════════════
   #846 — Community wall boulders (member web UI)
   Lives in community.html's "Boulders" tab (#1227; was its own page,
   wall-boulders.html) + the separate community-stats section.
   Reuses the spray editor styles (.spray-picker-*, .spray-dot-*, .hold-*,
   .spray-card, .g-badge); only the genuinely new pieces live here.
   ════════════════════════════════════════════════════════════════════════ */

/* The finish hold reuses spray's "top" red dot; aliased here for clarity so a
   future restyle of finish holds doesn't disturb spray. */

/* ── Gym-map "Create a boulder on this wall" CTA (#846) ── */
.wall-card-wrap { display: flex; flex-direction: column; gap: 6px; }
.wall-card-wrap > .wall-card { flex: 1; }
.wall-card-create {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  font-size: .78rem;
  font-weight: 700;
  color: var(--primary-dark);
  background: var(--primary-light);
  border: 1px solid var(--primary);
  border-radius: 8px;
  padding: 6px 8px;
  text-decoration: none;
  text-align: center;
  transition: background .15s, color .15s;
}
.wall-card-create:hover { background: var(--primary); color: #fff; }

/* ── Wall picker (step 1) photo badge ── */
.wallb-photo-badge {
  font-size: .68rem;
  font-weight: 600;
  color: var(--mid);
  background: rgba(0,0,0,.05);
  border-radius: 999px;
  padding: 2px 8px;
  align-self: center;
}
.wallb-photo-badge.has { color: #16A34A; background: #F0FDF4; }

/* ── Surface picker (step 2) ── */
.wallb-surface-opts { display: flex; flex-direction: column; gap: 10px; }
.wallb-surface-opt {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  text-align: left;
  background: var(--light);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 14px 16px;
  cursor: pointer;
  transition: border-color .15s, box-shadow .15s, transform .15s;
  font-family: inherit;
}
.wallb-surface-opt:hover {
  border-color: var(--primary);
  box-shadow: 0 4px 14px rgba(232,115,26,.16);
  transform: translateY(-1px);
}
.wallb-surface-opt .opt-ic { font-size: 1.7rem; line-height: 1; flex-shrink: 0; }
.wallb-surface-opt .opt-txt { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.wallb-surface-opt .opt-txt strong { font-size: .95rem; color: var(--dark); }
.wallb-surface-opt .opt-txt small { font-size: .8rem; color: var(--mid); line-height: 1.35; }

/* ── Surface picker (step 1) — mobile-style list (#957 web parity) ──
   "Upload your own photo" card on top, then gym walls grouped into
   "with a photo" (Tier-A) and "on the map" (Tier-C). */
.wallb-pick-list { display: flex; flex-direction: column; gap: 10px; }
.wallb-pick-section {
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--mid);
  margin: 14px 0 2px;
}
.wallb-pick-own {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  width: 100%;
  text-align: left;
  background: rgba(232,115,26,.08);
  border: 1px solid rgba(232,115,26,.3);
  border-radius: 14px;
  padding: 14px 16px;
  cursor: pointer;
  font-family: inherit;
  transition: border-color .15s;
}
.wallb-pick-own:hover { border-color: var(--primary); }
.wallb-pick-own .opt-ic { font-size: 1.6rem; line-height: 1; flex-shrink: 0; }
.wallb-pick-own .opt-txt { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.wallb-pick-own .opt-txt strong { font-size: .95rem; color: var(--dark); }
.wallb-pick-own .opt-txt small { font-size: .8rem; color: var(--mid); line-height: 1.35; }
.wallb-pick-tile {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  text-align: left;
  background: var(--light);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 10px;
  cursor: pointer;
  font-family: inherit;
  transition: border-color .15s, box-shadow .15s, transform .15s;
}
.wallb-pick-tile:hover {
  border-color: var(--primary);
  box-shadow: 0 4px 12px rgba(232,115,26,.15);
  transform: translateY(-1px);
}
.wallb-pick-thumb {
  width: 56px;
  height: 56px;
  flex-shrink: 0;
  border-radius: 10px;
  object-fit: cover;
  background: #ECE6DA;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.5rem;
}
.wallb-pick-thumb-map { color: var(--mid); }
/* #1239 Phase 4a — Tier-A wall photo picker strip (thumbnails, horizontal scroll). */
.wallb-photo-strip {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding-bottom: 4px;
  -webkit-overflow-scrolling: touch;
}
.wallb-photo-thumb {
  flex: 0 0 auto;
  width: 64px;
  height: 64px;
  border-radius: 10px;
  overflow: hidden;
  border: 2px solid var(--border);
  background: #ECE6DA;
  padding: 0;
  cursor: pointer;
  position: relative;
  transition: border-color .15s, box-shadow .15s;
}
.wallb-photo-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
.wallb-photo-thumb:hover { border-color: var(--primary); }
.wallb-photo-thumb.on {
  border-color: var(--primary);
  box-shadow: 0 0 0 2px rgba(232,115,26,.35);
}
.wallb-pick-meta { display: flex; flex-direction: column; gap: 3px; min-width: 0; flex: 1; }
.wallb-pick-name {
  font-size: .95rem;
  font-weight: 700;
  color: var(--dark);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.wallb-pick-sub { font-size: .8rem; color: var(--mid); }
.wallb-pick-chev { color: var(--mid); font-size: 1.4rem; flex-shrink: 0; }

/* ── Detail overlay ── */
.wallb-detail-card { max-height: calc(100vh - 36px); overflow-y: auto; }
.wallb-detail-img {
  position: relative;
  border-radius: 10px;
  overflow: hidden;
  border: 1px solid var(--border);
  background: #ECE6DA;
  line-height: 0;
}
.wallb-detail-img img { display: block; width: 100%; height: auto; }
.wallb-detail-dots { position: absolute; inset: 0; pointer-events: none; }

/* Side-by-side creator vs community grades — never merged (#846 §6). */
.wallb-grades {
  display: flex;
  align-items: stretch;
  gap: 10px;
  margin: 14px 0;
}
.wallb-grade-col {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 5px;
  background: var(--light);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 12px 10px;
  text-align: center;
}
.wallb-grade-kind {
  font-size: .68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--mid);
}
.wallb-grade-col .g-badge { width: auto; min-width: 34px; padding: 0 10px; height: 30px; font-size: 1rem; }
.wallb-grade-none { font-size: 1.3rem; font-weight: 800; color: var(--mid); }
.wallb-grade-votes { font-size: .72rem; color: var(--mid); }
.wallb-grade-sep {
  align-self: center;
  font-size: .78rem;
  font-weight: 700;
  color: var(--mid);
  text-transform: uppercase;
}

.wallb-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: 6px 0; }
.wallb-meta .spray-styles { margin: 0; }
.wallb-sendcount { font-size: .82rem; color: var(--mid); font-weight: 600; }
.wallb-ffh { font-size: .82rem; color: var(--dark); }
.wallb-rules {
  width: 100%;
  font-size: .85rem;
  color: var(--dark);
  background: var(--light);
  border-left: 3px solid var(--primary);
  border-radius: 6px;
  padding: 8px 12px;
  white-space: pre-wrap;
  word-break: break-word;
}

.wallb-actions { display: flex; flex-wrap: wrap; gap: 10px; margin: 12px 0; }

/* Suggest-grade vote chips. */
.wallb-vote-block { margin: 12px 0; }
.wallb-vote-label { font-size: .85rem; font-weight: 700; color: var(--dark); margin-bottom: 8px; }
.wallb-vote-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.wallb-vote-chip {
  background: var(--light);
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 5px 12px;
  font-size: .82rem;
  font-weight: 700;
  color: var(--dark);
  cursor: pointer;
  transition: background .12s, border-color .12s, color .12s;
}
.wallb-vote-chip:hover { border-color: var(--primary); }
.wallb-vote-chip.on { color: #fff; }
/* #1449 — colour-only vote/suggest swatch chips: circular colour buttons, no text
   on the face (label = accessible name/title). Selection is a thick ring; the
   per-step pattern (data-gp) keeps circuits distinguishable without hue. */
.wallb-vote-chip--co {
  width: 30px; height: 30px; padding: 0; border-radius: 50%;
  color: transparent; box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .18);
}
.wallb-vote-chip--co[data-gp="0"] { outline: 2px solid rgba(0,0,0,.28); outline-offset: -2px; }
.wallb-vote-chip--co[data-gp="1"] { outline: 2px dashed rgba(0,0,0,.4); outline-offset: -2px; }
.wallb-vote-chip--co[data-gp="2"] { outline: 2px double rgba(0,0,0,.45); outline-offset: -3px; }
.wallb-vote-chip--co[data-gp="3"] { box-shadow: inset 0 0 0 1px rgba(0,0,0,.18),
  inset 0 0 0 6px rgba(255,255,255,.5); }
.wallb-vote-chip--co.on { box-shadow: 0 0 0 3px var(--primary); }

/* Log-a-send sheet. */
.wallb-log-sheet {
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 14px 16px;
  margin: 10px 0;
  background: var(--light);
}
.wallb-log-title { font-size: .92rem; font-weight: 700; color: var(--dark); margin-bottom: 8px; }
.wallb-attempts-row { display: flex; align-items: center; gap: 10px; font-size: .88rem; margin-top: 8px; }
.wallb-attempts-val { min-width: 26px; text-align: center; font-weight: 800; font-size: 1.05rem; color: var(--dark); }

.wallb-detail-footer {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
}
.wallb-detail-manage { display: flex; gap: 8px; flex-wrap: wrap; }
.wallb-report-btn { margin-left: auto; color: var(--mid); }
.wallb-report-form { margin-top: 12px; }

/* List card grade chip (compact creator + community, when present). */
.wallb-grade-chip { display: inline-flex; align-items: center; gap: 4px; }
.wallb-grade-chip-label { font-size: .68rem; color: var(--mid); font-weight: 600; }
.wallb-grade-chip-meta { font-size: .68rem; color: var(--mid); }

/* ── Member-stats: separate "Community boulders" pyramid card (#846 §5) ──
   Visually distinct from the official pyramid: a left accent + tinted heading
   make the separation unmistakable. */
.community-card {
  border-left: 4px solid var(--primary);
  background: linear-gradient(180deg, var(--primary-light), transparent 120px);
}
.community-card .card-title .icon { filter: none; }

/* ── Public profile: community-sent counter ── */
.profile-community-count {
  font-size: .82rem;
  font-weight: 700;
  color: var(--primary-dark);
  margin-top: 6px;
}

/* Narrow screens: stack the side-by-side grades but keep them distinct. */
@media (max-width: 420px) {
  .wallb-grades { flex-direction: column; }
  .wallb-grade-sep { transform: none; }
}

/* ── Rope Climbing — Routes (epic #1494 M2) ──────────────────────────────────
   Public browse (routes.html) + detail (grade.html?d=rope) surfaces. Reuses
   the shared .boulder-tile/.bt-* (tile-render.js), .card/.fchip/.g-badge and
   dark-token conventions everywhere possible; this block only adds the truly
   route-specific pieces: the climb-type badge, the route-line SVG overlay,
   and the multi-photo pager. */

/* Climb-type badge (lead / toprope / autobelay) — a small pill mirroring the
   colour-chip visual weight, tinted-chip family (#1488) for AA contrast. */
.climb-type-badge {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 9px; border-radius: 999px; font-size: .74rem; font-weight: 700;
  background: var(--chip-info-bg); color: var(--chip-info-fg);
}
:root[data-theme="dark"] .climb-type-badge { background: var(--night-high); color: var(--chalk); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .climb-type-badge { background: var(--night-high); color: var(--chalk); }
}

/* Route-photo frame: position:relative wrapper so the SVG line overlay
   (route-line.js) sits exactly on top of the wall photo, same technique as
   .spray-picker-frame / .gym-map-overlay (inset:0 svg on a relative parent). */
.route-photo-frame { position: relative; display: block; line-height: 0; border-radius: 8px; overflow: hidden; }
.route-photo-frame img { display: block; width: 100%; height: auto; }
.route-line-svg { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; pointer-events: none; }
.route-line-halo   { opacity: .9; }
.route-line-stroke { opacity: .98; }

/* Multi-photo pager (routes whose line spans >1 wall photo, #1494 Q14). Mirrors
   the wall-photo picker strip pattern (community.html .wallb-photo-thumb) but
   read-only — no upload/edit affordance on the member-facing viewer. */
.route-photo-pager { display: flex; gap: 8px; margin-top: 8px; flex-wrap: wrap; }
.route-photo-pager button {
  border: 2px solid var(--border); border-radius: 8px; padding: 0; width: 56px; height: 56px;
  overflow: hidden; cursor: pointer; background: var(--light); flex-shrink: 0;
}
.route-photo-pager button.on { border-color: var(--primary); }
.route-photo-pager img { width: 100%; height: 100%; object-fit: cover; display: block; }
.route-photo-pager button:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }

/* Style chip focus state (#808 a11y sweep) — the boulder page never defined
   one (research finding); routes gets it from the start rather than copying
   the gap forward. Safe, additive: also benefits any existing .fchip/.bt-fav
   users of :focus-visible since browsers ignore an unknown-at-authoring-time
   selector on elements that don't match — no visual change to boulder pages. */
.fchip:focus-visible,
.bt-fav:focus-visible,
.boulder-style-chip:focus-visible,
.route-photo-pager button:focus-visible {
  outline: 2px solid var(--primary); outline-offset: 2px;
}

/* ── Training Layer #1496 T1/T2/M3 — Sessions/Goals shared components ──
   Shared between member-stats.html (Sessions/Goals tabs) and member-public.html
   (public-profile goal cards) so both pages render the identical goal-card
   look without duplicating CSS. */
.session-card {
  border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px;
  margin-bottom: 10px; background: var(--light);
}
.session-card-head {
  display: flex; align-items: center; gap: 10px; cursor: pointer;
  font-size: .9rem; font-weight: 700; color: var(--dark);
}
.session-card-head:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
.session-card-meta { font-size: .78rem; color: var(--mid); font-weight: 400; margin-left: auto; }
.session-card-detail { display: none; margin-top: 10px; font-size: .84rem; color: var(--dark); }
.session-card.open .session-card-detail { display: block; }
.session-card-sends { margin-top: 8px; display: grid; gap: 6px; }
.session-send-row {
  display: flex; align-items: center; gap: 8px; font-size: .82rem;
  padding: 4px 0; border-bottom: 1px solid var(--border);
}
.sessions-cal-grid {
  display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; margin-top: 10px;
}
.sessions-cal-day {
  border: 1px solid var(--border); border-radius: 6px; min-height: 44px;
  padding: 4px; font-size: .74rem; color: var(--mid); background: var(--light);
}
.sessions-cal-day.has-session { background: rgba(232,115,26,.14); border-color: var(--primary); color: var(--dark); font-weight: 700; }
:root[data-theme="dark"] .session-card { background: var(--night-high); border-color: var(--night-stroke); color: var(--chalk); }
:root[data-theme="dark"] .session-card-head { color: var(--chalk); }
:root[data-theme="dark"] .sessions-cal-day { background: var(--night-high); border-color: var(--night-stroke); color: var(--chalk-soft); }
:root[data-theme="dark"] .sessions-cal-day.has-session { background: rgba(232,115,26,.22); color: var(--chalk); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .session-card { background: var(--night-high); border-color: var(--night-stroke); color: var(--chalk); }
  :root:not([data-theme="light"]) .session-card-head { color: var(--chalk); }
  :root:not([data-theme="light"]) .sessions-cal-day { background: var(--night-high); border-color: var(--night-stroke); color: var(--chalk-soft); }
  :root:not([data-theme="light"]) .sessions-cal-day.has-session { background: rgba(232,115,26,.22); color: var(--chalk); }
}

.goal-card {
  border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px;
  margin-bottom: 10px; background: var(--light);
}
.goal-card-head { display: flex; align-items: center; gap: 8px; font-weight: 700; color: var(--dark); font-size: .92rem; }
.goal-card-type { font-size: .72rem; color: var(--mid); text-transform: uppercase; letter-spacing: .03em; }
.goal-progress-bar {
  height: 8px; border-radius: 999px; background: var(--border); overflow: hidden; margin: 8px 0;
}
.goal-progress-fill { height: 100%; background: var(--primary); border-radius: 999px; transition: width .3s ease; }
.goal-progress-fill.done { background: #2e9e5b; }
.goal-card-actions { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; }
.goal-card-actions button {
  font-size: .74rem; padding: 4px 10px; border-radius: 999px; border: 1px solid var(--border);
  background: #fff; color: var(--dark); cursor: pointer;
}
.goal-card-actions button:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }
.goal-card-public-toggle { margin-left: auto; font-size: .74rem; display: flex; align-items: center; gap: 4px; }
:root[data-theme="dark"] .goal-card { background: var(--night-high); border-color: var(--night-stroke); color: var(--chalk); }
:root[data-theme="dark"] .goal-card-head { color: var(--chalk); }
:root[data-theme="dark"] .goal-card-actions button { background: var(--night-surface); color: var(--chalk); border-color: var(--night-stroke); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .goal-card { background: var(--night-high); border-color: var(--night-stroke); color: var(--chalk); }
  :root:not([data-theme="light"]) .goal-card-head { color: var(--chalk); }
  :root:not([data-theme="light"]) .goal-card-actions button { background: var(--night-surface); color: var(--chalk); border-color: var(--night-stroke); }
}

/* Goal dialog (mirrors member-plan-session.html's sent-overlay-backdrop pattern) */
.goal-dialog-backdrop {
  position: fixed; inset: 0; z-index: 9800; background: rgba(20,20,30,.60);
  display: none; align-items: center; justify-content: center; padding: 16px;
}
.goal-dialog-backdrop.open { display: flex; }
.goal-dialog-card {
  background: var(--card, #fff); border-radius: 14px; width: 100%; max-width: 420px;
  max-height: 90vh; overflow-y: auto; padding: 20px 18px; box-shadow: 0 18px 60px rgba(0,0,0,.28);
}
.goal-dialog-card h2 { margin: 0 0 12px; font-size: 1.1rem; color: var(--dark); }
.goal-dialog-actions { display: flex; gap: 8px; justify-content: flex-end; margin-top: 16px; }
.goal-dialog-err { color: #c0392b; font-size: .82rem; margin-top: 8px; }
.goal-field { margin-bottom: 12px; }
.goal-field label { display: block; font-size: .8rem; font-weight: 700; margin-bottom: 6px; color: var(--dark); }
.goal-field select, .goal-field input {
  width: 100%; box-sizing: border-box; border: 1px solid var(--border); border-radius: 8px;
  padding: 8px 10px; font: inherit; color: var(--dark); background: #fff;
}
:root[data-theme="dark"] .goal-dialog-card { background: var(--night-high); color: var(--chalk); }
:root[data-theme="dark"] .goal-dialog-card h2 { color: var(--chalk); }
:root[data-theme="dark"] .goal-field label { color: var(--chalk); }
:root[data-theme="dark"] .goal-field select, :root[data-theme="dark"] .goal-field input {
  background: var(--night-surface); color: var(--chalk); border-color: var(--night-stroke);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .goal-dialog-card { background: var(--night-high); color: var(--chalk); }
  :root:not([data-theme="light"]) .goal-dialog-card h2 { color: var(--chalk); }
  :root:not([data-theme="light"]) .goal-field label { color: var(--chalk); }
  :root:not([data-theme="light"]) .goal-field select, :root:not([data-theme="light"]) .goal-field input {
    background: var(--night-surface); color: var(--chalk); border-color: var(--night-stroke);
  }
}

/* Gym news structured attachment chip (#1494 M3 Q48 / #1496 M3 web) — deep-
   links a post's resolved wall/climb/setting-day attachment into routes.html
   (rope) or grade.html/index.html (boulder). */
.news-attachment-chip {
  display: inline-flex; align-items: center; gap: 6px; margin-top: 8px;
  padding: 5px 12px; border-radius: 999px; border: 1px solid var(--border);
  background: var(--light); color: var(--dark); font-size: .82rem; font-weight: 600;
  text-decoration: none;
}
.news-attachment-chip:hover { border-color: var(--primary); color: var(--primary-dark); }
.news-attachment-chip:focus-visible { outline: 2px solid var(--primary); outline-offset: 2px; }
:root[data-theme="dark"] .news-attachment-chip {
  background: var(--night-high); border-color: var(--night-stroke); color: var(--chalk);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .news-attachment-chip {
    background: var(--night-high); border-color: var(--night-stroke); color: var(--chalk);
  }
}

/* ── admin theme overrides ── */
:root{--primary:#E8731A;--primary-dark:#be5e15;--primary-light:#fceee3;--dark:#2B2D42;--light:#F7F4EF;--border:#DDD5C8;}