/* =========================================================================
   GLOBAL STYLES
   ========================================================================= */

@import url('https://fonts.googleapis.com/css2?family=Quicksand:wght@400;500;600;700&display=swap');

/* =========================================================================
   DESIGN TOKENS
   Every colour, radius, shadow and type step lives here. Component classes
   and module code reference these and never hardcode a value — change a
   token once and it moves everywhere, in both themes. tools/test-colors.mjs
   fails on a new colour literal anywhere else.

   Three layers:
   1. HUES — twelve named hues, four steps each: wash (a tinted background),
      soft (a fill or outline), base (the colour itself) and ink (text on its
      wash). The only place most colour values are written.
   2. ROLES — what a colour means: --good, --cat-mens-*, --zone-ut2,
      --phase-build, --role-captain-*. Modules use these, never a hue.
   3. THEMES — :root is light. :root[data-theme="dark"] redefines the hues
      and the neutrals; the roles follow by themselves. data-theme and
      data-style (classic | ocean) are set before first paint by
      src/js/theme-boot.js from the member's Appearance choice.
   ========================================================================= */
:root {
  color-scheme: light;

  /* ── 1. Hues ──────────────────────────────────────────────────────── */
  --hue-slate-wash:     #F1F4F7; --hue-slate-soft:     #DCE2E9;
  --hue-slate-base:     #5B6B7E; --hue-slate-ink:      #334155;
  --hue-ocean-wash:     #E7F1FB; --hue-ocean-soft:     #C7DDF3;
  --hue-ocean-base:     #0B72CE; --hue-ocean-ink:      #0A4F91;
  --hue-sky-wash:       #E6F4F8; --hue-sky-soft:       #C4E3EC;
  --hue-sky-base:       #2489A8; --hue-sky-ink:        #155F75;
  --hue-indigo-wash:    #EEF0FC; --hue-indigo-soft:    #D6DBF7;
  --hue-indigo-base:    #4650C8; --hue-indigo-ink:     #2C3493;
  --hue-violet-wash:    #F3EFFB; --hue-violet-soft:    #E0D6F4;
  --hue-violet-base:    #7A4FC6; --hue-violet-ink:     #523188;
  --hue-plum-wash:      #F7EEF5; --hue-plum-soft:      #EBD5E5;
  --hue-plum-base:      #9E3A84; --hue-plum-ink:       #6E2560;
  --hue-rose-wash:      #FCEBF0; --hue-rose-soft:      #F6CBD6;
  --hue-rose-base:      #BE123C; --hue-rose-ink:       #8A0F2E;
  --hue-copper-wash:    #F9EDE5; --hue-copper-soft:    #F0D3C1;
  --hue-copper-base:    #A8481F; --hue-copper-ink:     #7A3314;
  --hue-amber-wash:     #FBF1E3; --hue-amber-soft:     #F3DABB;
  --hue-amber-base:     #B45309; --hue-amber-ink:      #7C3A06;
  --hue-gold-wash:      #FBF5E1; --hue-gold-soft:      #F1E3B0;
  --hue-gold-base:      #A57A06; --hue-gold-ink:       #6E5204;
  --hue-green-wash:     #E8F5EC; --hue-green-soft:     #C6E6D0;
  --hue-green-base:     #15803D; --hue-green-ink:      #0F5A2B;
  --hue-teal-wash:      #E4F4F3; --hue-teal-soft:      #BFE3E1;
  --hue-teal-base:      #0E7C7E; --hue-teal-ink:       #0A5A5C;

  /* ── Neutrals ─────────────────────────────────────────────────────── */
  /* Canvas & surfaces — cool light grey. Raised = a menu or sheet above a card. */
  --canvas:         #EEF1F5;
  --surface:        #FFFFFF;
  --surface-raised: #FFFFFF;
  --surface-sunk:   #E5EAF0;

  /* Ink */
  --ink:           #0F172A;   /* primary headings, body */
  --ink-muted:     #64748B;   /* secondary text, metadata, timestamps */
  --ink-faint:     #94A3B8;   /* placeholder, disabled */

  /* Primary — near-black ink for primary actions (buttons, FAB, CTAs). In
     dark mode it turns near-white, so the sidebar and dock use --chrome. */
  --primary:       #16181D;
  --primary-hover: #2A2D34;
  --primary-ink:   #FFFFFF;

  /* Text on a strong coloured fill (a danger button, a badge, the cox row of
     an accepted boat): white in light, near-black in dark. */
  --on-fill:       #FFFFFF;

  /* Chrome — the ink sidebar, the mobile dock and the public regatta header.
     Dark in both themes; the --chrome-* inks are for text on it. */
  --chrome:        #16181D;
  --chrome-ink:    #FFFFFF;
  --chrome-muted:  #9BA3AF;
  --chrome-faint:  rgba(255, 255, 255, 0.4);
  --chrome-hover:  rgba(255, 255, 255, 0.06);
  --chrome-active: rgba(255, 255, 255, 0.1);
  --chrome-line:   rgba(255, 255, 255, 0.07);
  --chrome-accent: #4DA3E8;   /* the active icon on the chrome */
  --chrome-good:   #6EE7A0;   /* the active sub-item, the toast tick */
  --chrome-warn:   #F5C26B;   /* "running late" on the public regatta header */
  --chrome-bad:    #F87171;
  --chrome-sky:    #8EC9FF;   /* the target rate on the interval runner */

  /* Accent — deep ocean blue, pulled from the blue end of the Withe mark.
     Reserved for ACTIVE states and identity only — never buttons. */
  --accent:        var(--hue-ocean-base);
  --accent-strong: #0A5FAE;   /* accent as text on white: meets contrast */
  --accent-wash:   var(--hue-ocean-wash);
  --accent-ink:    #FFFFFF;   /* text on an accent fill */

  /* Semantic — separate from the accent, never used decoratively */
  --good:          var(--hue-green-base);
  --good-wash:     var(--hue-green-wash);
  --good-soft:     var(--hue-green-soft);
  --good-ink:      var(--hue-green-ink);
  --good-bright:   #22C55E;   /* the live-plan dot */
  --warn:          var(--hue-amber-base);
  --warn-wash:     var(--hue-amber-wash);
  --warn-soft:     var(--hue-amber-soft);
  --warn-ink:      var(--hue-amber-ink);
  --bad:           var(--hue-rose-base);
  --bad-wash:      var(--hue-rose-wash);
  --bad-soft:      var(--hue-rose-soft);
  --bad-ink:       var(--hue-rose-ink);
  --bad-hover:     #A50F34;

  /* Menu section labels — Withe Club and Admin — in the blues of the Withe
     mark: the darkest on white (5.6:1), a bright one on the ink sidebar
     (7.8:1), where the dark blue reads at only 3.2:1. */
  --nav-group:        #0C6BB2;
  --nav-group-on-ink: #55B8DA;

  /* Tier — Withe Racing's copper. Marks the Racing nav label and the upgrade
     prompts only; the other nav labels are blue. Never a button colour. */
  --racing:        var(--hue-copper-base);
  --racing-on-ink: #E29A6B;   /* the same copper on the ink sidebar */
  --racing-wash:   var(--hue-copper-wash);
  --racing-line:   rgba(168, 72, 31, 0.3);

  /* ── 2. Roles ─────────────────────────────────────────────────────── */
  /* Crew categories: men's, women's, mixed (rota boards, session picker,
     crews, regattas, hosting). Mixed is violet — what indigo and plum make. */
  --cat-mens-wash:   var(--hue-indigo-wash);  --cat-mens-soft:   var(--hue-indigo-soft);
  --cat-mens-base:   var(--hue-indigo-base);  --cat-mens-ink:    var(--hue-indigo-ink);
  --cat-womens-wash: var(--hue-plum-wash);    --cat-womens-soft: var(--hue-plum-soft);
  --cat-womens-base: var(--hue-plum-base);    --cat-womens-ink:  var(--hue-plum-ink);
  --cat-mixed-wash:  var(--hue-violet-wash);  --cat-mixed-soft:  var(--hue-violet-soft);
  --cat-mixed-base:  var(--hue-violet-base);  --cat-mixed-ink:   var(--hue-violet-ink);

  /* Training zones (British Rowing order) and plan phases */
  --zone-ut2:       var(--hue-teal-base);
  --zone-ut1:       var(--hue-green-base);
  --zone-at:        var(--hue-sky-base);
  --zone-tr:        var(--hue-plum-base);
  --zone-an:        var(--hue-rose-base);
  --zone-power:     var(--hue-indigo-base);
  --zone-strength:  var(--hue-gold-base);
  --zone-technique: var(--hue-violet-base);
  --zone-recovery:  var(--hue-slate-base);
  --zone-test:      var(--hue-amber-base);
  --phase-base:     var(--hue-sky-base);
  --phase-build:    var(--hue-indigo-base);
  --phase-winter:   var(--hue-slate-base);
  --phase-race:     var(--hue-rose-base);
  --phase-taper:    var(--hue-gold-base);
  --phase-peak:     var(--primary);

  /* Club roles on a profile */
  --role-captain-wash: var(--hue-ocean-wash); --role-captain-ink: var(--hue-ocean-ink);
  --role-cox-wash:     var(--hue-teal-wash);  --role-cox-ink:     var(--hue-teal-ink);

  /* Home's On the water tiles: the colour of each kind's watermark */
  --cond-sun:   var(--hue-amber-base);
  --cond-cold:  var(--hue-sky-base);
  --cond-water: var(--hue-teal-base);
  --cond-sea:   var(--hue-ocean-base);

  /* Identity gradient — the avatar and the Withe mark */
  --brand-gradient: linear-gradient(135deg, #27C0A8, #0B72CE);
  --on-brand:       #FFFFFF;   /* text on the brand gradient, both themes */

  /* Heat map (attendance, results) — four steps of the accent */
  --heat-1: #D6E6F6; --heat-2: #9FC6EC; --heat-3: #4F97DA; --heat-4: var(--accent-strong);

  /* Hairlines & shadow — borders do the work, not drop shadows */
  --hairline:      rgba(0, 0, 0, 0.05);
  --hairline-firm: rgba(0, 0, 0, 0.09);
  --shadow-micro:  0 1px 3px rgba(0, 0, 0, 0.04);
  --shadow-soft:   0 2px 8px rgba(0, 0, 0, 0.07);
  --shadow-lift:   0 6px 20px -8px rgba(15, 23, 42, 0.18);
  --shadow-float:  0 8px 26px -6px rgba(15, 23, 42, 0.45);
  --scrim:         rgba(15, 23, 42, 0.4);
  --scrim-firm:    rgba(15, 23, 42, 0.46);
  --on-scrim:      #FFFFFF;   /* a ring or text drawn over the scrim */

  /* Frosted glass */
  --frost-bg:      rgba(255, 255, 255, 0.85);
  --frost-solid:   rgba(255, 255, 255, 0.97);   /* no backdrop-filter support */
  --frost-canvas:  rgba(238, 241, 245, 0.86);   /* frost tinted to the grey canvas */
  --frost-blur:    blur(12px) saturate(160%);
  --frost-edge:    1px solid rgba(0, 0, 0, 0.06);
  --skeleton-sheen: rgba(255, 255, 255, 0.6);

  /* Tinted fills for chips and badges */
  --tint:          rgba(0, 0, 0, 0.04);
  --tint-firm:     rgba(0, 0, 0, 0.07);

  /* Controls */
  --switch-off:    #CBD2DB;
  --switch-knob:   #FFFFFF;
  --scrollbar:     #CDD6E0;
  --hover-surface: #FBFCFD;
  --toast-tick:    #86EFAC;
  --control-on:    #FFFFFF;   /* the selected pill on a tinted segmented track */

  /* Buttons — glass lift (.w-btn) */
  --btn-glass:      rgba(255, 255, 255, 0.72);
  --btn-glass-edge: rgba(15, 23, 42, 0.1);
  --btn-shine:      #FFFFFF;
  --btn-shade:      #0F172A;
  --btn-sheen:      16%;

  /* Ocean style — the sea backdrop behind the canvas and the frosted glass
     on the dock, the menus, the sheets and the Next outing card */
  --ocean-backdrop:
    radial-gradient(120% 60% at 0% 0%, #CFE6F7 0%, transparent 60%),
    radial-gradient(90% 50% at 100% 35%, #D9EFEA 0%, transparent 65%),
    radial-gradient(120% 70% at 50% 100%, #C9D9F2 0%, transparent 70%), #EAF0F6;
  --glass-bg:      rgba(255, 255, 255, 0.56);
  --glass-strong:  rgba(244, 247, 251, 0.78);
  --glass-menu:    rgba(248, 250, 253, 0.94);   /* menus and sheets: text sits on them */
  --glass-edge:    rgba(255, 255, 255, 0.75);
  --glass-shadow:  0 8px 24px -12px rgba(15, 40, 80, 0.25), inset 0 1px 0 rgba(255, 255, 255, 0.8);
  --glass-blur:    blur(18px) saturate(170%);

  /* Radii */
  --r-card:        15px;
  --r-control:     10px;
  --r-pill:        9999px;

  /* Type — Quicksand ships 300–700 only; weight-800 requests resolve to 700 */
  --font-sans: 'Quicksand', ui-sans-serif, system-ui,
               -apple-system, 'SF Pro Text', 'Segoe UI', sans-serif;

  /* Spacing rhythm */
  --sp-1: 4px;  --sp-2: 8px;  --sp-3: 12px;
  --sp-4: 16px; --sp-5: 20px; --sp-6: 24px; --sp-8: 32px;

  /* Shell metrics — shared by CSS and the responsive shell */
  --tabbar-h: 62px;
}

/* ── 3. Dark theme ──────────────────────────────────────────────────────
   Night navy. Surfaces get LIGHTER as they rise (canvas → card → raised),
   because shadows do not show on a dark ground. Washes are opaque tints of
   each hue over the card surface, so sticky headers never show through. */
:root[data-theme="dark"] {
  color-scheme: dark;

  --hue-slate-wash:     #27303E; --hue-slate-soft:     #37404F;
  --hue-slate-base:     #9AA8BA; --hue-slate-ink:      #D5DDE6;
  --hue-ocean-wash:     #1C2F45; --hue-ocean-soft:     #233F5B;
  --hue-ocean-base:     #4DA3E8; --hue-ocean-ink:      #BFDDF7;
  --hue-sky-wash:       #1C3242; --hue-sky-soft:       #234457;
  --hue-sky-base:       #4FB6D6; --hue-sky-ink:        #C2E8F3;
  --hue-indigo-wash:    #252D46; --hue-indigo-soft:    #333B5D;
  --hue-indigo-base:    #8C93F0; --hue-indigo-ink:     #D3D6FB;
  --hue-violet-wash:    #2A2C46; --hue-violet-soft:    #3C395D;
  --hue-violet-base:    #AE8CF0; --hue-violet-ink:     #E3D8FB;
  --hue-plum-wash:      #302B40; --hue-plum-soft:      #473752;
  --hue-plum-base:      #D985C4; --hue-plum-ink:       #F3D3EB;
  --hue-rose-wash:      #332838; --hue-rose-soft:      #4E3245;
  --hue-rose-base:      #F47191; --hue-rose-ink:       #FCD0DB;
  --hue-copper-wash:    #312E33; --hue-copper-soft:    #4A3D3B;
  --hue-copper-base:    #E29A6B; --hue-copper-ink:     #F6D7C2;
  --hue-amber-wash:     #332F2E; --hue-amber-soft:     #4D4032;
  --hue-amber-base:     #F0A64A; --hue-amber-ink:      #FAE0BC;
  --hue-gold-wash:      #323331; --hue-gold-soft:      #4B4736;
  --hue-gold-base:      #E8C25A; --hue-gold-ink:       #F7E8BC;
  --hue-green-wash:     #1E3437; --hue-green-soft:     #264943;
  --hue-green-base:     #5BC98A; --hue-green-ink:      #CDEFD9;
  --hue-teal-wash:      #1A323C; --hue-teal-soft:      #1F454C;
  --hue-teal-base:      #3FB8AE; --hue-teal-ink:       #C4EDE9;

  --canvas:         #0B111C;
  --surface:        #141C2A;
  --surface-raised: #1A2433;
  --surface-sunk:   #0F1622;

  --ink:           #E6ECF3;
  --ink-muted:     #93A1B3;
  --ink-faint:     #66748A;

  --primary:       #EEF2F7;
  --primary-hover: #FFFFFF;
  --primary-ink:   #0B111C;
  --on-fill:       #0B111C;

  /* The chrome sits a shade below the canvas, edged by a hairline */
  --chrome:        #070B12;

  --accent-strong: #7CBCF0;
  --accent-ink:    #0B111C;
  --bad-hover:     #F7899F;
  --nav-group:        #55B8DA;
  --racing-line:   rgba(226, 154, 107, 0.34);

  --heat-1: #1C2F45; --heat-2: #234B70; --heat-3: #3B83C4; --heat-4: #7CBCF0;

  --hairline:      rgba(255, 255, 255, 0.06);
  --hairline-firm: rgba(255, 255, 255, 0.1);
  --shadow-micro:  0 1px 2px rgba(0, 0, 0, 0.3);
  --shadow-soft:   0 2px 8px rgba(0, 0, 0, 0.4);
  --shadow-lift:   0 10px 28px -10px rgba(0, 0, 0, 0.7);
  --shadow-float:  0 10px 30px -6px rgba(0, 0, 0, 0.7);
  --scrim:         rgba(2, 5, 10, 0.6);
  --scrim-firm:    rgba(2, 5, 10, 0.66);

  --frost-bg:      rgba(20, 28, 42, 0.85);
  --frost-solid:   rgba(20, 28, 42, 0.98);
  --frost-canvas:  rgba(11, 17, 28, 0.86);
  --frost-edge:    1px solid rgba(255, 255, 255, 0.07);
  --skeleton-sheen: rgba(255, 255, 255, 0.06);

  --tint:          rgba(255, 255, 255, 0.05);
  --tint-firm:     rgba(255, 255, 255, 0.09);

  --switch-off:    #3A4658;
  --switch-knob:   #E6ECF3;
  --scrollbar:     #2A3648;
  --hover-surface: #18212F;
  --toast-tick:    #15803D;
  --control-on:    #2A3648;

  --btn-glass:      rgba(34, 46, 66, 0.62);
  --btn-glass-edge: rgba(255, 255, 255, 0.12);
  --btn-shade:      #000000;

  --ocean-backdrop:
    radial-gradient(120% 60% at 0% 0%, #123A5C 0%, transparent 60%),
    radial-gradient(90% 50% at 100% 40%, #0E3B3A 0%, transparent 65%),
    radial-gradient(120% 70% at 50% 100%, #1B2552 0%, transparent 70%), #070C15;
  --glass-bg:      rgba(24, 36, 54, 0.5);
  --glass-strong:  rgba(20, 30, 46, 0.78);
  --glass-menu:    rgba(26, 36, 51, 0.94);
  --glass-edge:    rgba(255, 255, 255, 0.09);
  --glass-shadow:  0 10px 30px -14px rgba(0, 0, 0, 0.7), inset 0 1px 0 rgba(255, 255, 255, 0.06);
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: var(--font-sans);
  background: var(--canvas);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

::selection {
  background: var(--accent);
  color: var(--accent-ink);
}

::-webkit-scrollbar {
  width: 10px;
  height: 10px;
}
::-webkit-scrollbar-thumb {
  background: var(--scrollbar);
  border-radius: 8px;
  border: 2px solid var(--canvas);
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* =========================================================================
   TYPE ROLES
   ========================================================================= */
.w-title {
  font-size: 26px;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.15;
  color: var(--ink);
  text-wrap: balance;
  margin: 0;
}

.w-heading {
  font-size: 15.5px;
  font-weight: 600;
  letter-spacing: -0.005em;
  line-height: 1.3;
  color: var(--ink);
  margin: 0;
}

.w-meta {
  font-size: 13.5px;
  font-weight: 500;
  line-height: 1.45;
  color: var(--ink-muted);
}

.w-badge {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 12.5px;
  font-weight: 700;
  height: 31px;               /* the same box as .w-btn.is-sm, so a status */
  padding: 0 13px;            /* sits level with the button beside it      */
  border-radius: var(--r-control);
  background: var(--tint);
  color: var(--ink-muted);
  white-space: nowrap;
}

.w-badge.is-good { background: var(--good-wash); color: var(--good); }
.w-badge.is-warn { background: var(--warn-wash); color: var(--warn); }
.w-badge.is-bad  { background: var(--bad-wash);  color: var(--bad); }
.w-badge.is-accent { background: var(--accent-wash); color: var(--accent-strong); }
.w-badge.is-done { background: var(--surface); color: var(--ink-muted); border: 1px solid var(--hairline-firm); }

/* =========================================================================
   CARDS
   A card is a 3-part horizontal flow:
     left   visual anchor (date badge, avatar, boat marker)
     center title + metadata, takes the slack
     right  status tag or quick action
   ========================================================================= */
.w-card {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-card);
  box-shadow: var(--shadow-micro);
  padding: var(--sp-4);
  transition: transform 0.12s ease, box-shadow 0.12s ease, border-color 0.12s ease;
}

.w-card + .w-card { margin-top: var(--sp-3); }

.w-card.is-tappable { cursor: pointer; }
.w-card.is-tappable:hover { border-color: var(--hairline-firm); }
.w-card.is-tappable:active { transform: scale(0.99); }

.w-card.is-selected {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent), var(--shadow-micro);
}

.w-card-row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
}

.w-card-body {
  flex: 1;
  min-width: 0;              /* lets long titles ellipsis instead of pushing */
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.w-card-body .w-heading,
.w-card-body .w-meta {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.w-card-aside {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

/* =========================================================================
   FOLDABLE CARD — the corner fold
   The one way a card folds. A 48px triangle sits in the card's top-right
   corner and is the whole control: tap it to unfold, tap it again to fold.
   The card itself keeps its own border, radius and padding; it only adds
   `w-fold` (plus `is-open` / `is-mine`) and ends with an empty
   `<button class="w-fold-btn">`. The header wears `w-fold-head` so its text
   stays clear of the corner. What is inside the fold is the caller's: render
   it when open, leave it out when not. Grey when folded, blue when open,
   green when the card is the viewer's own (booked, seated, signed in).
   ========================================================================= */
.w-fold { position: relative; overflow: hidden; }
/* A week of folded session cards, side by side on a wide screen and one
   column on a phone, like Rowing Availability (#274). An opened card stays in
   its column, about a phone's width, so its boats go two to a row as they do
   on a phone; the gym card takes the full width. */
.w-card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(250px, 100%), 1fr)); gap: 14px; align-items: start; }
.w-card-grid > * { margin: 0 !important; }
.w-card-grid .w-boat-row { flex-wrap: wrap; }
.w-card-grid .w-boat-card { flex: 1 1 calc(50% - 5px) !important; max-width: calc(50% - 5px); }
.w-card-grid .w-boat-card.is-gym { flex-basis: 100% !important; max-width: 100%; }
.w-fold-head { padding-right: 40px; }

.w-fold-btn {
  position: absolute;
  top: 0;
  right: 0;
  width: 48px;
  height: 48px;
  padding: 0;
  margin: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.w-fold-btn::before {                /* the wedge */
  content: "";
  position: absolute;
  inset: 0;
  background: var(--surface-sunk);
  clip-path: polygon(0 0, 100% 0, 100% 100%);
  transition: background 0.15s ease;
}
.w-fold-btn::after {                 /* the triangle, pointing down = "opens" */
  content: "";
  position: absolute;
  top: 9px;
  right: 9px;
  width: 0;
  height: 0;
  border-left: 7px solid transparent;
  border-right: 7px solid transparent;
  border-top: 9px solid var(--ink);
  transition: transform 0.18s ease;
}
.w-fold-btn:hover::before { background: var(--accent-wash); }
.w-fold-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: -4px; border-radius: 0 12px 0 0; }

.w-fold.is-mine .w-fold-btn::before { background: var(--good-wash); }
.w-fold.is-mine .w-fold-btn::after { border-top-color: var(--good); }

/* Finished: the card sinks to the page colour with a dashed edge (the frame
   is inline on the rota cards); this dims the anchor and the corner. */
.w-fold.is-done .w-anchor { background: var(--surface); }
.w-fold.is-done .w-anchor-main { color: var(--ink-muted); }
.w-fold.is-done .w-fold-btn::before { background: var(--surface); }
.w-fold.is-done .w-fold-btn::after { border-top-color: var(--ink-faint); }

.w-fold.is-open .w-fold-btn::before { background: var(--accent-wash); }
.w-fold.is-open .w-fold-btn::after { transform: rotate(180deg); border-top-color: var(--accent-strong); }

/* The ⋯ button on a card, and the small menu it opens. The menu opens to the
   button's LEFT, level with it: a folded card clips its overflow (the corner
   fold) and the button sits on its lowest line, so there is no room below,
   and opening upwards would cover the card's title. */
.w-more { position: relative; flex-shrink: 0; }
.w-more-btn {
  width: 32px;
  height: 32px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--hairline-firm);
  border-radius: var(--r-control);
  background: var(--surface);
  color: var(--ink-muted);
  font-family: inherit;
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
}
.w-more-btn:hover, .w-more-btn[aria-expanded="true"] { background: var(--tint); color: var(--ink); }
.w-more-menu {
  position: absolute;
  right: calc(100% + 6px);
  top: 50%;
  transform: translateY(-50%);
  z-index: 5;
  min-width: 190px;
  padding: 6px;
  background: var(--surface);
  border: 1px solid var(--hairline-firm);
  border-radius: 12px;
  box-shadow: var(--shadow-lift);
}
.w-more-item {
  display: block;
  width: 100%;
  padding: 10px 12px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--ink);
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 600;
  text-align: left;
  white-space: nowrap;
  cursor: pointer;
}
.w-more-item:hover { background: var(--tint); }
.w-more-item.is-danger { color: var(--bad); }

/* Left visual anchor — the date block on a rota or session card */
.w-anchor {
  flex-shrink: 0;
  width: 46px;
  min-height: 46px;
  border-radius: 12px;
  background: var(--surface-sunk);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  line-height: 1.05;
  padding: 6px 4px;
}

.w-anchor-top {
  font-size: 10.5px;
  font-weight: 600;
  color: var(--ink-muted);
}

.w-anchor-main {
  font-size: 18px;
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.w-anchor.is-accent { background: var(--accent-wash); }
.w-anchor.is-accent .w-anchor-main { color: var(--accent-strong); }

/* =========================================================================
   FROSTED SURFACES — headers and anything sticky
   ========================================================================= */
.w-frost {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--frost-bg);
  backdrop-filter: var(--frost-blur);
  -webkit-backdrop-filter: var(--frost-blur);
  border-bottom: var(--frost-edge);
}

.w-frost.is-bottom {
  top: auto;
  bottom: 0;
  border-bottom: none;
  border-top: var(--frost-edge);
}

/* Safari/Firefox without backdrop-filter get an opaque surface instead of
   an unreadable translucent one. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .w-frost { background: var(--frost-solid); }
}

/* =========================================================================
   TABS, CHIPS & FILTERS
   ========================================================================= */
.w-tabs {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  padding: 6px 0;
}
.w-tabs::-webkit-scrollbar { display: none; }

.w-tab {
  flex: 0 0 auto;
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 600;
  padding: 8px 15px;
  border: none;
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--ink-muted);
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s ease, color 0.15s ease;
}

.w-tab:hover { background: var(--tint); color: var(--ink); }

.w-tab.is-active {
  background: var(--surface);
  color: var(--ink);
  font-weight: 700;
  box-shadow: var(--shadow-soft);
}

/* Filter pills (.w-tabs.w-filter): same look as the nav pills — only the
   SELECTED option wears the white pill with a shadow (the base .w-tab.is-active
   above); the rest read as plain text. Since Oct 2026 (erg insights PR 2) they
   sit on a light track, so a row reads as one control; it hugs its pills and
   still scrolls sideways when they do not fit. */
.w-tabs.w-filter {
  width: fit-content;
  max-width: 100%;
  gap: 4px;
  padding: 4px;
  border-radius: var(--r-pill);
  background: var(--tint);
}
/* A row that wraps instead (filterPills' { wrap: true }), for a narrow panel
   where a mouse can't scroll a row sideways: the Training Plan Creator's zone
   pills. Two lines on a pill-shaped track look wrong, so the track squares up. */
.w-tabs.w-filter.is-wrap { flex-wrap: wrap; overflow-x: visible; border-radius: var(--r-control); }
/* Smaller pills (filterPills' { compact: true }), for a row that holds several
   filters on one line: the Workout Library's Type, Phase and Zone. */
.w-tabs.w-filter.is-compact .w-tab { padding: 6px 9px; font-size: 13px; }

/* Filter trigger: compact pill with a caret */
.w-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-family: inherit;
  font-size: 13px;
  font-weight: 500;
  padding: 7px 12px;
  border: none;
  border-radius: var(--r-pill);
  background: var(--tint);
  color: var(--ink);
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s ease;
}

.w-chip:hover { background: var(--tint-firm); }
.w-chip:active { transform: scale(0.98); }

.w-chip.is-active {
  background: var(--accent-wash);
  color: var(--accent-strong);
  font-weight: 600;
}

.w-chip::after {
  content: "";
  width: 0;
  height: 0;
  border-left: 3.5px solid transparent;
  border-right: 3.5px solid transparent;
  border-top: 4px solid currentColor;
  opacity: 0.55;
}

.w-chip.no-caret::after { display: none; }

/* =========================================================================
   BUTTONS & FAB
   Buttons are deliberately NOT pill-shaped — pills mean navigation/filters;
   buttons get the squarer control radius and a consistent height.
   ========================================================================= */
.w-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  font-family: inherit;
  font-size: 13.5px;
  font-weight: 600;
  height: 38px;
  padding: 0 18px;
  border-radius: var(--r-control);
  /* Glass lift (Nov 2026): frosted glass with a sheen that fades in from the
     top edge, a slightly deeper foot and a soft shadow. No hard gloss line,
     and a filled button glows in its own colour rather than casting a dark
     shadow, so it never looks heavy on either theme. */
  --btn-fill: var(--btn-glass);
  border: 1px solid var(--btn-glass-edge);
  background-color: var(--btn-fill);
  background-image:
    linear-gradient(180deg, color-mix(in srgb, var(--btn-shine) var(--btn-sheen), transparent) 0%, transparent 60%),
    linear-gradient(180deg, transparent 40%, color-mix(in srgb, var(--btn-shade) 6%, transparent) 100%);
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--btn-shine) 22%, transparent),
    0 1px 1px color-mix(in srgb, var(--btn-shade) 8%, transparent),
    0 4px 12px -6px color-mix(in srgb, var(--btn-shade) 18%, transparent);
  -webkit-backdrop-filter: blur(12px) saturate(160%);
  backdrop-filter: blur(12px) saturate(160%);
  color: var(--ink);
  cursor: pointer;
  white-space: nowrap;
  transition: transform 0.12s ease, box-shadow 0.12s ease, filter 0.15s ease, background-color 0.15s ease;
}

.w-btn:hover:not(:disabled) { transform: translateY(-1px); filter: brightness(1.03); }
/* Club read-only (billing past_due / cancelled): grey out actions. Pragmatic
   v1 — dataService.readOnly is the real guard; this is the visual cue. */
[data-readonly] button.w-btn, [data-readonly] .w-btn { pointer-events: none; opacity: .55; }
/* Small, non-blocking club status banner above the top bar (trial / past due) */
.w-club-banner { padding: 6px 16px; font-size: 12px; font-weight: 600; text-align: center; color: var(--warn); background: var(--warn-wash); border-bottom: 1px solid var(--hairline-firm); }
.w-btn:active:not(:disabled) {
  transform: translateY(1px);
  filter: brightness(0.97);
  box-shadow: inset 0 1px 2px color-mix(in srgb, var(--btn-shade) 16%, transparent);
}

/* Tonal: the filled buttons are see-through glass too, tinted their colour,
   edged with a hairline of light and glowing in their own colour. */
.w-btn.is-primary,
.w-btn.is-danger {
  --btn-sheen: 20%;
  border-color: color-mix(in srgb, var(--btn-shine) 14%, transparent);
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--btn-shine) 22%, transparent),
    0 5px 14px -7px color-mix(in srgb, var(--btn-fill) 45%, transparent);
}
.w-btn.is-primary { --btn-fill: color-mix(in srgb, var(--primary) 88%, transparent); color: var(--primary-ink); }
.w-btn.is-danger { --btn-fill: color-mix(in srgb, var(--bad) 90%, transparent); color: var(--on-fill); }

.w-btn.is-quiet {
  --btn-fill: transparent;
  border-color: transparent;
  background-image: none;
  box-shadow: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  color: var(--ink-muted);
}
.w-btn.is-quiet:hover:not(:disabled) { --btn-fill: var(--tint); transform: none; filter: none; color: var(--ink); }

.w-btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
  filter: none;
}

/* Solid where glass cannot work or is not wanted */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .w-btn { --btn-glass: var(--surface); }
  .w-btn.is-primary { --btn-fill: var(--primary); }
  .w-btn.is-danger { --btn-fill: var(--bad); }
}
@media (prefers-reduced-transparency: reduce) {
  .w-btn { --btn-glass: var(--surface); -webkit-backdrop-filter: none; backdrop-filter: none; }
  .w-btn.is-primary { --btn-fill: var(--primary); }
  .w-btn.is-danger { --btn-fill: var(--bad); }
}

/* Compact button — toolbar actions that must stay one line high */
.w-btn.is-sm {
  height: 31px;
  padding: 0 13px;
  font-size: 12.5px;
  gap: 5px;
}

.w-fab {
  position: fixed;
  right: 18px;
  bottom: calc(18px + env(safe-area-inset-bottom, 0px));
  width: 54px;
  height: 54px;
  border-radius: var(--r-pill);
  border: none;
  background: var(--primary);
  color: var(--primary-ink);
  font-size: 26px;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: var(--shadow-lift);
  cursor: pointer;
  z-index: 60;
  transition: transform 0.12s ease, background 0.15s ease;
}

.w-fab:hover { background: var(--primary-hover); }
.w-fab:active { transform: scale(0.94); }

@media (prefers-reduced-motion: reduce) {
  .w-card, .w-btn, .w-chip, .w-fab, .w-tab, .w-fold-btn::before, .w-fold-btn::after { transition: none; }
  .w-card.is-tappable:active,
  .w-btn:active, .w-chip:active, .w-fab:active,
  .w-btn:hover:not(:disabled), .w-btn:active:not(:disabled) { transform: none; }
}

/* =========================================================================
   SESSIONS & WORKOUTS — Training (src/js/modules/training-ui.js)
   Water Session and Gym Session are the same two steps: a strip of session
   cards (date block, time, where, one status badge), then the workouts that
   can go in the selected session, each a choice row with a radio that opens
   to its brief. The Dashboard reuses the list rows and the meters.
   ========================================================================= */
.w-section-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 12px;
  margin: var(--sp-6) 0 var(--sp-3);
}
.w-section-head h2 { margin: 0; font-size: 17px; font-weight: 700; letter-spacing: -0.01em; color: var(--ink); }
.w-card > .w-section-head:first-child { margin-top: 0; }

.w-sess-strip {
  display: flex;
  gap: var(--sp-3);
  overflow-x: auto;
  scrollbar-width: none;
  padding: 2px 2px 6px;
}
.w-sess-strip::-webkit-scrollbar { display: none; }

.w-sess-card {
  flex: 0 0 auto;
  width: 212px;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--sp-2);
  text-align: left;
  font-family: inherit;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-card);
  box-shadow: var(--shadow-micro);
  padding: var(--sp-3);
  cursor: pointer;
}
.w-sess-card:hover { border-color: var(--hairline-firm); }
.w-sess-card.is-selected { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent), var(--shadow-micro); }
.w-sess-card.is-own { background: transparent; border: 1px dashed var(--hairline-firm); box-shadow: none; }
.w-sess-card.is-own.is-selected { border-style: solid; border-color: var(--accent); }
.w-sess-card .w-badge { align-self: flex-start; height: 27px; max-width: 100%; overflow: hidden; text-overflow: ellipsis; }

.w-choice {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-card);
  box-shadow: var(--shadow-micro);
}
.w-choice + .w-choice { margin-top: var(--sp-2); }
.w-choice.is-selected { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent), var(--shadow-micro); }
.w-choice-head { display: flex; align-items: center; gap: var(--sp-3); padding: var(--sp-3) var(--sp-4); cursor: pointer; }
.w-choice.w-fold .w-choice-head { padding-right: 52px; }   /* clear of the 48px corner fold */
.w-radio { flex: 0 0 auto; width: 20px; height: 20px; border-radius: 50%; border: 2px solid var(--ink-faint); background: var(--surface); box-sizing: border-box; }
.w-choice.is-selected .w-radio { border: 6px solid var(--accent); }
.w-choice-body {
  padding: 0 var(--sp-4) var(--sp-4) calc(var(--sp-4) + 20px + var(--sp-3));
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--ink);
}
.w-zone-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 5px; vertical-align: middle; }
.w-ivl { display: flex; flex-wrap: wrap; gap: 5px; }
.w-ivl span { font-size: 12px; font-weight: 600; background: var(--tint); border-radius: 8px; padding: 3px 9px; font-variant-numeric: tabular-nums; }
.w-sub { font-size: 11.5px; font-weight: 700; color: var(--ink-muted); }
.w-actions { display: flex; gap: var(--sp-2); flex-wrap: wrap; align-items: center; margin-top: var(--sp-4); }
/* A session card is one of a set of choices: its radio, beside the status
   badge so the title keeps the card's width, shows which is chosen */
.w-sess-foot { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); min-height: 27px; }
.w-sess-foot .w-badge { min-width: 0; }
.w-sess-card.is-selected .w-radio { border: 6px solid var(--accent); }

/* Gym Session's Workout Library picker: search, filters, the shape of a session */
.w-search { display: flex; align-items: center; gap: var(--sp-2); height: 44px; padding: 0 14px; background: var(--surface); border: 1px solid var(--hairline-firm); border-radius: var(--r-control); }
.w-search:focus-within { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent); }
.w-search svg { flex: 0 0 auto; color: var(--ink-muted); }
.w-search input { flex: 1; min-width: 0; border: 0; outline: none; background: transparent; font-family: inherit; font-size: 14px; color: var(--ink); }
.w-lib-bar { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-2); flex-wrap: wrap; margin: var(--sp-1) 0 var(--sp-3); }
.w-shape-wrap { display: flex; flex-direction: column; gap: 6px; }
.w-shape { display: flex; align-items: flex-end; gap: 2px; height: 30px; border-bottom: 1px solid var(--hairline-firm); }
.w-shape.is-dense { gap: 1px; }
.w-shape span { display: block; min-width: 1px; border-radius: 4px 4px 0 0; }
.w-shape .is-work { height: 28px; }
.w-shape .is-easy { height: 10px; background: var(--ink-faint); }
.w-shape .is-rest { height: 6px; background: var(--ink-faint); opacity: 0.45; }
.w-shape-legend { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 2px var(--sp-2); font-size: 11.5px; font-weight: 600; color: var(--ink-muted); }
.w-shape-legend span { white-space: nowrap; }
.w-shape-legend .is-main { color: var(--ink); }

.w-dash-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr)); gap: var(--sp-4); align-items: start; }
.w-dash-grid > div { display: flex; flex-direction: column; gap: var(--sp-4); }
.w-dash-grid .w-card + .w-card { margin-top: 0; }
.w-list-row { display: flex; align-items: center; gap: var(--sp-3); padding: 10px 0; border-top: 1px solid var(--hairline-firm); }
.w-list-row:first-child { border-top: 0; padding-top: 2px; }
.w-meter { display: flex; flex-direction: column; gap: 6px; }
.w-meter + .w-meter { margin-top: var(--sp-4); }
.w-meter-top { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; font-size: 13.5px; color: var(--ink); }
.w-meter-top b { font-size: 15px; font-variant-numeric: tabular-nums; }
.w-meter-track { position: relative; height: 6px; border-radius: 6px; background: var(--surface-sunk); }
.w-meter-target { position: absolute; top: -4px; bottom: -4px; width: 2px; margin-left: -1px; border-radius: 2px; background: var(--accent); }

/* The Register (PR C): who did what, week by week — Session Results, captains and admins */
.w-reg-wrap { display: inline-block; max-width: 100%; vertical-align: top; overflow-x: auto; background: var(--surface); border: 1px solid var(--hairline); border-radius: var(--r-card); box-shadow: var(--shadow-micro); }
.w-reg { border-collapse: collapse; font-size: 13px; font-variant-numeric: tabular-nums; }
.w-reg th, .w-reg td { padding: 8px 10px; text-align: center; border-bottom: 1px solid var(--hairline-firm); white-space: nowrap; }
.w-reg th { font-size: 11px; font-weight: 600; color: var(--ink-muted); vertical-align: bottom; line-height: 1.3; }
.w-reg th:first-child, .w-reg td:first-child { text-align: left; font-weight: 600; color: var(--ink); position: sticky; left: 0; background: var(--surface); }
.w-reg tfoot td { font-weight: 700; border-bottom: 0; }
.w-reg-c { display: inline-grid; vertical-align: middle; place-items: center; width: 24px; height: 24px; border-radius: 7px; font-size: 12px; font-weight: 700; color: var(--ink-faint); }
.w-reg-c.is-done { background: var(--good-wash); color: var(--good); }
.w-reg-c.is-missed { background: var(--warn-wash); color: var(--warn); }
.w-reg-c.is-up { border: 1.5px dashed var(--ink-faint); color: var(--ink-muted); }
.w-heat { display: inline-grid; place-items: center; min-width: 38px; height: 26px; border-radius: 6px; font-size: 11px; font-weight: 700; color: var(--ink); background: var(--surface-sunk); }
.w-heat.l1 { background: var(--heat-1); } .w-heat.l2 { background: var(--heat-2); } .w-heat.l3 { background: var(--heat-3); color: var(--on-fill); } .w-heat.l4 { background: var(--heat-4); color: var(--on-fill); }
.w-heat.is-before { background: repeating-linear-gradient(45deg, var(--surface-sunk) 0 4px, transparent 4px 8px); color: var(--ink-faint); }
.w-heat.is-empty { background: transparent; color: var(--ink-faint); }
.w-meter-fill { height: 100%; border-radius: 6px; background: var(--primary); }

@media (max-width: 600px) {
  .w-choice-body { padding-left: var(--sp-4); }
  .w-sess-card { width: 184px; }
  .w-actions .w-btn { flex: 1 1 auto; }
}

/* An erg row's intervals and its whole-row tiles (Gym Session, erg-log-card.js).
   Classes, not inline grids: the phone rule folds inline grids to one column,
   and a split table must keep its columns on a phone. */
.w-ergtab-row {
  display: grid;
  grid-template-columns: 22px repeat(3, minmax(0, 1fr));
  gap: 6px;
  align-items: center;
  padding: 9px 2px;
  border-top: 1px solid var(--hairline-firm);
  font-size: 14px;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.w-ergtab-row.is-head { padding: 0 2px 6px; border-top: 0; font-size: 11px; font-weight: 700; color: var(--ink-muted); }
.w-ergtab-row.has-hr { grid-template-columns: 22px repeat(4, minmax(0, 1fr)); }
.w-ergtab-row > :not(:first-child) { text-align: right; }
.w-ergtiles { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }

/* =========================================================================
   PAGE-ENTRY ANIMATION
   main.js puts .stage-enter on #module-stage on NAVIGATION only (then removes
   it), so each page's top-level components rise in with a subtle stagger —
   data re-renders mid-task never replay it.
   ========================================================================= */
@keyframes w-rise {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}

.stage-enter .animate-fade-in > * {
  animation: w-rise 0.38s cubic-bezier(0.2, 0.7, 0.3, 1) both;
}
.stage-enter .animate-fade-in > *:nth-child(2) { animation-delay: 0.05s; }
.stage-enter .animate-fade-in > *:nth-child(3) { animation-delay: 0.10s; }
.stage-enter .animate-fade-in > *:nth-child(4) { animation-delay: 0.15s; }
.stage-enter .animate-fade-in > *:nth-child(5) { animation-delay: 0.20s; }
.stage-enter .animate-fade-in > *:nth-child(n+6) { animation-delay: 0.24s; }

@media (prefers-reduced-motion: reduce) {
  .stage-enter .animate-fade-in > * { animation: none; }
}

/* =========================================================================
   ATOMIC UTILITIES (used throughout the existing inline markup — keep)
   ========================================================================= */
.fx { display: flex; }
.col { display: flex; flex-direction: column; }
.grid { display: grid; }
.ac { align-items: center; }
.jc { justify-content: center; }
.jb { justify-content: space-between; }
.f1 { flex: 1; }
.noshrink { flex-shrink: 0; }
.wrap { flex-wrap: wrap; }
.fw5 { font-weight: 500; }
.fw6 { font-weight: 600; }
.fw7 { font-weight: 700; }
.fw8 { font-weight: 800; }
.fs11 { font-size: 11px; }
.fs12 { font-size: 12px; }
.fs13 { font-size: 13px; }
.fs14 { font-size: 14px; }
.fs15 { font-size: 15px; }
.fs16 { font-size: 16px; }
.fs20 { font-size: 20px; }
.fs22 { font-size: 22px; }
.tc { text-align: center; }
.nowrap { white-space: nowrap; }
.gap8 { gap: 8px; }
.gap10 { gap: 10px; }
.gap12 { gap: 12px; }
.gap16 { gap: 16px; }
.gap24 { gap: 24px; }
.m0 { margin: 0; }
.mt8 { margin-top: 8px; }
.mt12 { margin-top: 12px; }
.mt16 { margin-top: 16px; }
.mb8 { margin-bottom: 8px; }
.mb12 { margin-bottom: 12px; }
.mb16 { margin-bottom: 16px; }
.posrel { position: relative; }
.posabs { position: absolute; }
.round { border-radius: 50%; }
.ohide { overflow: hidden; }
.bbox { box-sizing: border-box; }
.pointer { cursor: pointer; }
.w100 { width: 100%; }
.b0 { border: none; }

/* =========================================================================
   SCREENS (mobile content fallbacks for unmigrated modules)
   ========================================================================= */
/* Admin → Members: table on desktop, cards on mobile (the 7-column table
   cannot fit a phone and its actions column was clipped off-screen). */
.w-members-cards { display: none; }

@media (max-width: 880px) {
  body { overflow-x: hidden; }
  .w-members-tablewrap { display: none; }
  .w-members-cards { display: flex; flex-direction: column; gap: 10px; }
  /* Inputs under 16px make mobile browsers auto-zoom on focus — the page then
     stays zoomed, which broke fixed-position overlays like the intro tour. */
  input, select, textarea { font-size: 16px !important; }
  [style*="grid-template-columns"]:not([style*="auto-fill"]):not([style*="auto-fit"]) { grid-template-columns: 1fr !important; }
  [style*="width: 530px"], [style*="width:530px"] { width: 100% !important; }
  h1:not(.w-page-title) { font-size: 22px !important; }
  .w-title { font-size: 24px; }

  /* Page-header rows: keep the title on its own line and stack whatever sat
     beside it (date pickers, filters, actions) underneath. */
  div[style*="space-between"]:has(> div > .w-page-title),
  div[style*="space-between"]:has(> .w-page-title) { display: block !important; }
  div[style*="space-between"]:has(> div > .w-page-title) > * + *,
  div[style*="space-between"]:has(> .w-page-title) > * + * { margin-top: 12px; flex-wrap: wrap; row-gap: 8px; }
  div[style*="space-between"]:has(.w-page-title) select { max-width: 100%; }
  /* Pill rows never wrap — they stay on one line and scroll sideways, even
     when the header-stacking rule above tries to set flex-wrap on them. */
  .w-tabs { flex-wrap: nowrap !important; max-width: 100%; min-width: 0; }
  .w-tabs.is-wrap { flex-wrap: wrap !important; }   /* …except a row made to wrap */

  /* Create Rota → Session Boards: Publish goes underneath the title */
  .w-boards-head { display: block !important; }
  .w-boards-head .w-btn { margin-top: 10px; width: 100%; }

  /* V9: boat cards on a phone — at most TWO side by side, then wrap.
     The gym (indoor) card always takes the full width. */
  .w-boat-row { flex-wrap: wrap; }
  .w-boat-card { flex: 1 1 calc(50% - 5px) !important; max-width: calc(50% - 5px); }
  .w-boat-card.is-gym { flex-basis: 100% !important; max-width: 100%; }
}

@media print {
  body * { visibility: hidden !important; }
  #coxcard-print, #coxcard-print * { visibility: visible !important; -webkit-print-color-adjust: exact !important; print-color-adjust: exact !important; }
  #coxcard-print { position: fixed !important; left: 0 !important; top: 0 !important; right: 0 !important; margin: 0 auto !important; width: 100% !important; max-width: 700px !important; box-shadow: none !important; }
  @page { margin: 12mm; }
}

/* =========================================================================
   APP SHELL — ink sidebar on desktop, floating ink dock on mobile.
   Both render from the same nav config in main.js; only the shell forks
   at 880px.
   ========================================================================= */
.app-shell {
  display: flex;
  min-height: 100vh;
  background: var(--canvas);
}

/* --- Ink sidebar (desktop) --- */
.w-sidebar {
  position: fixed;
  left: 0;
  top: 0;
  bottom: 0;
  width: 232px;
  background: var(--chrome);
  border-right: 1px solid var(--chrome-line);
  z-index: 100;
  display: flex;
  flex-direction: column;
  overflow-y: auto;
}

.w-sidebar-brand {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 22px 20px 18px;
}
.w-sidebar-brand .name { font-size: 16px; font-weight: 700; color: var(--chrome-ink); line-height: 1.2; }
.w-sidebar-brand .sub  { font-size: 10px; font-weight: 600; color: var(--chrome-faint); }

.w-sidebar-nav { padding: 6px 12px 0; }

.w-nav-item {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 11px 14px;
  margin-bottom: 2px;
  border: none;
  border-radius: 12px;
  background: transparent;
  color: var(--chrome-muted);
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
.w-nav-item:hover { background: var(--chrome-hover); color: var(--chrome-ink); }
.w-nav-item.is-active { background: var(--chrome-active); color: var(--chrome-ink); font-weight: 700; }
.w-nav-ic { width: 20px; height: 20px; flex-shrink: 0; }
.w-nav-item.is-active .w-nav-ic { color: var(--chrome-accent); }

/* Help & guides pinned to the bottom of the sidebar, above the build line */
.w-sidebar-help {
  margin-top: auto;
  padding: 6px 12px 0;
  border-top: 1px solid var(--chrome-line);
}
.w-sidebar-help .w-nav-item { text-decoration: none; }

.w-sidebar-foot {
  padding: 12px 20px 18px;
  font-size: 11.5px;
  color: var(--chrome-faint);
  line-height: 1.4;
}

/* Copyright line: sidebar foot (desktop), More sheet (mobile), sign-in card */
.w-copyright {
  font-size: 11px;
  line-height: 1.4;
  color: var(--ink-muted);
}
.w-sidebar-foot .w-copyright { margin-top: 4px; color: inherit; }
.w-sheet .w-copyright { text-align: center; padding-top: 12px; }

/* --- Stage & top bar --- */
.w-stage {
  margin-left: 232px;
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  min-width: 0;
}

/* Two rows, always: the account row (bell, season, name, avatar) sits ABOVE
   the subtab pills, so the pills can take the full width and the bell and
   avatar never fight them for space on a phone. */
.w-topbar {
  position: sticky;
  top: 0;
  z-index: 50;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 2px 12px;
  padding: 8px 28px 6px;
  background: var(--frost-canvas);
  backdrop-filter: var(--frost-blur);
  -webkit-backdrop-filter: var(--frost-blur);
}
.w-topbar .w-tabs { flex: 1 1 100%; min-width: 0; }
/* The subtab bar sits on a light track, as the filter rows do (Oct 2026): it
   hugs its pills and still scrolls sideways when they do not fit. On the
   track the hover is a shade firmer, or it would not show. */
.w-topbar #subtab-bar {
  flex: 0 1 auto;
  width: fit-content;
  max-width: 100%;
  gap: 4px;
  padding: 4px;
  border-radius: var(--r-pill);
  background: var(--tint);
}
#subtab-bar .w-tab:not(.is-active):hover { background: var(--tint-firm); }

.w-topbar-right {
  order: -1;
  flex: 1 1 100%;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 14px;
}

.w-userblock { position: relative; display: flex; align-items: center; gap: 10px; }
.w-usermeta { text-align: right; line-height: 1.2; }
.w-usermeta .who { font-size: 13px; font-weight: 700; color: var(--ink); }
.w-roleselect {
  font-size: 11px;
  color: var(--ink-muted);
  border: none;
  background: transparent;
  cursor: pointer;
  font-family: inherit;
  padding: 0;
}
/* --- Account menu: the avatar is a button, and drops a small menu under it
   (name, My profile, Sign out). It is positioned against .w-userblock; the
   top bar's backdrop-filter makes that a containing block, so a fixed-position
   backdrop would be sized to the bar — outside clicks are closed in JS
   instead (see appActions.toggleUserMenu in main.js). --- */
.w-avatar-btn {
  padding: 0;
  border: none;
  background: none;
  border-radius: var(--r-pill);
  cursor: pointer;
  display: inline-flex;
  flex-shrink: 0;
}
.w-avatar-btn:hover .w-avatar { box-shadow: 0 0 0 3px var(--tint-firm); }
.w-usermenu {
  position: absolute;
  top: calc(100% + 10px);
  right: 0;
  z-index: 60;
  width: 268px;
  max-width: calc(100vw - 24px);
  padding: 6px;
  background: var(--surface-raised);
  border: 1px solid var(--hairline-firm);
  border-radius: 14px;
  box-shadow: var(--shadow-lift);
  animation: usermenu-in 0.12s ease-out;
}
@keyframes usermenu-in {
  from { opacity: 0; transform: translateY(-4px); }
  to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) { .w-usermenu { animation: none; } }
.w-usermenu-head {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 10px 12px;
  margin-bottom: 6px;
  border-bottom: 1px solid var(--hairline);
}
.w-usermenu-avatar { width: 40px; height: 40px; font-size: 14px; }
.w-usermenu-id { min-width: 0; }
/* A long name or a club email address gets an ellipsis rather than breaking
   mid-word; the full text is on the element's title. */
.w-usermenu-name,
.w-usermenu-sub,
.w-usermenu-club { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.w-usermenu-name { font-size: 14px; font-weight: 700; color: var(--ink); line-height: 1.25; }
.w-usermenu-sub { margin-top: 2px; font-size: 11.5px; color: var(--ink-muted); }
/* The club, under the email: ink rather than muted so it is not read as a
   second address. */
.w-usermenu-club { margin-top: 2px; font-size: 11.5px; font-weight: 600; color: var(--ink); }
.w-usermenu-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 10px;
  border: none;
  border-radius: 10px;
  background: transparent;
  color: var(--ink);
  font-family: inherit;
  font-size: 14px;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}
.w-usermenu-item:hover { background: var(--tint); }
.w-usermenu-item .w-nav-ic { display: inline-flex; color: var(--ink-muted); }

/* Appearance, in the account menu: a Theme segmented control and two Style
   tiles with a little preview of each look. */
.w-appear { padding: 2px 6px 10px; margin-bottom: 6px; border-bottom: 1px solid var(--hairline); }
.w-appear-label { margin: 2px 4px 6px; font-size: 11px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-muted); }
.w-appear-sub { display: flex; justify-content: space-between; align-items: baseline; gap: 8px; margin: 10px 4px 6px; font-size: 12.5px; font-weight: 700; color: var(--ink); }
.w-appear-sub small { font-size: 11px; font-weight: 600; color: var(--ink-muted); }
.w-seg { display: flex; padding: 3px; border-radius: var(--r-pill); background: var(--tint-firm); }
.w-seg-btn {
  flex: 1;
  padding: 6px 0;
  border: 0;
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--ink-muted);
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
.w-seg-btn[aria-checked="true"] { background: var(--control-on); color: var(--ink); box-shadow: var(--shadow-soft); }
.w-appear-tiles { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.w-appear-tile {
  padding: 6px;
  border: 1.5px solid var(--hairline-firm);
  border-radius: 12px;
  background: transparent;
  color: var(--ink);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
}
.w-appear-tile[aria-checked="true"] { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-wash); }
.w-appear-tile b { display: block; margin-top: 5px; font-size: 12.5px; }
.w-appear-tile-sub { display: block; font-size: 11px; font-weight: 600; color: var(--ink-muted); }
.w-appear-pv { position: relative; display: block; height: 40px; overflow: hidden; border: 1px solid var(--hairline); border-radius: 8px; background: var(--canvas); }
.w-appear-pv.is-ocean { background: var(--ocean-backdrop); }
.w-appear-pv i { position: absolute; left: 6px; right: 6px; height: 11px; border: 1px solid var(--hairline); border-radius: 4px; background: var(--surface); }
.w-appear-pv i:first-child { top: 6px; }
.w-appear-pv i:last-child { top: 21px; right: 18px; }
.w-appear-pv.is-ocean i:first-child { background: var(--glass-bg); border-color: var(--glass-edge); }
.w-appear-note { margin-top: 8px; font-size: 11px; color: var(--ink-muted); text-align: center; }
.w-avatar {
  position: relative;
  overflow: hidden;
  flex-shrink: 0;
  width: 36px;
  height: 36px;
  border-radius: var(--r-pill);
  /* Same identity gradient as the Rower Profile avatar */
  background: var(--brand-gradient);
  color: var(--on-brand);
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  letter-spacing: .02em;
  flex-shrink: 0;
}

.w-main {
  padding: 26px 32px;
  flex: 1;
}

/* The GPS map (live-tracking.js, Leaflet + OpenStreetMap tiles): in dark mode
   the light tiles are inverted and toned down rather than fetched from a
   second tile host. The track and markers are drawn over them in tokens. */
:root[data-theme="dark"] .leaflet-tile-pane { filter: invert(1) hue-rotate(180deg) brightness(.85) contrast(.9) saturate(.6); }

/* --- Floating ink dock (mobile) --- */
.w-dock {
  position: fixed;
  left: 14px;
  right: 14px;
  bottom: calc(12px + env(safe-area-inset-bottom, 0px));
  z-index: 90;
  display: none;
  justify-content: space-around;
  padding: 9px 6px;
  background: var(--chrome);
  border-radius: var(--r-pill);
  box-shadow: var(--shadow-float);
}

.w-dock-item {
  border: none;
  background: transparent;
  text-align: center;
  font-family: inherit;
  font-size: 10.5px;
  font-weight: 600;
  color: var(--chrome-muted);
  cursor: pointer;
  line-height: 1.2;
  padding: 2px 8px;
}
.w-dock-item svg { display: block; margin: 0 auto 2px; width: 19px; height: 19px; }
.w-dock-item.is-active { color: var(--chrome-ink); font-weight: 700; }
.w-dock-item.is-active svg { color: var(--chrome-accent); }

/* --- "More" sheet (mobile) --- */
.w-sheet-backdrop {
  position: fixed;
  inset: 0;
  z-index: 95;
  background: var(--scrim);
}

.w-sheet {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 96;
  background: var(--surface);
  border-radius: 20px 20px 0 0;
  padding: 10px 16px calc(20px + env(safe-area-inset-bottom, 0px));
  box-shadow: var(--shadow-lift);
  /* grouped nav + plan card can outgrow a short phone: scroll inside the sheet */
  max-height: calc(100dvh - 24px);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.w-sheet-grab {
  width: 36px;
  height: 4px;
  border-radius: 2px;
  background: var(--hairline-firm);
  margin: 0 auto 12px;
}

/* The club's name heads the sheet, above the plan card (every role). A long
   name wraps rather than clipping: this is the only place on a phone it is
   shown in full. */
.w-sheet-club {
  padding: 0 14px 10px;
  font-size: 17px;
  font-weight: 700;
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: var(--ink);
  overflow-wrap: anywhere;
}

.w-sheet-item {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 12px 14px;
  border: none;
  border-radius: 12px;
  background: transparent;
  color: var(--ink);
  font-family: inherit;
  font-size: 14.5px;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}
.w-sheet-item:active { background: var(--tint); }
.w-sheet-item.is-active { background: var(--accent-wash); color: var(--accent-strong); }
.w-sheet-item .w-nav-ic { color: var(--ink-muted); }
.w-sheet-item.is-active .w-nav-ic { color: var(--accent-strong); }

/* --- Withe Club / Withe Racing / Admin section labels: sidebar (ink) and More sheet (white) --- */
.w-nav-group {
  font-size: 11.5px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  padding: 14px 14px 5px;
}
/* A faint rule above every section but the first, inset to line up with the
   text. "Has an earlier label" rather than :first-child: in the More sheet
   the list follows the club name and plan card. */
.w-nav-group ~ .w-nav-group {
  margin: 8px 14px 0;
  padding-left: 0;
  padding-right: 0;
  border-top: 1px solid var(--hairline-firm);
}
.w-sidebar .w-nav-group { color: var(--nav-group-on-ink); }
.w-sidebar .w-nav-group.is-racing { color: var(--racing-on-ink); }
.w-sidebar .w-nav-group ~ .w-nav-group { border-top-color: var(--chrome-line); }
.w-sheet .w-nav-group { color: var(--nav-group); padding-top: 10px; padding-bottom: 4px; }
.w-sheet .w-nav-group.is-racing { color: var(--racing); }
.w-nav-group-note { font-size: 11px; font-weight: 500; opacity: 0.75; text-transform: none; letter-spacing: 0; }

/* "1 regatta included" under Regatta Hosting while Withe Club's hosted regatta is unused */
.w-nav-stack { display: flex; flex-direction: column; line-height: 1.2; }
.w-nav-sub { margin-top: 2px; font-size: 11px; font-weight: 600; }
.w-sidebar .w-nav-sub { color: var(--chrome-good); }
.w-sheet .w-nav-sub { color: var(--good); }

/* Upgrade to Racing — last row of the Racing group, captains and admins only */
.w-nav-upgrade {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  width: 100%;
  margin: 4px 0 2px;
  padding: 10px 14px;
  border-radius: 12px;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.w-nav-upgrade-t { display: block; font-size: 14px; font-weight: 700; }
.w-nav-upgrade-s { display: block; font-size: 11.5px; font-weight: 500; line-height: 1.35; margin-top: 2px; }
.w-sidebar .w-nav-upgrade { background: rgba(226, 154, 107, 0.13); border: 1px solid rgba(226, 154, 107, 0.34); color: var(--chrome-ink); } /* colour-ok: copper on the chrome, both themes */
.w-sidebar .w-nav-upgrade:hover,
.w-sidebar .w-nav-upgrade.is-active { background: rgba(226, 154, 107, 0.22); } /* colour-ok */
.w-sidebar .w-nav-upgrade .w-nav-ic { color: var(--racing-on-ink); }
.w-sidebar .w-nav-upgrade-s { color: var(--chrome-faint); }
.w-sheet .w-nav-upgrade { background: var(--racing-wash); border: 1px solid var(--racing-line); color: var(--ink); }
.w-sheet .w-nav-upgrade .w-nav-ic { color: var(--racing); }
.w-sheet .w-nav-upgrade-s { color: var(--ink-muted); }
.w-nav-upgrade:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Your plan — top of the sidebar and the More sheet, captains and admins only */
.w-plan {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 9px 12px;
  border-radius: 12px;
}
.w-sidebar .w-plan { margin: 0 12px 2px; background: var(--chrome-hover); border: 1px solid var(--chrome-line); }
.w-sheet .w-plan { margin: 0 0 4px; background: var(--canvas); }
.w-plan-l { font-size: 11px; font-weight: 600; }
.w-plan-v { display: flex; align-items: center; gap: 6px; margin-top: 1px; font-size: 13px; font-weight: 700; }
.w-plan-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--good-bright); flex-shrink: 0; }
.w-sidebar .w-plan-l { color: var(--chrome-faint); }
.w-sidebar .w-plan-v { color: var(--chrome-ink); }
.w-sheet .w-plan-l { color: var(--ink-muted); }
.w-sheet .w-plan-v { color: var(--ink); }
.w-plan-manage {
  flex-shrink: 0;
  border: none;
  background: transparent;
  padding: 4px 2px;
  font-family: inherit;
  font-size: 11.5px;
  font-weight: 600;
  cursor: pointer;
}
.w-sidebar .w-plan-manage { color: var(--chrome-muted); }
.w-sheet .w-plan-manage { color: var(--ink-muted); }

/* Events → Event Timetable: the hosting card at the foot of the page */
.w-host-card {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-top: 24px;
  padding: 14px 16px;
  border: 1px dashed var(--racing-line);
  border-radius: var(--r-card);
  background: var(--racing-wash);
}
.w-host-card-ic { display: flex; color: var(--racing); flex-shrink: 0; }
.w-host-card-text { min-width: 0; }
.w-host-card-t { font-size: 14px; font-weight: 700; color: var(--ink); }
.w-host-card-s { margin-top: 2px; font-size: 12.5px; line-height: 1.45; color: var(--ink-muted); }
.w-host-card .w-btn { margin-left: auto; flex-shrink: 0; }
@media (max-width: 600px) {
  .w-host-card { flex-wrap: wrap; }
  .w-host-card .w-btn { margin-left: 36px; }
}

/* Withe Racing page — what Upgrade to Racing opens, and the gated-page fallback */
.w-racing-offer { padding: 22px 24px; }
.w-racing-title { margin: 0 0 8px; font-size: 20px; font-weight: 700; color: var(--ink); }
.w-racing-lede { margin: 0 0 8px; font-size: 14px; line-height: 1.5; color: var(--ink-muted); }
.w-racing-list { margin: 0 0 18px; padding-left: 20px; font-size: 14px; line-height: 1.7; color: var(--ink); }
.w-racing-buy { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.w-racing-buy .w-btn { text-decoration: none; }
.w-racing-buy span { font-size: 13px; color: var(--ink-muted); }
.w-racing-note { margin: 12px 0 0; font-size: 12.5px; line-height: 1.5; color: var(--ink-muted); }
.w-racing-note.is-box { margin-top: 4px; padding: 10px 12px; border-radius: var(--r-control); background: var(--canvas); }

/* Bottom row of the More sheet: Sign out on the left, round help icon on the right */
.w-sheet-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.w-sheet-foot .w-sheet-item { flex: 1; width: auto; }
.w-sheet-help {
  flex-shrink: 0;
  width: 44px;
  height: 44px;
  margin-right: 6px;
  border-radius: var(--r-pill);
  background: var(--tint);
  color: var(--ink);
  display: flex;
  align-items: center;
  justify-content: center;
  text-decoration: none;
}
.w-sheet-help svg { width: 22px; height: 22px; }
.w-sheet-help:active { background: var(--tint-firm); }
.w-sheet-icons { display: flex; align-items: center; gap: 8px; }
button.w-sheet-help { border: 0; padding: 0; font: inherit; cursor: pointer; }

/* Bug button (src/js/bug-report.js): beside Help in the sidebar and the More
   sheet, shown only while the portal's switch is on — bug-report.js sets
   html[data-bug-reports="on"], so turning it on or off needs no re-render. */
html:not([data-bug-reports="on"]) .w-bug-btn { display: none; }

.w-sheet-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 14px;
  border-top: 1px solid var(--hairline);
  margin-top: 8px;
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-muted);
}

/* --- Page headers --- */
/* Training: the crew / squad pills sit on one row and the drop-down on the row
   below them on a phone (#180). */
@media (max-width: 880px) {
  .w-tr-ctl { flex: 1 1 100%; }
  .w-tr-picker { flex: 1 1 100%; width: 100%; }
}
.w-page-eyebrow {
  font-size: 12px;
  font-weight: 600;
  color: var(--ink-muted);
  margin-bottom: 2px;
}

.w-page-title {
  margin: 0;
  font-size: 28px;
  font-weight: 700;
  letter-spacing: -0.02em;
  line-height: 1.15;
  color: var(--ink);
  text-wrap: balance;
}

/* --- Active-indicator glide ---
   The shell re-renders on navigation, so the active pill can't CSS-transition;
   instead the View Transitions API morphs it between renders. Only the named
   indicators animate — the root crossfade is disabled so content still
   switches instantly. */
.w-tab.is-active      { view-transition-name: active-pill; }
.w-nav-item.is-active { view-transition-name: active-nav; }
.w-dock-item.is-active { view-transition-name: active-dock; }
/* In-page filter rows reuse the .w-tab look; their active pill must NOT share
   the nav's `active-pill` name (duplicate names abort every view transition on
   the page). Each filter group instead gets its OWN unique name inline (set by
   filterPills() in src/js/pills.js) so its black pill glides too; this rule is
   the fallback for any filter row without one. */
.w-tabs.w-filter .w-tab.is-active {
  view-transition-name: none;
  view-transition-class: w-filter-pill;
}

::view-transition-old(root),
::view-transition-new(root) { animation: none; }

/* The sticky top bar and floating dock are lifted as their own layers and
   stacked ABOVE any content layer (e.g. a gliding filter pill), so page
   content can never paint over the navigation during a transition. Their
   own active pills sit higher still. */
.w-topbar { view-transition-name: w-topbar; }
.w-dock   { view-transition-name: w-dock; }
::view-transition-group(w-topbar),
::view-transition-group(w-dock) { z-index: 60; }
/* A lifted layer can't re-run backdrop-filter, and the top bar's frost is
   translucent — without an opaque backing the page title shows through it
   during the transition (the mobile "title over the nav pills" glitch). */
::view-transition-group(w-topbar) { background: var(--canvas); }
::view-transition-group(active-pill),
::view-transition-group(active-nav),
::view-transition-group(active-dock) { z-index: 70; }

/* Filter-change transitions (html.vt-chrome-static, set by rerenderT): the
   chrome doesn't move, so its pills are left OUT of the overlay — a lifted
   layer ignores its row's overflow clipping, letting a clipped nav pill
   momentarily paint over the avatar. The filter pills' own inline
   view-transition-name overrides these, so they still glide. */
html.vt-chrome-static .w-tab.is-active { view-transition-name: none; }
html.vt-chrome-static .w-nav-item.is-active { view-transition-name: none; }
html.vt-chrome-static .w-dock-item.is-active { view-transition-name: none; }

::view-transition-group(active-pill),
::view-transition-group(active-nav),
::view-transition-group(active-dock) {
  animation-duration: 0.28s;
  animation-timing-function: cubic-bezier(0.3, 0, 0.2, 1);
}

/* Separate rule: browsers without view-transition-class support would drop a
   combined selector list entirely, killing the nav timings above. */
::view-transition-group(.w-filter-pill) {
  animation-duration: 0.28s;
  animation-timing-function: cubic-bezier(0.3, 0, 0.2, 1);
}

/* The glide while view transitions are off (src/js/pill-glide.js): a copy of
   the active pill's background slides under the row's labels; the new pill's
   own background waits until it lands. The ghost is stacked beneath every
   label explicitly: relying on paint order alone failed on iOS Safari, where
   the animating ghost was composited ABOVE some labels and hid the text of
   the pill it landed on (Profile & Preferences / Training) until it finished. */
.w-glide-row { position: relative; isolation: isolate; }
.w-glide-row > :not(.w-glide) { position: relative; z-index: 1; }
.w-glide { position: absolute; z-index: 0; pointer-events: none; }
.w-glide-to { background: transparent !important; box-shadow: none !important; transition: none !important; }

/* Subtab labels: full name on desktop, shortLabel on mobile */
.w-lbl-short { display: none; }

/* --- The fork --- */
@media (max-width: 880px) {
  .w-sidebar { display: none; }
  .w-lbl-full { display: none; }
  .w-lbl-short { display: inline; }

  /* The BODY never scrolls on mobile — the stage is the scroller. If the body
     scrolls, iOS resizes the layout viewport as its toolbars collapse and the
     fixed dock rides down with it, ending up partly off-screen. With an inner
     scroller the viewport never moves, so the dock is always fully visible. */
  html, body { height: 100%; overflow: hidden; }
  .app-shell { height: 100vh; height: 100dvh; min-height: 0; }
  .w-stage {
    min-height: 0;
    height: 100%;
    overflow-y: auto;
    overflow-x: hidden;
    -webkit-overflow-scrolling: touch;
  }
  /* While a field has focus: room below the content, so the field can be lifted
     above the keyboard even at the end of a page (viewport.js, the keyboard). */
  .w-stage.is-typing::after { content: ''; flex: none; height: 55vh; }
  /* The same for the sign-in screens, which scroll themselves (.w-gate). */
  .w-gate.is-typing::after { content: ''; display: block; height: 55vh; }

  /* Mobile page titles run BIGGER than desktop, Spond-style (see mockups) */
  .w-page-title { font-size: 34px; }

  /* Context-aware subtabs: arriving via a dock item narrows My Profile's
     pill bar to that context (desktop always shows all three). */
  /* Availability tab: the three availability pages only; Profile tab: profile, training, notifications */
  .dock-ctx-availability .w-tab[data-tab="myprofile"],
  .dock-ctx-availability .w-tab[data-tab="my-training"],
  .dock-ctx-availability .w-tab[data-tab="notifications"] { display: none; }
  .dock-ctx-profile .w-tab[data-tab="availability"],
  .dock-ctx-profile .w-tab[data-tab="gym-availability"],
  .dock-ctx-profile .w-tab[data-tab="race-availability"] { display: none; }
  .w-stage { margin-left: 0; }
  .w-topbar { padding: 10px 14px; }
  /* The status strips (trial, support view, offline, back online, a new
     version) go BELOW the top bar on a phone. First in the stage, they sat
     under the frosted edge an installed iPhone app draws beneath the status
     bar, and read as a blur (Oct 2026). The top bar has always sat there and
     reads fine. The stage is a column, so this is order alone. */
  .w-stage > .w-topbar { order: -1; }
  /* The fade reaches a little past the status bar, enough to soften the bell
     and the signal waves at 10px (Tom, Oct 2026). Room for it, in the
     installed app on iOS only: -webkit-touch-callout is iOS Safari's alone, so
     Android, a computer and a browser tab keep the bar as it was. */
  @supports (-webkit-touch-callout: none) {
    @media (display-mode: standalone) { .w-topbar { padding-top: 30px; } }
  }
  .w-topbar .w-season { display: none; }
  .w-usermeta { display: none; }
  .w-dock { display: flex; }
  .w-main { padding: 18px 13px calc(84px + env(safe-area-inset-bottom, 0px)); }
  .w-fab { bottom: calc(84px + env(safe-area-inset-bottom, 0px)); }
}

/* =========================================================================
   DIALOGS — branded replacements for alert / confirm / prompt (src/js/dialog.js)
   ========================================================================= */
.w-dialog-overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  /* NO backdrop-filter here, deliberately (17 Sep 2026). This scrim is the
     only full-screen -webkit-backdrop-filter in the app that is CREATED AND
     DESTROYED on every confirm, and tearing that compositing layer down is
     what leaves iOS WebKit hit-testing a stale layer tree: the page still
     paints, every tap lands on <html>, and only a reload recovers it. The
     blur was decoration; a slightly deeper scrim reads the same. The frost on
     .w-topbar / .w-frost keeps its blur — those layers live for the life of
     the page and are not implicated. */
  background: var(--scrim-firm);
  animation: w-dialog-fade 0.16s ease;
}

.w-dialog {
  width: 100%;
  max-width: 400px;
  background: var(--surface);
  border: 1px solid var(--hairline-firm);
  border-radius: var(--r-card);
  box-shadow: var(--shadow-lift);
  padding: var(--sp-5) var(--sp-5) var(--sp-4);
  animation: w-dialog-pop 0.18s cubic-bezier(0.2, 0.9, 0.3, 1.2);
}

.w-dialog-brand {
  display: flex;
  align-items: center;
  gap: 7px;
  margin-bottom: var(--sp-3);
  padding-bottom: var(--sp-3);
  border-bottom: 1px solid var(--hairline);
}
.w-dialog-brand img { width: 24px; height: auto; display: block; }
.w-dialog-brand span {
  font-size: 15px;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--accent-strong);
}

.w-dialog-msg {
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--ink);
  white-space: pre-line;          /* messages use \n for lists */
  overflow-wrap: anywhere;
  max-height: 55vh;
  overflow-y: auto;
}

.w-dialog-input {
  width: 100%;
  margin-top: var(--sp-4);
  padding: 10px 12px;
  border: 1px solid var(--hairline-firm);
  border-radius: var(--r-control);
  font: inherit;
  font-size: 14px;
  color: var(--ink);
  background: var(--surface);
}

.w-dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--sp-2);
  margin-top: var(--sp-5);
}

@keyframes w-dialog-fade { from { opacity: 0; } }
@keyframes w-dialog-pop  { from { opacity: 0; transform: scale(0.94) translateY(8px); } }

@media (prefers-reduced-motion: reduce) {
  .w-dialog-overlay, .w-dialog { animation: none; }
}

/* Toast — appToast() in dialog.js. Bottom centre, clear of the mobile dock.
   Never takes a tap: it sits over live content and must not become the
   invisible layer that swallows one. */
.w-toast {
  position: fixed;
  left: 50%;
  bottom: calc(24px + env(safe-area-inset-bottom, 0px));
  z-index: 210;
  max-width: calc(100vw - 32px);
  padding: 10px 18px;
  border-radius: var(--r-pill);
  background: var(--primary);
  color: var(--primary-ink);
  font-size: 14px;
  font-weight: 600;
  box-shadow: var(--shadow-lift);
  pointer-events: none;
  opacity: 0;
  transform: translate(-50%, 8px);
  transition: opacity 0.18s ease, transform 0.18s ease;
}
/* The tick stops it reading as one more dark button beside the one just pressed */
.w-toast::before { content: '✓'; margin-right: 8px; color: var(--toast-tick); font-weight: 800; }
.w-toast.is-on { opacity: 1; transform: translate(-50%, 0); }
@media (max-width: 880px) {
  .w-toast { bottom: calc(84px + env(safe-area-inset-bottom, 0px)); }
}
@media (prefers-reduced-motion: reduce) {
  .w-toast { transition: none; transform: translate(-50%, 0); }
}

/* Bug / idea report (src/js/bug-report.js). Its own overlay class: dialog.js
   removes any body-level .w-dialog-overlay it does not own. Same scrim, and
   the same rule — no backdrop-filter on a layer that comes and goes. */
.w-report-overlay {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: var(--scrim-firm);
  animation: w-dialog-fade 0.16s ease;
}
.w-report {
  max-width: 460px;
  max-height: calc(100dvh - 40px);
  display: flex;
  flex-direction: column;
}
.w-report-body { overflow-y: auto; -webkit-overflow-scrolling: touch; min-height: 0; }
.w-report-type { border: 0; margin: 0 0 var(--sp-3); padding: 0; display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.w-report-type legend { padding: 0; margin-bottom: 6px; font-size: 12px; font-weight: 600; color: var(--ink-muted); }
.w-report-type label {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  padding: 10px;
  border: 1px solid var(--hairline-firm);
  border-radius: var(--r-control);
  cursor: pointer;
  font-size: 13.5px;
  line-height: 1.3;
}
.w-report-type label:has(input:checked) { border-color: var(--accent); background: var(--accent-wash); }
.w-report-type input { margin: 2px 0 0; accent-color: var(--accent); flex-shrink: 0; }
.w-report-type small { display: block; margin-top: 2px; font-size: 12px; color: var(--ink-muted); }
.w-report-field { display: flex; flex-direction: column; gap: 4px; margin-bottom: var(--sp-3); font-size: 12px; font-weight: 600; color: var(--ink-muted); }
.w-report-field[hidden] { display: none; }
.w-report-field em { font-style: normal; font-weight: 500; }
.w-report-field input, .w-report-field textarea {
  width: 100%;
  box-sizing: border-box;
  padding: 9px 11px;
  border: 1px solid var(--hairline-firm);
  border-radius: var(--r-control);
  font: inherit;
  font-size: 16px;          /* under 16px iOS zooms the page on focus */
  font-weight: 400;
  color: var(--ink);
  background: var(--surface);
  resize: vertical;
}
.w-report-shot { display: flex; align-items: center; gap: 10px; margin-bottom: var(--sp-3); }
.w-report-shot-note { font-size: 12px; color: var(--ink-muted); }
.w-report-thumb { max-height: 96px; max-width: 120px; border-radius: 8px; border: 1px solid var(--hairline-firm); display: block; }
.w-report-auto { font-size: 12.5px; color: var(--ink-muted); }
.w-report-auto summary { cursor: pointer; font-weight: 600; }

/* Rota board: a boat's crew/squad pick. The label shows the choice at card
   size and wraps; the real select is stretched over it, invisible, so the
   native picker still opens. Phones force selects to 16px (iOS zooms on focus
   below that), which cut a long crew name off in a narrow card. */
.w-boat-tag { position: relative; display: flex; align-items: center; gap: 4px; margin-top: 6px; padding: 4px 6px; border: 1px solid var(--hairline-firm); border-radius: 6px; background: var(--surface); font-size: 11px; font-weight: 700; line-height: 1.25; cursor: pointer; }
.w-boat-tag-text { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.w-boat-tag svg { flex: none; opacity: .6; }
.w-boat-tag select { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; }
.w-boat-tag:focus-within { outline: 2px solid var(--accent); outline-offset: 1px; }
.w-report-auto ul { margin: 6px 0 0; padding-left: 18px; line-height: 1.55; }
.w-report-err { margin-top: var(--sp-3); font-size: 13px; color: var(--bad); }
.w-report-done { text-align: center; padding: var(--sp-4) 0 var(--sp-2); font-size: 14.5px; }
.w-report-done p { margin: 6px 0 0; font-size: 13.5px; color: var(--ink-muted); }
.w-report-tick {
  width: 44px; height: 44px; margin: 0 auto 10px;
  border-radius: var(--r-pill);
  background: var(--accent-wash); color: var(--accent-strong);
  display: flex; align-items: center; justify-content: center;
  font-size: 22px; font-weight: 700;
}
/* Phone: sit at the top, so the keyboard covers the empty half of the screen */
@media (max-width: 880px) {
  .w-report-overlay { align-items: flex-start; padding: calc(12px + env(safe-area-inset-top, 0px)) 12px 12px; }
  .w-report { max-height: calc(100dvh - 24px - env(safe-area-inset-top, 0px)); }
  /* A field has focus: room below, so a lower field can be lifted above the
     keyboard inside the body (viewport.js, data-kb-scroller; #198) */
  .w-report-body.is-typing::after { content: ''; display: block; height: 55vh; }
}
@media (prefers-reduced-motion: reduce) {
  .w-report-overlay { animation: none; }
}

/* =========================================================================
   SELECTS — modern rounded dropdowns
   The closed buttons are still mostly inline-styled per screen; this layer
   owns the OPEN picker via customizable <select> (Chromium 135+). Browsers
   without ::picker(select) support keep their native picker untouched.
   ========================================================================= */
select {
  font-family: inherit;
  cursor: pointer;
  accent-color: var(--accent);
}

/* Legacy form controls (log/create-session modals) never got a class def */
.form-control {
  box-sizing: border-box;
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--hairline-firm);
  border-radius: var(--r-control);
  font: inherit;
  font-size: 14px;
  color: var(--ink);
  background: var(--surface);
}

@supports selector(::picker(select)) {
  select:not([multiple]):not([size]),
  select:not([multiple]):not([size])::picker(select) {
    appearance: base-select;
  }

  /* The open panel: rounded card floating just below the button */
  ::picker(select) {
    margin-block: 5px;
    padding: 6px;
    background: var(--surface);
    border: 1px solid var(--hairline-firm);
    border-radius: 14px;
    box-shadow: var(--shadow-lift);
    opacity: 1;
    transition: opacity 0.14s ease, transform 0.14s ease,
                overlay 0.14s ease allow-discrete, display 0.14s ease allow-discrete;
  }
  @starting-style {
    ::picker(select) { opacity: 0; transform: translateY(-5px); }
  }

  select option {
    padding: 9px 12px;
    border-radius: 9px;
    font-size: 13.5px;
    font-weight: 500;
    color: var(--ink);
    cursor: pointer;
  }
  select option:hover,
  select option:focus-visible { background: var(--tint-firm); }
  select option:checked {
    background: var(--accent-wash);
    color: var(--accent-strong);
    font-weight: 600;
  }
  select option::checkmark { color: var(--accent-strong); }

  select optgroup { font-size: 12px; color: var(--ink-muted); }

  /* Chevron replaces the native arrow; flips while open */
  select::picker-icon {
    color: var(--ink-muted);
    transition: rotate 0.14s ease;
  }
  select:open::picker-icon { rotate: 180deg; }

  @media (prefers-reduced-motion: reduce) {
    ::picker(select), select::picker-icon { transition: none; }
  }
}

/* ── Switch (V10 notifications card) ─────────────────────────────── */
/* Create Rota: the seat under a dragged name, and every seat while a copy is picked up */
.w-seat.is-drop { background: var(--warn-wash) !important; outline: 2px dashed var(--primary); outline-offset: -3px; }
/* Create Rota's people panel: a card hidden by the name search (#270) */
.rb-person.is-hidden { display: none !important; }
/* Create Rota's session picker (Oct 2026): rows across the week — day names,
   AM cards, a line, PM cards — so every PM card starts level. Each day is
   display: contents here and its own column again on a phone (below). */
.rb-week { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); column-gap: 10px; row-gap: 8px; }
.rb-day { display: contents; }
.rb-day-label { grid-row: 1; font-size: 12px; font-weight: 800; color: var(--ink); text-align: center; }
.rb-day > .rb-am { grid-row: 2; }
.rb-day > .rb-pm { grid-row: 4; }
.rb-ampm-line { grid-row: 3; grid-column: 1 / -1; height: 1px; background: var(--hairline-firm); margin: 4px 0; }
/* The tick box inside a category pill, and the Compare switch beside them */
.rb-tick { width: 15px; height: 15px; box-sizing: border-box; border-radius: 4px; border: 1.5px solid var(--ink-faint); background: var(--surface); display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; }
.rb-tick.is-on { border-color: var(--primary); background: var(--primary); color: var(--primary-ink); }
.rb-compare { display: inline-flex; align-items: center; gap: 10px; font-size: 13.5px; font-weight: 700; color: var(--ink); cursor: pointer; }
@media (max-width: 880px) {
  .rb-week { display: flex; flex-direction: column; gap: 14px; }
  .rb-day { display: flex; flex-direction: column; gap: 8px; }
  .rb-ampm-line { display: none; }
  /* Compare is desktop only: the boards stack on a phone whatever it is set to */
  .rb-compare { display: none; }
}
.w-seat.is-drop, .w-seat.is-drop * { color: var(--ink) !important; }
.w-copying .w-seat { cursor: copy; }
.w-copying .w-seat:hover { background: var(--warn-wash) !important; outline: 2px dashed var(--warn); outline-offset: -3px; }
.w-switch { position: relative; display: inline-block; width: 44px; height: 26px; flex-shrink: 0; }
.w-switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.w-switch span { position: absolute; inset: 0; border-radius: 26px; background: var(--switch-off); cursor: pointer; transition: background .2s; }
.w-switch span::before { content: ""; position: absolute; top: 3px; left: 3px; width: 20px; height: 20px; border-radius: 50%; background: var(--switch-knob); box-shadow: var(--shadow-micro); transition: transform .2s; }
.w-switch input:checked + span { background: var(--good); }
.w-switch input:checked + span::before { transform: translateX(18px); }
.w-switch input:focus-visible + span { outline: 2px solid var(--ink); outline-offset: 2px; }
.w-switch input:disabled + span { opacity: .45; cursor: not-allowed; }
/* ── Notifications: bell + inbox (V10) ───────────────────────────── */
.w-bell { position: relative; width: 36px; height: 36px; border: none; border-radius: 50%; background: none; color: var(--ink); cursor: pointer; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; }
.w-bell svg { width: 20px; height: 20px; }
.w-bell:hover { background: var(--tint-firm); }
/* Connection (Oct 2026): live waves by the bell, struck through and amber when
   offline. The banner under it says how old the data on screen is. */
.w-conn { width: 36px; height: 36px; border: none; border-radius: 50%; background: none; color: var(--ink-muted); cursor: pointer; display: inline-flex; align-items: center; justify-content: center; flex-shrink: 0; }
.w-conn svg { width: 20px; height: 20px; }
.w-conn:hover { background: var(--tint-firm); }
.w-conn[data-state="offline"] { color: var(--warn); background: var(--warn-wash); }
.w-conn[data-state="back"] { color: var(--good); }
.w-conn-refresh { padding: 0; border: none; background: none; font: inherit; color: inherit; text-decoration: underline; cursor: pointer; }
/* A newer release is live (update-check.js): news, not a warning */
.w-update-banner { color: var(--accent-strong); background: var(--accent-wash); }
.w-bell-badge { position: absolute; top: 3px; right: 2px; min-width: 17px; height: 17px; padding: 0 5px; border-radius: 9px; background: var(--bad); color: var(--on-fill); font-size: 11px; font-weight: 700; line-height: 17px; text-align: center; box-sizing: border-box; }
.w-notif-list { display: grid; gap: 10px; max-width: 720px; }
.w-notif-wrap { position: relative; border-radius: 14px; overflow: hidden; max-height: 400px; }
.w-notif-under { position: absolute; inset: 0; display: flex; align-items: center; justify-content: flex-end; padding-right: 22px; background: var(--bad); color: var(--on-fill); font-size: 13px; font-weight: 700; opacity: .85; }
.w-notif-wrap.will-delete .w-notif-under { opacity: 1; }
.w-notif { position: relative; display: flex; align-items: flex-start; gap: 12px; padding: 14px 16px 14px 14px; background: var(--surface); border: 1px solid var(--hairline-firm); border-radius: 14px; cursor: pointer; touch-action: pan-y; will-change: transform; }
.w-notif:hover { background: var(--hover-surface); }
.w-notif-dot { width: 8px; height: 8px; margin-top: 7px; border-radius: 50%; background: transparent; flex-shrink: 0; }
.w-notif.is-new .w-notif-dot { background: var(--primary); }
.w-notif-main { flex: 1; min-width: 0; }
.w-notif-title { font-size: 15px; font-weight: 700; color: var(--ink); line-height: 1.3; }
.w-notif.is-new .w-notif-title { font-weight: 800; }
.w-notif-body { font-size: 13.5px; color: var(--ink); line-height: 1.45; margin-top: 4px; white-space: pre-line; }
.w-notif-time { font-size: 12px; color: var(--ink-muted); margin-top: 6px; }
.w-notif-x { width: 30px; height: 30px; margin: -6px -8px 0 0; border: none; border-radius: 50%; background: none; color: var(--ink-muted); font-size: 20px; line-height: 30px; cursor: pointer; flex-shrink: 0; }
.w-notif-x:hover { background: var(--tint-firm); color: var(--ink); }
@media (max-width: 880px) {
  .w-topbar .w-bell, .w-topbar .w-conn { width: 34px; height: 34px; }
  .w-notif-x { display: none; }   /* swipe left instead */
}
/* ── Avatar picture (V10 Google photo) — sits over the initials; if the image
   fails to load it removes itself and the initials show through. */
.w-avatar img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; border-radius: inherit; }
.w-avatar-lg { width: 100px; height: 100px; margin: 0 auto 16px; font-family: 'Quicksand', sans-serif; font-size: 32px; font-weight: 800; border-radius: 50%; }

/* ── Live-session takeover (interval runner / manual recording) ──
   While the timer runs the ink panel covers the WHOLE viewport — over the
   top bar, sub-tabs, sidebar and bottom dock — so the cox gets every pixel.
   Sits under dialogs (z 200) so an alert can still surface. Inline padding /
   radius / margin on the panel are overridden here. */
.w-session-full {
  position: fixed;
  inset: 0;
  z-index: 150;
  margin: 0 !important;
  border-radius: 0 !important;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: calc(18px + env(safe-area-inset-top, 0px)) 18px calc(22px + env(safe-area-inset-bottom, 0px)) !important;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  justify-content: safe center;
}
.w-session-full > * { flex-shrink: 0; }

/* While a takeover is on the floating dock, FAB and More sheet are removed —
   on iOS the dock otherwise paints over the panel's bottom buttons — and the
   stage stops scrolling. html.session-full is set by enterFullscreen and
   cleared by leaveFullscreen (live-tracking.js); the :has() rules cover a
   panel left on screen without the class, and are kept SEPARATE so a browser
   without :has() still applies the class-based ones. */
html.session-full .w-dock, html.session-full .w-fab,
html.session-full .w-sheet, html.session-full .w-sheet-backdrop { display: none !important; }
html.session-full .w-stage { overflow: hidden; }
html:has(.w-session-full) .w-dock, html:has(.w-session-full) .w-fab,
html:has(.w-session-full) .w-sheet, html:has(.w-session-full) .w-sheet-backdrop { display: none !important; }
html:has(.w-session-full) .w-stage { overflow: hidden; }

/* =========================================================================
   SIGN-IN GATE (sign-in.js card()) — every screen before the app boots
   ========================================================================= */
/* Its own scroller, because on a phone the body cannot scroll (viewport.js):
   without one, a tall card could not be scrolled and a field under the
   keyboard could not be reached. data-kb-scroller opts it into the keyboard
   lift; the inner box keeps the card centred while it fits. */
.w-gate { height: 100dvh; overflow-y: auto; -webkit-overflow-scrolling: touch; background: var(--canvas); }
.w-gate-inner { min-height: 100%; box-sizing: border-box; display: flex; align-items: center; justify-content: center; padding: 24px; }

/* =========================================================================
   PUBLIC REGATTA PAGES (/r/{slug}) — no sign-in, no app shell
   ========================================================================= */
.w-pub { min-height: 100dvh; display: flex; flex-direction: column; background: var(--canvas); }
.w-pub-head { background: var(--chrome); color: var(--chrome-ink); padding: 18px 16px 14px; }
.w-pub-head > * { max-width: 720px; margin-left: auto; margin-right: auto; }
.w-pub-brand { display: flex; align-items: center; gap: 8px; font-size: 12px; font-weight: 600; color: var(--chrome-muted); }
.w-pub-brand img { display: block; }
.w-pub-brand .dot { opacity: .5; }
.w-pub-title { margin: 8px auto 4px; font-size: 24px; font-weight: 700; letter-spacing: -0.02em; line-height: 1.15; }
.w-pub-sub { font-size: 13px; color: var(--chrome-muted); }
.w-pub-sub .late { color: var(--chrome-warn); font-weight: 600; }
.w-pub-tabs { display: flex; gap: 6px; margin-top: 12px; }
.w-pub-tab { font-family: inherit; font-size: 13.5px; font-weight: 600; padding: 8px 15px; border-radius: var(--r-pill); border: none; background: transparent; color: var(--chrome-muted); cursor: pointer; }
.w-pub-tab.on { background: var(--chrome-active); color: var(--chrome-ink); font-weight: 700; }
.w-pub-main { flex: 1; width: 100%; max-width: 720px; margin: 0 auto; padding: 16px 16px 40px; box-sizing: border-box; }
.w-pub-foot { text-align: center; font-size: 11.5px; color: var(--ink-muted); padding: 16px; }
.w-pub-foot a { color: var(--accent-strong); font-weight: 600; text-decoration: none; }
.w-pub-race { display: flex; gap: 12px; align-items: center; padding: 10px 0; border-top: 1px solid var(--hairline); }
.w-pub-race.racing { background: var(--accent-wash); margin: 0 -16px; padding: 10px 16px; }
.w-pub-pos { width: 30px; height: 30px; border-radius: var(--r-pill); background: var(--surface-sunk); font-weight: 700; font-size: 13px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; font-variant-numeric: tabular-nums; }
.w-pub-pos.gold { background: var(--primary); color: var(--primary-ink); }

/* Join Withe — the foot of every public regatta page */
.w-join { margin-top: 28px; padding: 18px 20px; border-radius: var(--r-card); background: var(--surface); border: 1px solid var(--hairline); }
.w-join-t { font-size: 17px; font-weight: 700; color: var(--ink); }
.w-join-s { margin-top: 4px; font-size: 13.5px; line-height: 1.5; color: var(--ink-muted); }
.w-join-list { margin: 10px 0 14px; padding-left: 18px; font-size: 13.5px; line-height: 1.6; color: var(--ink); }
.w-join-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }

/* Public regatta pages have no app stage: on a phone the body must scroll
   (the mobile block above locks html/body so the app's inner stage scrolls
   instead — main.js marks the public route with html.is-public). */
html.is-public, html.is-public body { height: auto; overflow: auto; overflow-x: hidden; }

/* Boat card contact sheets (src/js/rota-contacts.js): rower numbers and
   emergency contacts, every number a tel: link. */
.w-cl { max-height: calc(100dvh - 40px); display: flex; flex-direction: column; }
.w-cl-title { display: flex; align-items: center; gap: 10px; font-family: 'Quicksand', sans-serif; font-weight: 800; font-size: 17px; color: var(--ink); margin-bottom: 6px; }
.w-cl-title.is-bad { color: var(--bad); }
.w-cl-title small { display: block; font-size: 12px; font-weight: 600; color: var(--ink-muted); }
.w-cl-body { overflow-y: auto; -webkit-overflow-scrolling: touch; min-height: 0; }
.w-cl-row { padding: 10px 0; border-top: 1px solid var(--hairline-firm); }
.w-cl-seat { font-size: 11px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-muted); }
.w-cl-name { font-size: 14.5px; font-weight: 700; color: var(--ink); }
.w-cl-name span { font-size: 12px; font-weight: 600; color: var(--ink-muted); }
.w-cl-none { margin-top: 3px; font-size: 13px; font-style: italic; color: var(--ink-muted); }
.w-cl-call { display: inline-flex; align-items: center; gap: 8px; margin-top: 6px; min-height: 44px; padding: 0 16px; border-radius: 12px; background: var(--good-wash); color: var(--good); font-size: 15px; font-weight: 800; text-decoration: underline; }
.w-cl-call.is-bad { background: var(--bad-wash); color: var(--bad); }
.w-cl-call svg { vertical-align: 0 !important; }
.w-boat-contacts { display: grid; grid-template-columns: 1fr 1fr; border-top: 1px solid var(--hairline-firm); }
.w-boat-contacts:has(> :only-child) { grid-template-columns: 1fr; }
.w-boat-contacts button { height: 44px; border: 0; background: var(--surface); color: var(--ink); cursor: pointer; display: flex; align-items: center; justify-content: center; }
.w-boat-contacts button + button { border-left: 1px solid var(--hairline-firm); }
.w-boat-contacts button.is-bad { color: var(--bad); }
.w-boat-contacts svg { vertical-align: 0 !important; stroke-width: 1.5; }

/* =========================================================================
   OCEAN STYLE — :root[data-style="ocean"] (Appearance → Style, Nov 2026)
   A soft sea gradient behind the canvas, and frosted glass on the things
   that float over it: the top bar, the mobile dock, the account menu, the
   More sheet and the Next outing card (.w-glass). Cards in lists stay solid,
   so a long rota still scrolls smoothly on a budget phone. Both themes:
   --ocean-backdrop and the --glass-* tokens have a light and a dark value.
   ========================================================================= */
:root[data-style="ocean"] body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  background: var(--ocean-backdrop);
  pointer-events: none;
}
:root[data-style="ocean"] .app-shell,
:root[data-style="ocean"] .w-gate,
:root[data-style="ocean"] .w-pub { background: transparent; }
:root[data-style="ocean"] .w-topbar { background: var(--glass-bg); }

:root[data-style="ocean"] .w-dock,
:root[data-style="ocean"] .w-usermenu,
:root[data-style="ocean"] .w-sheet,
:root[data-style="ocean"] .w-glass {
  background: var(--glass-strong);
  border: 1px solid var(--glass-edge);
  box-shadow: var(--glass-shadow);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
}
:root[data-style="ocean"] .w-glass { background: var(--glass-bg); }
/* A menu opens inside the top bar, whose own backdrop-filter stops the
   browser blurring what is behind the menu: it needs to be nearly opaque. */
:root[data-style="ocean"] .w-usermenu,
:root[data-style="ocean"] .w-sheet { background: var(--glass-menu); }
/* The dock turns from ink to glass, so its items take the page's inks */
:root[data-style="ocean"] .w-dock-item { color: var(--ink-muted); }
:root[data-style="ocean"] .w-dock-item.is-active { color: var(--ink); }
:root[data-style="ocean"] .w-dock-item.is-active svg { color: var(--accent); }

/* Solid instead of glass where it cannot work or is not wanted: a browser
   without backdrop-filter, and anyone whose device asks for less
   transparency. The sea stays; only the glass goes. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  :root[data-style="ocean"] .w-dock,
  :root[data-style="ocean"] .w-usermenu,
  :root[data-style="ocean"] .w-sheet,
  :root[data-style="ocean"] .w-glass,
  :root[data-style="ocean"] .w-topbar { background: var(--surface-raised); }
}
@media (prefers-reduced-transparency: reduce) {
  :root[data-style="ocean"] .w-dock,
  :root[data-style="ocean"] .w-usermenu,
  :root[data-style="ocean"] .w-sheet,
  :root[data-style="ocean"] .w-glass,
  :root[data-style="ocean"] .w-topbar {
    background: var(--surface-raised);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }
}

/* =========================================================================
   POLISH (Nov 2026)
   ========================================================================= */
/* Your next outing / session — the card at the top of Rowing Rota (weekly-rota.js
   renderNextOuting). Glass in the Ocean style (.w-glass, above). */
.w-next { margin-bottom: var(--sp-5); padding: var(--sp-4) var(--sp-5); }
.w-next-eyebrow { font-size: 17px; font-weight: 700; color: var(--ink); }
.w-next-when { margin-top: 2px; font-size: 22px; font-weight: 700; letter-spacing: -.01em; color: var(--ink); }
.w-next-sub { margin-top: 2px; font-size: 13px; font-weight: 600; color: var(--ink-muted); }

/* The crew as a seat strip: the six rowing seats in order, the cox set apart,
   empty seats dashed, the viewer's own filled in. */
.w-seatstrip { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-top: var(--sp-3); }
.w-seatstrip-seat {
  display: grid; place-items: center;
  width: 32px; height: 32px; border-radius: 50%;
  background: var(--tint-firm); color: var(--ink);
  font-size: 11.5px; font-weight: 700;
}
.w-seatstrip-seat.is-open { background: transparent; border: 1.5px dashed var(--ink-faint); color: var(--ink-faint); }
.w-seatstrip-seat.is-cox { margin-left: 8px; box-shadow: 0 0 0 1.5px var(--ink-muted); }
.w-seatstrip-seat.is-me { background: var(--accent); color: var(--accent-ink); }

/* Numbers that line up: times, splits, scores and counts keep still as they
   change. Quicksand carries tabular figures. */
body { font-variant-numeric: tabular-nums; }

/* Press feedback: a tapped pill or chip gives a little, so the tap reads as
   registered (buttons already do, .w-btn:active). */
.w-tab:active, .w-chip:active, .w-seg-btn:active, .w-appear-tile:active, .w-dock-item:active { transform: scale(0.97); }
@media (prefers-reduced-motion: reduce) {
  .w-tab:active, .w-chip:active, .w-seg-btn:active, .w-appear-tile:active, .w-dock-item:active { transform: none; }
}

/* In the dark, the selected sidebar item gets a faint blue wash and edge
   instead of another solid block. */
:root[data-theme="dark"] .w-nav-item.is-active {
  background: linear-gradient(90deg, color-mix(in srgb, var(--chrome-accent) 22%, transparent), color-mix(in srgb, var(--chrome-accent) 4%, transparent));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--chrome-accent) 18%, transparent);
}

/* =========================================================================
   HOME (Nov 2026) — the landing page (modules/home.js): a greeting, the next
   outing card (.w-next, above) and this week's rowing and gym rota places.
   ========================================================================= */
.w-home { display: flex; flex-direction: column; gap: var(--sp-4); max-width: 640px; }
.w-home-hello { padding: 0 2px; }
.w-home > .w-card { margin: 0; }
/* Home has one page, so no subtab bar */
.dock-ctx-home #subtab-bar { display: none; }

.w-home-week { padding: var(--sp-4) var(--sp-5); }
.w-home-week-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); margin-bottom: 4px; }
.w-home-h2 { margin: 0; font-size: 17px; font-weight: 700; color: var(--ink); }
.w-home-link {
  border: 0; background: transparent; padding: 6px 0; min-height: 32px;
  font-family: inherit; font-size: 14px; font-weight: 700; color: var(--accent-strong); cursor: pointer;
}
.w-home-list { list-style: none; margin: 0; padding: 0; }
.w-home-row { display: flex; align-items: center; gap: var(--sp-3); padding: 10px 0; border-top: 1px solid var(--hairline-firm); }
.w-home-row:first-child { border-top: 0; }
.w-home-day { width: 52px; flex: none; font-size: 13px; font-weight: 700; color: var(--ink); }
.w-home-what { flex: 1; min-width: 0; font-size: 14px; font-weight: 600; color: var(--ink); }
.w-home-row.is-past .w-home-day,
.w-home-row.is-past .w-home-what { color: var(--ink-muted); }
.w-home-tag { flex: none; font-size: 12px; font-weight: 700; border-radius: var(--r-pill); padding: 3px 8px; }
.w-home-tag.is-next { color: var(--accent-strong); background: var(--accent-wash); }
.w-home-tag.is-good { color: var(--good); padding: 3px 0; }
.w-home-tag.is-bad { color: var(--bad); padding: 3px 0; }
.w-home-empty { margin: 6px 0 2px; font-size: 13px; font-weight: 500; color: var(--ink-muted); }
/* On the water (daylight now; tide, wind and sea to come) */
.w-home-water { padding: var(--sp-4) var(--sp-5); }
.w-home-water-head { display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3); flex-wrap: wrap; margin-bottom: var(--sp-3); }
.w-home-water-sub { margin-top: 2px; font-size: 13px; font-weight: 600; color: var(--ink-muted); }
.w-home-seg { flex: none; }
.w-home-seg .w-seg-btn { padding: 6px 14px; min-height: 32px; }
.w-home-tiles { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
.w-home-tile { position: relative; overflow: hidden; background: var(--tint); border-radius: 12px; padding: 12px 14px; }
.w-home-tile.is-wide { grid-column: span 2; }
/* The watermark: a large faint line drawing in the tile's corner, behind the text */
.w-home-mark, .w-home-waves { position: absolute; fill: none; stroke: currentColor; stroke-linecap: round; stroke-linejoin: round; opacity: .22; pointer-events: none; }
.w-home-mark { right: -10px; bottom: -12px; width: 78px; height: 78px; stroke-width: 1.3; }
.w-home-waves { left: 0; bottom: -2px; width: 100%; height: 44px; stroke-width: 1.4; }
.w-home-tile > :not(svg) { position: relative; }
.w-home-tile.is-sun { color: var(--cond-sun); }
.w-home-tile.is-cold { color: var(--cond-cold); }
.w-home-tile.is-water { color: var(--cond-water); }
.w-home-tile.is-sea { color: var(--cond-sea); }
.w-home-tile-label { font-size: 12px; font-weight: 700; color: var(--ink-muted); }
.w-home-tile-big { margin-top: 4px; font-size: 20px; font-weight: 700; color: var(--ink); }
.w-home-tile-note { margin-top: 2px; font-size: 12px; font-weight: 600; color: var(--ink-muted); }
.w-home-wind { margin-bottom: var(--sp-3); }
.w-home-wind-head { font-size: 26px; font-weight: 700; letter-spacing: -0.01em; line-height: 1.15; color: var(--ink); }
.w-home-wind-sub { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; margin-top: 6px; font-size: 13px; font-weight: 600; color: var(--ink-muted); }
.w-home-force { font-size: 12.5px; font-weight: 700; color: var(--accent-strong); background: var(--accent-wash); border-radius: var(--r-pill); padding: 3px 10px; }
.w-home-credit { margin: var(--sp-3) 0 0; font-size: 11px; line-height: 1.5; font-weight: 500; color: var(--ink-muted); }
/* The Tide tile: the day's curve and its high and low waters */
.w-home-tide-curve { display: block; width: 100%; height: 56px; margin-top: 8px; }
.w-home-tide-fill { fill: currentColor; opacity: .12; }
.w-home-tide-line { fill: none; stroke: currentColor; stroke-width: 2; vector-effect: non-scaling-stroke; stroke-linejoin: round; }
.w-home-tide-now { stroke: var(--ink); stroke-width: 1.5; stroke-dasharray: 3 3; vector-effect: non-scaling-stroke; }
.w-home-tide-evs { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.w-home-tide-ev { font-size: 12px; font-weight: 700; color: var(--ink-muted); background: var(--tint); border-radius: var(--r-pill); padding: 3px 9px; }
.w-home-tide-ev.is-high { color: var(--accent-strong); background: var(--accent-wash); }
/* On the Tide tile the swell sits behind the curve, fainter */
.w-home-tide .w-home-waves { opacity: .12; }

/* "New: dark mode" callout (announce.js): the page dims, a ring circles the
   account button and a card points at it */
.w-announce { position: fixed; inset: 0; z-index: 200; }
.w-announce-scrim { position: absolute; inset: 0; }
.w-announce-ring { position: absolute; border-radius: 50%; box-shadow: 0 0 0 2.5px var(--on-scrim), 0 0 0 8px color-mix(in srgb, var(--on-scrim) 28%, transparent); pointer-events: none; }
.w-announce-card { position: absolute; box-sizing: border-box; background: var(--surface-raised); color: var(--ink); border-radius: 18px; padding: 18px 20px 20px; box-shadow: var(--shadow-float); }
.w-announce-arrow { position: absolute; top: -8px; width: 20px; height: 20px; background: var(--surface-raised); transform: rotate(45deg); border-radius: 4px; }
.w-announce-icon { position: relative; width: 38px; height: 38px; border-radius: 50%; display: grid; place-items: center; background: var(--accent-wash); color: var(--accent-strong); margin-bottom: 10px; }
.w-announce-title { position: relative; font-size: 19px; font-weight: 700; }
.w-announce-text { margin: 6px 0 16px; font-size: 14px; line-height: 1.45; font-weight: 500; color: var(--ink-muted); }
.w-announce-btns { display: flex; gap: 10px; }
.w-announce-btns .is-primary { flex: 1; }
/* Show me: Appearance is ringed for a few seconds, tagged New */
.w-appear.is-new { border-radius: 14px; box-shadow: 0 0 0 2px var(--accent), 0 0 0 6px var(--accent-wash); transition: box-shadow .4s ease; }
.w-appear.is-new .w-appear-label::after { content: "New"; margin-left: 8px; padding: 1px 7px; border-radius: var(--r-pill); background: var(--accent-wash); color: var(--accent-strong); letter-spacing: 0; text-transform: none; }
