/* =====================================================================
   Fulham Boxing Club — staff app.
   Design system taken from the Claude Design project
   "Fulham Boxing Club.dc.html": dark rail, warm paper canvas, white cards,
   Space Grotesk for headings and figures, Manrope for everything else.
   ===================================================================== */

:root{
  /* canvas */
  --paper:#F7F5F2; --card:#fff; --line:#ECEAE5; --divider:#F2F0EC; --sunk:#FAF9F7;
  /* rail */
  --rail:#14151A; --rail-hover:#212229; --rail-line:#23242B;
  --rail-text:#B7B9C0; --rail-sub:#8B8D96;
  /* type */
  --ink:#16171B; --muted:#6B6F76; --faint:#A2A5AC;
  /* accent */
  --accent:#1E7845; --accent-d:#145C34; --accent-soft:rgba(30,120,69,.14);
  /* status palette */
  --green:#17B26A; --green-ink:#0E8C51; --green-soft:rgba(23,178,106,.14);
  --blue:#2F6FED;  --blue-ink:#1D4FBE;  --blue-soft:rgba(47,111,237,.14);
  --violet:#8B5CF6;--violet-ink:#6D3FD1;--violet-soft:rgba(139,92,246,.14);
  --amber:#F5A623; --amber-ink:#C9840F; --amber-soft:rgba(245,166,35,.16);
  --pink:#FF5C93;  --red:#E8341C;       --grey-soft:rgba(160,160,160,.18); --grey-ink:#7A7C82;
  /* shape */
  --r-card:16px; --r-ctl:10px; --r-modal:18px; --r-pill:20px;
  --shadow-card:0 1px 2px rgba(0,0,0,.03);
  --font:'Manrope',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;
  --display:'Space Grotesk','Manrope',sans-serif;
}

*{box-sizing:border-box}
/* The app toggles visibility with the `hidden` attribute, and almost every
   block below sets `display` — which beats the UA [hidden] rule. */
[hidden]{display:none !important}

/* The app owns the viewport: the document itself never scrolls. Anything that
   genuinely cannot fit (a 300-row member list) scrolls inside its own .pane,
   so the chrome — rail, page header, footer — is always where you left it.
   100dvh not 100vh: on mobile 100vh is the *largest* viewport height, which
   sits under the browser toolbar and guarantees the thing we are avoiding. */
/* 100vh, NOT 100%. This is the whole white-bar bug in one line.
   On an installed iOS app, height:100% resolves against a layout viewport
   that stops above the home indicator -- measured on an iPhone 15 Pro Max:
   documentElement.clientHeight 873 against a 932 screen, while 100vh reads
   932. So the DOCUMENT ended 59px short, and the strip below it was never
   the app to paint. That is also why recolouring html did nothing: the
   background was reaching the bottom of a document that finished early.
   100vh is the full viewport including the safe areas, so the background
   now runs edge to edge and the home indicator floats over it. */
html,body{height:100dvh;overflow:hidden;overscroll-behavior:none}
/* ...but ONLY once installed. In a browser tab Safari's toolbar collapses
   and expands, and vh/lvh both mean "as if it were hidden" — permanently
   taller than what you can see. With overflow:hidden that leaves the bottom
   of the layout parked behind the toolbar with no way to scroll to it, and
   the top clipped as the visual viewport slides inside a layout box bigger
   than itself. dvh is the one that tracks the toolbar; vh is right only
   where there is no toolbar to track. Same split in account/portal.css. */
@media (display-mode:standalone),(display-mode:fullscreen),(display-mode:minimal-ui){
  html,body{height:100vh}
}
/* The canvas colour comes from <html>, not from <body>: if anything ever does
   manage to scroll past the body — an iOS keyboard shifting the whole webview
   up is the one that actually happens — the strip it exposes is painted paper
   rather than the UA default, which on iOS is black. That black band under the
   keyboard was the bug; --app-h below removes the scroll, and this makes sure
   it is not a black band even in the case that gets past it. */
html{background:var(--paper)}
body{
  margin:0;background:var(--paper);color:var(--ink);
  font-family:var(--font);font-size:14px;line-height:1.5;
  -webkit-font-smoothing:antialiased;
}
a{color:var(--blue);text-decoration:none}
a:hover{color:var(--blue-ink)}

/* Scrollbars are opt-in. .pane shows a slim one on hover only; everything
   else that scrolls internally hides it entirely. */
::-webkit-scrollbar{width:0;height:0}
.pane{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;scrollbar-width:none}
.pane::-webkit-scrollbar{width:6px;height:6px}
.pane::-webkit-scrollbar-thumb{background:transparent;border-radius:3px;transition:background .2s}
.pane:hover::-webkit-scrollbar-thumb{background:#D9D5CD}
.pane-x{min-width:0;overflow-x:auto;scrollbar-width:none}
.pane-x::-webkit-scrollbar{height:6px}
.pane-x:hover::-webkit-scrollbar-thumb{background:#D9D5CD}
@keyframes toastIn{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}
@keyframes modalIn{from{opacity:0;transform:translateY(8px) scale(.99)}to{opacity:1;transform:none}}
.a-tip{
  position:fixed;z-index:70;background:var(--ink);color:#fff;font-size:11.5px;font-weight:700;
  padding:6px 10px;border-radius:8px;pointer-events:none;max-width:230px;line-height:1.35;
  box-shadow:0 10px 24px rgba(0,0,0,.2);
}

/* ---------------------------------------------------------------- shell */
/* The white bar along the bottom of the installed iOS app, in three acts —
   each fix real, each insufficient, and worth writing down so the next
   person does not repeat them:

     1. --app-h measured short, because viewport.js only recognised
        `display-mode: standalone` as an installed app. Widened. Still there.
     2. Everything sized FROM --app-h, i.e. from a measurement, when a
        measurement is the one thing that had already proved unreliable.
        Switched to position:fixed with inset:0. Still there.
     3. inset:0 covers the LAYOUT viewport and nothing beyond it. On an
        installed iOS app that viewport can stop above the home indicator,
        and the strip below is the root canvas — which ONLY html's background
        paints, and which no percentage, inset or fixed offset can reach.

   So the shell is sized to the LARGE viewport (100lvh) instead: the biggest
   height the browser will report, which is the screen. min-height, not
   height, so wherever the two agree — every desktop, every Android, iOS in a
   tab — this changes nothing at all.

   Every other full-screen fixed layer (the drawer, both scrims) needs the
   same, or it stops at the same line and the bar reappears through it. */
.shell{
  display:flex;position:fixed;top:0;left:0;right:0;bottom:0;overflow:hidden;
  /* dvh in a browser tab, lvh installed — see the block at the end of this
     file, and html/body at the top. */
  min-height:100dvh;
}
/* Keyboard up: iOS shifts the whole page up rather than resizing it, so the
   shell re-pins to the visual viewport's own top and takes its measured
   height. Without this the focused field can sit behind the keyboard. */
html[data-kb] .shell{
  bottom:auto;top:var(--app-top,0);height:var(--app-h,100dvh);min-height:0;
}
/* The slim mobile bar (hamburger / wordmark / avatar). Hidden here; the
   max-width:860px block below is the only place that shows it, alongside
   the sidebar's switch to an off-canvas drawer — the two change together. */
.topbar{display:none}

/* The rail now carries thirteen items plus Settings. Fixed padding cannot
   make that fit every laptop and desktop height, so the vertical rhythm is
   tied to viewport height and clamped at both ends: it compresses on a short
   window rather than growing a scrollbar, and stops compressing before it
   becomes unreadable. That alone stopped being enough once the item count
   grew, so #nav (below) also gets its own scroll — brand and footer (Settings,
   avatar, log out) stay pinned in place and only the middle list of screens
   ever grows a scrollbar, on any window short enough to need one. This is
   the same element the mobile off-canvas drawer reuses (see the max-width:860px
   block), so the fix applies there too without extra rules. */
.sidebar{
  width:216px;flex-shrink:0;background:var(--rail);
  display:flex;flex-direction:column;
  gap:clamp(1px,.45vh,4px);
  padding:clamp(12px,2.2vh,24px) 16px clamp(10px,1.6vh,18px);
  /* 100% of .shell, which is now pinned to the screen — not --app-h, which
     would overflow the shell by however much the measurement disagrees. */
  height:100%;overflow:hidden;
}
.sidebar nav{flex-direction:column;min-height:0}
/* The primary list only — #navFoot (just Settings) stays fixed-height. */
#nav{flex:1;overflow-y:auto;scrollbar-width:none}
#nav::-webkit-scrollbar{width:0}
.brand{display:flex;align-items:center;gap:10px;padding:0 8px clamp(10px,2vh,20px)}
/* The club's actual badge, with its own transparent background, so it sits
   on the dark rail and on the white sign-in card without a plate behind it. */
.brand .mark{width:36px;height:36px;flex-shrink:0;display:block;object-fit:contain}
.brand .name{font-family:var(--display);font-weight:700;color:#fff;font-size:14px;line-height:1.25;flex:1;min-width:0}

/* ---- rail collapse (desktop only — see the max-width:860px block) ---- */
.railcollapsebtn{
  flex-shrink:0;width:28px;height:28px;display:flex;align-items:center;justify-content:center;
  background:none;border:none;padding:0;cursor:pointer;color:var(--rail-sub);border-radius:8px;
  transition:background .15s ease,color .15s ease;
}
.railcollapsebtn:hover{background:var(--rail-hover);color:#fff}
.railcollapsebtn svg{display:block;width:15px;height:15px}
/* Collapsed: icons only. Width shrinks to fit a railbtn's own padding plus
   icon, everything that would overflow (labels, brand name, who/role text,
   the "Log out" word) just hides rather than truncating — an ellipsis on a
   36px-wide rail teaches nothing. The rail stays a flex column throughout,
   so nothing here has to duplicate .sidebar's own layout rules.
   min-width:861px guards the whole thing: below that the rail is already
   the off-canvas mobile drawer (max-width:860px block, further down), and
   .collapsed's fixed width must never fight that drawer's own sizing if a
   window is narrowed while collapsed=true is still set from a wider one. */
@media (min-width:861px){
  .sidebar.collapsed{width:72px}
  /* Logo and toggle stack instead of sitting side by side — in a 72px rail
     a horizontal pair either crowds the logo off-centre or forces the
     toggle right up against it. Stacked, the logo lands truly centred and
     the toggle gets its own row below with real space around it. */
  .sidebar.collapsed .brand{flex-direction:column;justify-content:center;gap:8px;padding:0 0 clamp(10px,2vh,20px)}
  .sidebar.collapsed .brand .name{display:none}
  .sidebar.collapsed .railcollapsebtn svg{transform:rotate(180deg)}
  .sidebar.collapsed .railbtn{justify-content:center;padding-left:0;padding-right:0}
  .sidebar.collapsed .railbtn .lbl,.sidebar.collapsed .railbtn .count{display:none}
  /* Same reasoning as .brand above: avatar-then-cog side by side has
     nowhere to go at this width, stacked they both get to be centred. */
  .sidebar.collapsed .me{flex-direction:column;gap:6px;padding-left:0;padding-right:0}
  .sidebar.collapsed .me .who{display:none}
  .sidebar.collapsed #logoutBtn{justify-content:center;padding-left:0;padding-right:0}
  .sidebar.collapsed #logoutBtn .lbl{display:none}
}

.sidebar nav{display:flex;flex-direction:column;gap:clamp(1px,.45vh,4px)}
.railbtn{
  display:flex;align-items:center;gap:11px;width:100%;
  padding:clamp(6px,1.15vh,10px) 12px;border-radius:var(--r-ctl);border:none;background:none;
  font:inherit;font-size:13.5px;font-weight:700;color:var(--rail-text);
  text-align:left;cursor:pointer;transition:background .15s ease,color .15s ease;
  line-height:1.25;
  /* iOS Safari/PWAs otherwise paint a blue tap overlay and can select the
     label while the drawer is closing, which looks like Dashboard stayed
     active after another destination was chosen. Keep normal page text
     selectable; only the controls in the navigation chrome opt out. */
  -webkit-tap-highlight-color:transparent;
  -webkit-touch-callout:none;
  -webkit-user-select:none;user-select:none;
}
.railbtn:hover{background:var(--rail-hover);color:#fff}
.railbtn.active{background:var(--accent);color:#fff}
.railbtn svg{flex-shrink:0}
.railbtn .count{
  margin-left:auto;background:var(--accent);color:#fff;
  font-size:10.5px;font-weight:800;padding:2px 6px;border-radius:var(--r-pill);
}
.railbtn.active .count{background:rgba(255,255,255,.25)}

.rail-foot{margin-top:auto;display:flex;flex-direction:column;gap:clamp(1px,.45vh,4px);flex-shrink:0}
/* avatar · name+role · settings cog, on one row. `min-width:0` on the middle
   column is what actually stops a long club name from shoving the cog off the
   rail — without it a flex item refuses to shrink below its content width and
   the two overlap. */
.me{
  display:flex;align-items:center;gap:9px;
  padding:clamp(7px,1.4vh,12px) 4px 4px 8px;
  border-top:1px solid var(--rail-line);margin-top:clamp(3px,.8vh,6px);
}
.me .who{min-width:0;flex:1}
.me .who b{
  display:block;color:#fff;font-size:11.5px;font-weight:700;line-height:1.25;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.me .who span{display:block;color:var(--rail-sub);font-size:10.5px;line-height:1.2}
.cogbtn{
  flex-shrink:0;width:32px;height:32px;display:flex;align-items:center;justify-content:center;
  background:none;border:none;padding:0;cursor:pointer;color:var(--rail-text);
  border-radius:8px;transition:background .15s ease,color .15s ease;
}
.cogbtn:hover{background:var(--rail-hover);color:#fff}
.cogbtn.active{background:var(--accent);color:#fff}
.cogbtn svg{display:block;width:17px;height:17px}
@media (pointer:coarse){.cogbtn{width:44px;height:44px}}
@media (prefers-reduced-motion:reduce){.cogbtn{transition:none}}
.avatar{
  width:30px;height:30px;border-radius:50%;background:var(--blue);flex-shrink:0;
  display:flex;align-items:center;justify-content:center;color:#fff;font-weight:700;font-size:12px;
}
.avatar.lg{width:44px;height:44px;font-size:15px}
.avatar.sm{width:34px;height:34px;font-size:12.5px}

.main{flex:1;min-width:0;min-height:0;display:flex;flex-direction:column;overflow:hidden;position:relative}
/* A screen is built off-document and swapped in whole, so navigating never
   blanks the app. When a render is slow enough to notice, this hairline runs
   across the top of the screen you are still looking at — rather than
   replacing it with the word "Loading". */
.main.busy::before{
  content:'';position:absolute;top:0;left:0;right:0;height:2px;z-index:5;
  background:linear-gradient(90deg,transparent,var(--accent),transparent);
  background-size:40% 100%;background-repeat:no-repeat;
  animation:railProgress 1.1s ease-in-out infinite;
}
@keyframes railProgress{from{background-position:-45% 0}to{background-position:145% 0}}
/* The page fills the viewport and does not scroll. A page that needs more room
   marks one child .pane and that child scrolls. */
.page{
  padding:clamp(16px,2.6vh,32px) clamp(18px,2.2vw,32px);
  display:flex;flex-direction:column;gap:clamp(12px,1.8vh,20px);
  flex:1;min-width:0;min-height:0;overflow:hidden;
}

/* ------------------------------------------------------------- headings */
.pagehead{display:flex;align-items:flex-start;justify-content:space-between;flex-wrap:wrap;gap:16px}
.pagehead h1{font-family:var(--display);font-weight:700;font-size:26px;margin:0}
.pagehead .sub{color:var(--muted);font-size:14px;margin-top:4px}
.pagehead .acts{display:flex;gap:10px;flex-wrap:wrap}
h2.card-title,.card-title{font-family:var(--display);font-weight:700;font-size:16px;margin:0}
.muted{color:var(--muted);font-size:12.5px}
.faint{color:var(--faint);font-size:12px}

/* Content region under the fixed page header. It is the single scroller on a
   screen; children that declare flex:1 (calendar, pipeline, inbox) size to it
   and scroll internally instead, so nothing ever double-scrolls. */
.pagebody{display:flex;flex-direction:column;gap:clamp(12px,1.8vh,20px)}
.pagehead{flex-shrink:0}

/* ---------------------------------------------------------------- cards */
.card{background:var(--card);border:1px solid var(--line);border-radius:var(--r-card);padding:22px;box-shadow:var(--shadow-card)}
.card.tight{padding:20px}
.card-head{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:16px}
.grid{display:grid;gap:20px}
.grid.k4{grid-template-columns:repeat(4,1fr)}
.grid.k3{grid-template-columns:repeat(3,1fr)}
.grid.k2{grid-template-columns:repeat(2,1fr)}
.grid.wide-left{grid-template-columns:2fr 1fr;align-items:stretch}
.grid.wide-right{grid-template-columns:1fr 2fr;align-items:stretch}
.stack{display:flex;flex-direction:column;gap:12px}

/* KPI tile */
.kpi{background:var(--card);border:1px solid var(--line);border-radius:var(--r-card);padding:20px;box-shadow:var(--shadow-card)}
.kpi .k{display:flex;align-items:center;justify-content:space-between}
.kpi .label{color:var(--muted);font-size:12.5px;font-weight:700;text-transform:uppercase;letter-spacing:.04em}
.kpi .dot{width:8px;height:8px;border-radius:50%}
.kpi .v{font-family:var(--display);font-weight:700;font-size:30px;margin-top:8px}
.kpi.sm .v{font-size:26px}
.kpi .trendrow{display:flex;align-items:center;gap:6px;margin-top:6px}
.pill{font-size:11.5px;font-weight:800;padding:2px 7px;border-radius:var(--r-pill);white-space:nowrap}
.pill.green{background:var(--green-soft);color:var(--green-ink)}
.pill.blue{background:var(--blue-soft);color:var(--blue-ink)}
.pill.violet{background:var(--violet-soft);color:var(--violet-ink)}
.pill.amber{background:var(--amber-soft);color:var(--amber-ink)}
.pill.grey{background:var(--grey-soft);color:var(--grey-ink)}

/* -------------------------------------------------------------- buttons */
.btn{
  font:inherit;font-size:13.5px;font-weight:700;padding:10px 18px;
  border-radius:var(--r-ctl);border:none;cursor:pointer;
  transition:transform .15s ease,box-shadow .15s ease,background .15s ease;
}
.btn.primary{background:var(--accent);color:#fff}
.btn.primary:hover:not(:disabled){background:var(--accent-d);transform:translateY(-1px);box-shadow:0 6px 16px var(--accent-soft)}
.btn.secondary{background:#fff;color:var(--ink);border:1px solid var(--line)}
.btn.secondary:hover:not(:disabled){background:var(--paper);transform:translateY(-1px)}
.btn:disabled{opacity:.55;cursor:default}
.btn.sm{padding:6px 12px;font-size:12.5px;border-radius:8px}
.btn.ghost{background:none;border:1px solid var(--line);color:var(--ink);padding:7px 14px;font-size:12.5px;border-radius:8px}
.btn.ghost:hover:not(:disabled){background:var(--paper)}
.btn.link{background:none;border:none;color:var(--muted);font-size:12px;font-weight:700;padding:0}
.btn.link:hover{color:var(--red)}
.btn.danger{color:var(--red);background:none;border:none;font-size:13px;font-weight:700;padding:0}

.segment{display:flex;gap:6px;background:var(--divider);padding:4px;border-radius:var(--r-ctl)}
.segment button{border:none;background:none;font:inherit;font-size:12.5px;font-weight:700;color:var(--muted);padding:6px 12px;border-radius:7px;cursor:pointer}
.segment button.active{background:#fff;color:var(--ink);box-shadow:var(--shadow-card)}

/* --------------------------------------------------------------- inputs */
.field{display:flex;flex-direction:column}
.field label,.flabel{font-size:12px;color:var(--muted);font-weight:700;margin-bottom:6px;display:block}
.field input,.field select,.field textarea,.inp{
  width:100%;box-sizing:border-box;border:1px solid var(--line);border-radius:var(--r-ctl);
  padding:10px 12px;font-size:13.5px;font-family:var(--font);outline:none;background:#fff;color:var(--ink);
}
.field textarea{resize:vertical}
.field input:focus,.field select:focus,.field textarea:focus,.inp:focus{border-color:var(--accent)}
.field input[type=checkbox]{width:auto}
.field .hint{font-size:11.5px;color:var(--faint);margin-top:5px}
/* 16px on touch, for the same reason as the block in shared/kit.css: under
   16px iOS zooms the page on focus and never zooms back. This one covers the
   sign-in card, which is not built out of the kit — and the sign-in card is
   the field almost everybody focuses first. */
@media (pointer:coarse){
  .field input,.field select,.field textarea,.inp{font-size:16px}
}
.formgrid{display:grid;gap:14px}
.formgrid.c2{grid-template-columns:1fr 1fr}
.formgrid.c3{grid-template-columns:1fr 1fr 1fr}
.searchbox{max-width:280px}

/* toggle switch */
.switch{width:40px;height:22px;border-radius:var(--r-pill);border:none;cursor:pointer;background:#E3E1DB;position:relative;transition:background .15s ease;flex-shrink:0}
.switch i{width:16px;height:16px;border-radius:50%;background:#fff;position:absolute;top:3px;left:3px;transition:left .15s ease}
.switch.on{background:var(--accent)}
.switch.on i{left:21px}

/* ---------------------------------------------------------------- rows */
.rowlist{display:flex;flex-direction:column}
.rowlist .r{display:grid;align-items:center;gap:12px;padding:12px 4px;border-top:1px solid var(--divider)}
.rowlist .r:first-child{border-top:none}
.rowlist .hdr{font-size:11.5px;color:var(--faint);font-weight:800;text-transform:uppercase;letter-spacing:.03em;padding-bottom:10px;border-top:none}
.linerow{display:flex;align-items:center;gap:14px;padding:14px 4px;border-top:1px solid var(--divider)}
.linerow:first-child{border-top:none}
.linerow .grow{flex:1;min-width:0}
.linerow b{font-size:13.5px;font-weight:700;display:block}
.linerow small{font-size:12px;color:var(--muted)}
.swatch{width:8px;height:8px;border-radius:50%;flex-shrink:0}
.swatch.sq{width:38px;height:38px;border-radius:10px}
.bar{height:7px;background:var(--divider);border-radius:var(--r-pill);overflow:hidden}
.bar i{display:block;height:100%;background:var(--accent);border-radius:var(--r-pill)}
.empty{color:var(--muted);font-size:13px;padding:22px 0;text-align:center}

/* --------------------------------------------------------- dashboard */
/* height:180px, not height:100%+flex:1: this card's wrapping div has no
   definite height of its own to resolve a percentage against, so the
   bars container's actual rendered height (204px, measured) ran past
   what the card itself sized to (238px) — 47px of bar+label bled out
   through the card's overflow:visible into whatever sat below it. Same
   family of bug as analytics.css's .a-bars fix, different mechanism
   (there it was flex-basis:0% beating `height`; here it's a percentage
   height with no definite ancestor) — both come down to never trusting
   a percentage/flex-relative height when nothing above it is definite.
   Confirmed by measuring computed vs card bounding rect, 2026-09-04. */
.bars{display:flex;align-items:flex-end;gap:10px;height:180px}
.bars .b{flex:1;display:flex;flex-direction:column;align-items:center;gap:8px;height:100%;justify-content:flex-end}
/* #E9E7E1 (the old fill) sits at ~1.15:1 contrast against the white card —
   effectively invisible at a glance, which is why a chart that was
   genuinely rendering real data still read to a member as "showing
   nothing." rgba(muted) reads as a clear, if quiet, bar. */
.bars .b i{display:block;width:100%;max-width:28px;border-radius:6px 6px 2px 2px;background:rgba(107,111,118,.45);transition:height .4s ease}
.bars .b i.on{background:var(--accent)}
.bars .b span{font-size:11px;color:var(--faint);font-weight:600}
.heat{display:grid;grid-template-columns:44px repeat(7,1fr);gap:4px}
.heat .hl{font-size:10.5px;color:var(--faint);font-weight:600;display:flex;align-items:center}
.heat .dl{text-align:center;font-size:11px;color:var(--faint);font-weight:700}
.heat .cell{aspect-ratio:1.4;border-radius:4px;background:var(--divider)}
.classline{display:flex;gap:12px;align-items:center}
.classline .tag{width:4px;align-self:stretch;border-radius:4px;background:var(--accent)}
.classline .grow{flex:1;min-width:0}
.classline .grow b{font-size:13.5px;font-weight:700;display:block}
.classline .grow span{font-size:12px;color:var(--muted)}
.classline .cap{font-size:12px;font-weight:700;color:var(--muted)}
.bigstat{font-family:var(--display);font-weight:700;font-size:24px}

/* --------------------------------------------------------- calendar */
/* overflow:auto here (not the page) is what lets the 820px-wide grid below
   scroll sideways on a phone instead of pushing the whole page off-screen —
   overflow:auto gives this box an automatic minimum width of 0, so a narrow
   viewport can only ever squeeze this box, never widen it. */
.calwrap{background:var(--card);border:1px solid var(--line);border-radius:var(--r-card);padding:18px;flex:1;min-height:0;overflow:auto;scrollbar-width:none;position:relative}
.cal{display:grid;grid-template-columns:56px repeat(7,minmax(96px,1fr));grid-auto-rows:minmax(17px,1fr);gap:2px;min-width:820px;min-height:100%}
/* Day headers and hour labels are stuck to the edges of .calwrap's own
   scrollport (its nearest scrolling ancestor), not the page, so they stay in
   view — and stay lined up with their column/row — through both the
   sideways scroll a phone needs and any vertical scroll a short window needs. */
.cal .dayhdr{text-align:center;font-size:12px;font-weight:800;align-self:center;grid-row:1;position:sticky;top:0;background:var(--card);z-index:2}
/* nowrap is deliberate: a wrapped hour label silently doubles the height of
   the row it labels, which is much harder to spot than a label that is
   visibly too wide for its gutter. */
.cal .hourlbl{font-size:10.5px;color:var(--faint);font-weight:700;text-align:right;padding-right:6px;grid-column:1;position:sticky;left:0;background:var(--card);z-index:1;white-space:nowrap}
/* The hour label spans both of its hour's rows, so it has to sit at the top
   of that pair rather than floating in the middle of them — the number names
   the line it is level with. */
.cal .hourlbl{align-self:start;padding-top:1px}
/* The grid is ruled in half hours (see calendarGrid in crm.js), so each hour
   is two cells with the 2px grid gap between them. Rounding only the outside
   corners is what keeps the pair reading as one hour with a hairline through
   it, rather than as two unrelated blocks. */
.cal .slot{background:var(--sunk)}
.cal .slot.oclock{border-radius:6px 6px 0 0}
.cal .slot.half{border-radius:0 0 6px 6px}
.cal .slot.over{background:var(--accent-soft);outline:2px dashed var(--accent)}
.cal .ev{
  background:var(--accent);border-radius:8px;padding:6px 8px;color:#fff;cursor:grab;
  display:flex;flex-direction:column;justify-content:center;overflow:hidden;
  box-shadow:0 2px 6px rgba(0,0,0,.12);
}
.cal .ev:active{cursor:grabbing}
.cal .ev b{font-size:11.5px;font-weight:800;line-height:1.2}
.cal .ev span{font-size:10.5px;opacity:.85}
/* A 30-minute class is one half-hour row tall — room for the name and
   nothing else. Hiding the coach line beats letting two lines overflow a
   block that cannot grow; the name is what identifies the class, and the
   coach is still on the tooltip and in the popup. */
.cal .ev.short{padding:2px 8px;justify-content:center}
.cal .ev.short b{font-size:10.5px}
.cal .ev.short span{display:none}
.cal.oneday .ev.short{padding:4px 10px}
.cal.oneday .ev.short b{font-size:12px}
/* PT sessions share the class block's geometry but are drawn outlined and
   hatched rather than filled — glanceable proof it is a 1:1, not a class,
   even before you read the label. Colour comes from the coach (colourFor),
   set inline per-event alongside the hatch itself; see crm.js renderCalendar. */
.cal .ev.pt{background-color:#fff;box-shadow:none;cursor:pointer}
.cal .ev.pt b,.cal .ev.pt span{opacity:1;color:inherit}
.cal .ev.pt span{opacity:.75}
/* An empty cell offers a 1:1 booking, so it has to look offerable. */
.cal .slot{cursor:pointer;transition:background .12s ease}
.cal .slot:hover{background:var(--accent-soft)}
.cal .hdrspacer{grid-row:1;grid-column:1;position:sticky;top:0;left:0;background:var(--card);z-index:3}

/* One-day mode (phones — see calendarGrid() in crm.js). A single column that
   fills the width, so a class block is a real, readable, tappable object
   instead of a 50px sliver. `min-width:0` undoes the 820px floor that exists
   only to make seven columns scroll sideways; there is nothing to scroll to
   here. */
.cal.oneday{grid-template-columns:46px 1fr;min-width:0}
.cal.oneday .dayhdr{font-size:12px;color:var(--muted)}
.cal.oneday .ev{padding:8px 10px;border-radius:10px}
.cal.oneday .ev b{font-size:13.5px}
.cal.oneday .ev span{font-size:12px}
.cal.oneday .hourlbl{font-size:11.5px}

/* The day picker above the one-day grid. Seven equal buttons — no scrolling,
   because a week is exactly seven and they fit. */
.daystrip{display:grid;grid-template-columns:repeat(7,1fr);gap:5px;margin-bottom:10px;flex:0 0 auto}
.daybtn{
  display:flex;flex-direction:column;align-items:center;gap:2px;
  padding:7px 0;min-height:48px;justify-content:center;
  border:1px solid var(--line);border-radius:10px;background:var(--card);
  font:inherit;color:var(--muted);cursor:pointer;
  transition:background .14s ease,color .14s ease,border-color .14s ease;
}
.daybtn .dow{font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:.03em}
.daybtn b{font-family:var(--display);font-size:15px;font-weight:700;line-height:1}
.daybtn.on{background:var(--accent);border-color:var(--accent);color:#fff}

/* The schedule filter above the grid. It wraps rather than scrolls: with a
   dozen coaches, a chip hidden off the end is a schedule you forget you hid. */
.calfilters{display:flex;flex-wrap:wrap;gap:12px 22px;align-items:flex-start;flex-shrink:0}
.calhost{display:flex;flex-direction:column;flex:1;min-height:0}

/* --------------------------------------------------------- pipeline */
.pipeline{display:grid;grid-template-columns:repeat(5,1fr);gap:14px;align-items:stretch;flex:1;min-height:0}
.col{background:var(--divider);border-radius:14px;padding:12px;min-height:0;display:flex;flex-direction:column}
.col.over{outline:2px dashed var(--accent);outline-offset:-2px}
.col .colhead{display:flex;align-items:center;justify-content:space-between;padding:4px 6px 12px}
.col .colhead .lbl{font-size:12.5px;font-weight:800;text-transform:uppercase;letter-spacing:.03em}
.col .colhead .n{background:#fff;border-radius:var(--r-pill);padding:2px 9px;font-size:11.5px;font-weight:800;color:var(--muted)}
.col .cards{display:flex;flex-direction:column;gap:8px;flex:1;min-height:0;overflow-y:auto;scrollbar-width:none}
.leadcard{background:#fff;border-radius:10px;padding:12px;cursor:grab;box-shadow:0 1px 2px rgba(0,0,0,.04)}
.leadcard:active{cursor:grabbing}
/* A dropped card is moved straight away and the write happens behind it, so
   this is the only feedback there is that something landed. */
.leadcard.landed{animation:cardLand .32s cubic-bezier(.2,.8,.3,1)}
@keyframes cardLand{
  0%{transform:scale(.96);box-shadow:0 8px 20px rgba(0,0,0,.14)}
  100%{transform:none;box-shadow:0 1px 2px rgba(0,0,0,.04)}
}
.leadcard b{font-size:13px;font-weight:700;display:block}
.leadcard .src{font-size:11.5px;color:var(--muted);margin-top:4px}
.leadcard .meta{display:flex;align-items:center;justify-content:space-between;margin-top:8px;font-size:11px;color:var(--faint);gap:8px}

/* ------------------------------------------------------ conversations */
.inbox{background:var(--card);border:1px solid var(--line);border-radius:var(--r-card);display:flex;overflow:hidden;flex:1;min-height:0}
.inbox .list{width:300px;flex-shrink:0;border-right:1px solid var(--line);overflow-y:auto}
.inbox .conv{display:flex;gap:10px;padding:14px 16px;cursor:pointer;border-bottom:1px solid var(--divider);background:none;border-left:none;border-right:none;border-top:none;width:100%;text-align:left;font:inherit}
.inbox .conv:hover{background:var(--sunk)}
.inbox .conv.active{background:var(--accent-soft)}
.inbox .conv .grow{flex:1;min-width:0}
.inbox .conv .top{display:flex;justify-content:space-between;gap:6px}
.inbox .conv .top b{font-size:13px;font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.inbox .conv .top span{font-size:10.5px;color:var(--faint);flex-shrink:0}
.inbox .conv .last{font-size:12px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-top:2px}
.inbox .unread{width:8px;height:8px;border-radius:50%;background:var(--accent);margin-top:5px;flex-shrink:0}
.inbox .thread{flex:1;display:flex;flex-direction:column;min-width:0}
.inbox .thread .thead{padding:16px 20px;border-bottom:1px solid var(--line);display:flex;align-items:center;gap:10px}
.inbox .thread .body{flex:1;overflow-y:auto;padding:20px;display:flex;flex-direction:column;gap:10px}
.bubble{max-width:65%;padding:10px 14px;border-radius:14px;font-size:13.5px;line-height:1.4;background:var(--divider);color:var(--ink);align-self:flex-start}
.bubble.mine{background:var(--accent);color:#fff;align-self:flex-end}
.bubble time{display:block;font-size:10px;opacity:.65;margin-top:4px}
.inbox .composer{padding:14px 16px;border-top:1px solid var(--line);display:flex;gap:10px}

/* ---------------------------------------------------------- modal */
.scrim{position:fixed;inset:0;min-height:100dvh;background:rgba(20,21,26,.45);display:flex;align-items:center;justify-content:center;z-index:50;padding:20px}
.modal{background:#fff;border-radius:var(--r-modal);padding:28px;width:440px;max-width:92vw;max-height:88dvh;display:flex;flex-direction:column;overflow:hidden;box-shadow:0 24px 60px rgba(0,0,0,.25);animation:modalIn .18s ease}
.modal.wide{width:620px}
/* The app's own confirm (ui.js confirmDialog). Stacks ABOVE .scrim rather than
   replacing it, so a "Delete" raised from inside an open form leaves the form
   underneath — and still there if you cancel. */
.confirm-scrim{z-index:60;background:rgba(20,21,26,.55)}
.confirm-modal{width:400px;padding:24px}
.confirm-modal h2{margin-bottom:10px}
.confirm-body{display:flex;flex-direction:column;gap:9px;font-size:13.5px;line-height:1.55;color:var(--muted)}
.confirm-body p{margin:0}
.confirm-modal .foot{margin-top:22px;border-top:none;padding-top:0}
/* Type-to-confirm, for deletes with no undo. */
.confirm-type{display:flex;flex-direction:column;gap:6px;margin-top:16px}
.confirm-type label{font-size:12px;font-weight:700;color:var(--muted)}
.confirm-type input{
  width:100%;font:inherit;font-size:14px;font-weight:700;letter-spacing:.5px;
  padding:10px 12px;border:1px solid var(--line);border-radius:10px;
  background:var(--paper);color:var(--ink);
}
.confirm-type input:focus{outline:none;border-color:var(--red)}
.confirm-modal .btn.danger-solid:disabled{opacity:.45;cursor:not-allowed}
.confirm-modal .btn.danger-solid:disabled:hover{background:var(--red)}
@media (pointer:coarse){.confirm-type input{font-size:16px}}
/* A filled red button, not the borderless `.btn.danger` used for "Delete" in a
   form footer: this one IS the action, and it needs the weight to say so. */
.btn.danger-solid{background:var(--red);color:#fff;border:none;font-weight:700}
.btn.danger-solid:hover{background:#c92a15}
.modal h2{font-family:var(--display);font-weight:700;font-size:18px;margin:0 0 18px;flex-shrink:0}
.modal .body{display:flex;flex-direction:column;gap:14px;min-height:0;overflow-y:auto;scrollbar-width:none;margin:0 -4px;padding:0 4px}
/* The footer is a normal child of the scrolling .body (openModal in ui.js
   appends it there), so on a long form it would otherwise scroll away with
   the rest of the content. `position:sticky;bottom` keeps it pinned to the
   bottom of .body's own scrollport instead — reachable without hunting for
   it, and never past the edge of the 88dvh modal cap into the browser chrome. */
/* The divider is a plain hairline rather than the soft upward shadow it used
   to be. The shadow only made sense while content was scrolling under it; on
   a form that fits, it rendered as a short grey smear floating above the
   buttons that read as a rendering fault. A border is honest in both cases:
   this is the action row, and it is separated from the form. */
.modal .foot{
  display:flex;justify-content:flex-end;align-items:center;gap:10px;margin-top:20px;flex-shrink:0;
  position:sticky;bottom:-1px;background:#fff;padding-top:14px;
  border-top:1px solid var(--divider);
}
.modal .foot .spread{margin-right:auto}
.swatches{display:flex;gap:8px}
.swatches button{width:28px;height:28px;border-radius:50%;border:2px solid #fff;cursor:pointer;padding:0}
.swatches button.on{border-color:var(--ink)}

/* ---------------------------------------------------------- toast */
.toast{
  position:fixed;
  bottom:calc(26px + env(safe-area-inset-bottom));
  right:calc(26px + env(safe-area-inset-right));
  background:var(--ink);color:#fff;
  padding:14px 20px;border-radius:12px;font-size:13.5px;font-weight:600;
  box-shadow:0 12px 28px rgba(0,0,0,.25);animation:toastIn .25s ease;z-index:60;max-width:min(420px,90vw);
}
.toast.bad{background:var(--red)}

/* ---------------------------------------------------------- sign-in */
/* Pinned rather than measured, same as .shell — this is the other
   full-screen root, and it had the identical band of bare <html> under it. */
.authwrap{position:fixed;top:0;left:0;right:0;bottom:0;min-height:100dvh;display:flex;align-items:center;justify-content:center;padding:clamp(12px,3vh,24px);background:var(--rail);overflow:hidden}
html[data-kb] .authwrap{bottom:auto;top:var(--app-top,0);height:var(--app-h,100dvh);min-height:0}
.authcard{background:#fff;border-radius:var(--r-modal);padding:clamp(20px,3.4vh,34px);width:400px;max-width:100%;max-height:100%;overflow-y:auto;scrollbar-width:none;box-shadow:0 24px 60px rgba(0,0,0,.3)}
.authcard .brand{padding:0 0 clamp(14px,2.4vh,22px)}
.authcard .brand .mark{width:44px;height:44px}
.authcard h1{font-family:var(--display);font-weight:700;font-size:22px;margin:0 0 4px}
.authcard form{display:flex;flex-direction:column;gap:clamp(10px,1.8vh,14px);margin-top:clamp(12px,2.2vh,20px)}
.authcard .btn{width:100%}
.err{color:var(--red);font-size:12.5px;min-height:1.1em;font-weight:600}
.authcard .alt{margin-top:14px;font-size:12.5px;color:var(--muted);text-align:center}
.authcard .alt button{background:none;border:none;font:inherit;color:var(--accent);font-weight:700;cursor:pointer;text-decoration:underline;padding:0}

/* ---------------------------------------------------------- responsive */
@media (max-width:1180px){
  .grid.k4{grid-template-columns:repeat(2,1fr)}
  .grid.k3{grid-template-columns:repeat(2,1fr)}
  .grid.wide-left,.grid.wide-right{grid-template-columns:1fr}
  .pipeline{grid-template-columns:repeat(3,1fr)}
}
/* Below 861px the rail stops being a strip of unlabelled icons (the actual
   complaint this section exists to fix) and becomes a labelled, full-height
   drawer that slides in from the left over a slim top bar. The drawer is
   driven entirely by a class (.sidebar.open) and `transform`, never
   `display` — see the note on [hidden] at the top of this file for why that
   distinction matters here: fighting `display` on this element has bitten
   this codebase twice already. Everything that opens/closes it lives in
   crm.js next to go()/NAVIGATION, since closing it on navigation is a
   go()-level concern, not a CSS one. */
@media (max-width:860px){
  .shell{flex-direction:column}

  /* The rail is already the off-canvas drawer down here — "collapse it
     further" isn't a coherent second idea on top of that. */
  .railcollapsebtn{display:none}

  .topbar{
    display:flex;align-items:center;justify-content:space-between;gap:12px;
    flex-shrink:0;background:var(--rail);position:relative;
    padding:10px 14px;padding-top:calc(10px + env(safe-area-inset-top));
  }
  .hamburger{
    width:44px;height:44px;flex-shrink:0;display:flex;align-items:center;justify-content:center;
    background:none;border:none;color:#fff;cursor:pointer;border-radius:var(--r-ctl);
    transition:background .15s ease;
  }
  .hamburger:hover{background:var(--rail-hover)}
  .hamburger svg{display:block}
  /* Centred, by balancing the row rather than by absolute positioning.
     Removing the account avatar left the bar with two children, so
     space-between pinned the badge and the club's name hard against the right
     edge — a masthead in the corner. The avatar element stays in the layout as
     an invisible 44px block, exactly matching the hamburger opposite it, and
     the brand takes the whole middle and centres inside it. Symmetric ends
     mean the middle is the true centre of the screen, and it survives the
     safe-area padding and a notch without any calc() in sight. */
  .topbar-brand{
    flex:1;display:flex;align-items:center;justify-content:center;
    gap:8px;min-width:0;overflow:hidden;
  }
  .topbar-brand .mark{width:22px;height:22px;flex-shrink:0;object-fit:contain}
  .topbar-brand .name{
    font-family:var(--display);font-weight:700;color:#fff;font-size:13px;
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  }
  /* The account avatar is gone from the top bar. You know who you are signed
     in as, and on a 360px phone it was taking space off the wordmark to say
     so. `visibility:hidden` rather than `display:none`, because the element
     now earns its keep as the counterweight to the hamburger — see
     .topbar-brand above. It is aria-hidden in index.html and invisible here,
     so it is gone in every sense that matters while still holding 44px open. */
  .topbar .avatar{
    visibility:hidden;flex:0 0 44px;width:44px;height:1px;min-width:0;
    background:none;border:none;box-shadow:none;padding:0;margin:0;
  }

  /* Off-canvas: fixed, full height, translated fully out of view. Opening it
     is `.open` adding translateX(0) — no layout property involved, so there
     is nothing here for a `display` rule anywhere else in this file to fight. */
  .sidebar{
    position:fixed;top:0;left:0;bottom:0;z-index:40;
    /* Pinned to BOTH edges, with no height of its own. It used to be
       top:0 + height:var(--app-h), and an --app-h that measured even
       slightly short of the screen (an installed PWA reporting the visual
       viewport without the home-indicator inset) left the drawer ending
       above the bottom of the screen with bare <html> — painted --paper,
       i.e. white — showing under a full-height dark rail. Fixed positioning
       resolves against the layout viewport, which with viewport-fit=cover
       is the whole screen, so top+bottom is the one way to say "all of it"
       that cannot be short. viewport.js has been fixed too (see its
       display-mode note); this is the rule that makes it not matter.

       `height:auto` is load-bearing. The base .sidebar rule above sets
       height:var(--app-h,100dvh), and with top:0 also set an explicit height
       beats bottom:0 outright — so simply not repeating the height here
       would leave the base one in force and change nothing at all. */
    width:min(84vw,320px);height:auto;min-height:100dvh;
    transform:translateX(-100%);
    transition:transform .26s cubic-bezier(.2,.8,.3,1);
    box-shadow:0 0 0 1px transparent;
    /* The drawer is fixed to top:0, and with viewport-fit=cover that is the
       true top of the screen — underneath the clock and the notch. The base
       .sidebar rule's padding is a plain clamp() with no safe-area term, so
       the first nav item sat level with the status bar. The insets are added
       here rather than in the base rule because on a desktop they are always
       zero and the rail is not against the screen edge anyway. */
    padding-top:calc(clamp(12px,2.2vh,24px) + env(safe-area-inset-top));
    padding-bottom:calc(clamp(10px,1.6vh,18px) + env(safe-area-inset-bottom));
    padding-left:calc(16px + env(safe-area-inset-left));
  }
  .sidebar.open{transform:translateX(0);box-shadow:24px 0 48px rgba(0,0,0,.22)}
  /* Labels are the point of the drawer — the whole reason a horizontal strip
     of bare icons doesn't work is that there's nowhere to put them. */
  .railbtn span.lbl{display:inline}
  .railbtn{min-height:44px}

  .nav-scrim{
    position:fixed;inset:0;min-height:100dvh;z-index:30;background:rgba(20,21,26,.45);
    opacity:0;pointer-events:none;transition:opacity .26s cubic-bezier(.2,.8,.3,1);
  }
  .nav-scrim.show{opacity:1;pointer-events:auto}

  /* A modal is centred in a full-screen scrim, so on a tall phone it can sit
     under the notch or over the home indicator. The insets keep it clear of
     both without moving it on any device that has neither. */
  .scrim{
    padding:calc(20px + env(safe-area-inset-top)) calc(20px + env(safe-area-inset-right))
            calc(20px + env(safe-area-inset-bottom)) calc(20px + env(safe-area-inset-left));
  }

  .page{padding:16px 14px calc(24px + env(safe-area-inset-bottom))}
  .grid.k4,.grid.k3,.grid.k2{grid-template-columns:1fr}
  .grid{gap:20px}
  .bars{min-height:140px;padding-bottom:8px}
  .card{padding:16px}
  .card.tight{padding:14px}
  .pagehead h1{font-size:21px}

  /* ---- The page scrolls, not a box inside it ----
     On a desktop the chrome is fixed and one inner .pane scrolls, so the page
     header is always in view. On a phone that header is a permanent band
     eating ~60px of a 700px screen, and you cannot scroll it away. Here the
     whole .page is the scroller and the header goes with it: `overflow:hidden`
     becomes `auto` on the page, and every inner scroller is released to its
     natural height so nothing double-scrolls (a scroller inside a scroller is
     how you get a list that traps the flick you meant for the page). */
  .page{overflow-y:auto;overflow-x:hidden;overscroll-behavior:contain;-webkit-overflow-scrolling:touch}
  .pane{overflow:visible;flex:none;min-height:auto}
  .pagebody{min-height:auto}
  /* Sideways scrolling is the one kind that stays: a wide table or the
     timetable's own grid still needs it, and it does not fight a vertical flick. */
  .pane-x{overflow-x:auto}

  /* ---- Leads pipeline: a board you swipe, not five stacked lists ----
     One column per row turned a five-stage pipeline into a very long page
     where "how many are in Trial?" meant scrolling. Back to five columns,
     side by side, scrolled horizontally — which is what a kanban board is.
     Each column is a fixed 78vw so the next one peeks in at the edge and it
     is obvious there is more to the right. */
  .pipeline{
    display:flex;grid-template-columns:none;gap:12px;
    overflow-x:auto;overflow-y:hidden;
    scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch;
    padding-bottom:6px;
  }
  .pipeline .col{
    flex:0 0 min(78vw,300px);scroll-snap-align:start;
    height:min(62vh,520px);min-height:0;
  }
  .col .cards{overflow-y:auto}

  .inbox{flex-direction:column;height:min(78vh,640px)}
  .inbox .list{width:100%;max-height:220px;border-right:none;border-bottom:1px solid var(--line)}
  .formgrid.c2,.formgrid.c3{grid-template-columns:1fr}

  /* The timetable keeps a real height of its own — it is a grid, not a list,
     and letting it grow to its natural 16-hour height inside a scrolling page
     would mean scrolling past a thousand pixels of empty 6am. */
  .calwrap{height:min(70vh,620px);flex:none;padding:12px}
  .calhost{flex:none}

  /* Modals: full-width sheets rather than a 440px card with 8px of margin. */
  .scrim{padding:10px;align-items:flex-end}
  .modal,.modal.wide{width:100%;max-width:100%;padding:20px 18px;max-height:92dvh;border-radius:18px 18px 12px 12px}
  .modal h2{font-size:16.5px;margin-bottom:14px;line-height:1.3}
  /* A stacked footer beats three buttons squeezed onto one line, where the
     middle one ends up 40px wide and the labels wrap mid-word. */
  .modal .foot{flex-wrap:wrap;gap:8px}
  .modal .foot .btn{flex:1 1 auto;min-width:96px;justify-content:center;text-align:center}
  .modal .foot .spread{flex-basis:100%;margin-right:0;order:1}

  /* Buttons in a page header wrap onto their own line and share it evenly,
     instead of the last one hanging off the right edge. */
  .pagehead{gap:12px}
  .pagehead .acts{width:100%;gap:8px}
  /* Every action, not just .btn — a page head can hold a search box or a
     week-stepper segment, and those were staying at their desktop width and
     leaving a ragged half-empty row. */
  .pagehead .acts > *{flex:1 1 auto}
  .searchbox{max-width:none}

  /* ---- Tables become records ----
     A .rowlist row is a CSS grid whose column widths are set inline, per
     screen, in JS ("2.2fr 2.2fr 1.4fr 1.3fr 1.1fr 1fr" for Members). Six
     columns in 360px gives each one about 50px, so every cell was an
     ellipsis and the header above it was a row of clipped words. There is no
     column width that fixes that — six columns is simply more than fits.
     So on a phone a row stops being a row: the name goes on its own line and
     the rest of the fields wrap underneath it as small print. `!important`
     is the one thing that beats the inline grid-template-columns; it is
     deliberate, not a shortcut.
     The header row goes too — it labels columns that no longer exist. */
  /* The header labels columns that no longer exist. Written as `.r.hdr`
     rather than `.hdr`: the rule below also sets display with !important and
     at equal specificity the later rule would win — which it did, leaving a
     row of column headings above a layout that has no columns. */
  .rowlist .r.hdr{display:none !important}
  .rowlist .r{
    display:flex !important;flex-wrap:wrap;align-items:baseline;
    gap:3px 8px;padding:12px 2px;
  }
  .rowlist .r > *{
    min-width:0;max-width:100%;font-size:12px;
    overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  }
  /* The name. Full width, so nothing ever shares a line with it. */
  .rowlist .r > *:first-child{flex:1 0 100%;font-size:13.5px;font-weight:700}
  /* A separator between the small-print fields, because without one
     "111macifawcett@gmail.com — Unassigned portal" reads as one long string.
     From the third child on: the second is the first field on the line and
     wants no leading dot. */
  .rowlist .r > *:nth-child(n+3)::before{content:'· ';color:var(--faint)}
  /* Pills keep their own type size — they are the status, and shrinking them
     to match the small print is what made them stop reading as pills. */
  .rowlist .r > .pill{font-size:11px}
  /* And the avatar goes. In a stacked record it is a 34px circle of initials
     sitting above a name that already says the same thing. */
  .rowlist .r .avatar{display:none}

  /* Touch targets: nothing interactive under 44×44. Most of these keep their
     compact visual size and get an invisible hit-slop instead — actually
     growing them would make dense rows (a roster's Check-in/Cancel pair, the
     week segment control) look oversized for what they are. */
  .btn.sm{min-height:44px;display:inline-flex;align-items:center}
  .segment{padding:6px}
  .segment button{min-height:38px}
  .switch{position:relative}
  .switch::after{content:'';position:absolute;inset:-11px}
  .btn.link,.btn.danger{padding:11px 0}
}

/* =====================================================================
   Settings tabs (added for settings.js — do not remove, appended only,
   no existing rule above this block was changed).
   ===================================================================== */
/* Reuses .segment (a pill-style segmented control already defined above)
   for the Settings tab strip; this just lets it wrap on narrow widths and
   keeps tab labels from breaking mid-word. */
.segment.settings-tabs{flex-wrap:wrap;margin-bottom:4px}
.segment.settings-tabs button{white-space:nowrap}
/* kit.js controls (web/shared/kit.css) sit inside a CSS grid via kRow();
   grid items stretch to the row's full height by default, which vertically
   centers a short kStepper oddly next to a taller kField. Top-align instead. */
.kgrid > .kit{align-self:start}
/* The Settings tabs must each fit the viewport with no scrolling (see
   settings.js). Scoped to .settings-panel only, so nothing else that uses
   .card / .kgrid / .kf elsewhere in the app is affected — these just trim
   the vertical rhythm enough for six sections' worth of panels to fit. */
.settings-panel .card{padding:14px 18px}
.settings-panel .card-head{margin-bottom:8px}
.settings-panel .kgrid{gap:10px}
/* The compact variant of the kit field. These three numbers are a set: with
   15px of top padding the value's first line starts at y=16.5, so the lifted
   label (10.5px tall after the .75 scale) has to rest at y=13 to clear it.
   Change one and re-check the other two — see the geometry note in kit.css.
   .ktrig is listed alongside .kf-in because a select in Settings has to be
   the same height as the text field next to it. */
.settings-panel .kf .kf-in,.settings-panel .kf .ktrig{padding:15px 13px 6px}
.settings-panel .kf .kf-lab{top:13px}
/* Class types / rooms lists can grow past what fits — they get their own
   internal scroller instead of pushing the tab body taller than the page. */
.settings-panel .cat-list{max-height:180px;overflow-y:auto;scrollbar-width:none}
.settings-panel .cat-list::-webkit-scrollbar{width:6px}
.settings-panel .cat-list:hover::-webkit-scrollbar-thumb{background:#D9D5CD}

/* Motion is decoration everywhere it appears here — the progress hairline,
   the landing card, the modal. None of it carries information, so all of it
   goes when the operating system asks for less movement. */
@media (prefers-reduced-motion:reduce){
  .main.busy::before,.leadcard.landed{animation:none}
  .main.busy::before{background:var(--accent);opacity:.5}
  /* The drawer's transform slide and the scrim's cross-fade only exist under
     max-width:860px, but disabling the transition unconditionally here is
     harmless above that width (the property is simply never exercised) and
     keeps every "turn animation off" rule in the one place. */
  .sidebar,.nav-scrim{transition:none}
}

/* =====================================================================
   Dashboard rollup additions (SPEC-STRIPE.md §7) — appended, no rule
   above this point was changed. A KPI tile that links through to the
   screen it came from (kpiTile()'s optional onClick in crm.js) needs to
   read as tappable and to carry a visible keyboard focus ring, since it's
   a plain div with role="button" rather than a native control.
   ===================================================================== */
.kpi[role="button"]{transition:transform .15s ease,box-shadow .15s ease}
.kpi[role="button"]:hover{transform:translateY(-2px);box-shadow:0 8px 20px rgba(0,0,0,.08)}
.kpi[role="button"]:focus-visible{outline:2px solid var(--accent);outline-offset:2px}

@media (prefers-reduced-motion:reduce){
  .kpi[role="button"]{transition:none}
  .kpi[role="button"]:hover{transform:none}
}

/* =====================================================================
   Native <select> — themed.

   ui.js's field(…,'select',…) renders a real <select> in 14 places. The
   browser's default is a square grey control that looks nothing like the
   rest of the app, so `appearance:none` strips the platform chrome and the
   caret is redrawn as a background SVG (a data: URI — allowed by the CSP's
   img-src 'self' data:).

   The OPEN list is drawn by the operating system and genuinely cannot be
   styled; `accent-color` is the only lever browsers give us over the
   highlight, so at least it is the club's green rather than system blue.
   Anywhere the popup itself has to be on-theme, use kSelect from
   shared/kit.js instead — it is a real DOM listbox, not a native one.
   ===================================================================== */
.field select,select.inp{
  appearance:none;-webkit-appearance:none;-moz-appearance:none;
  padding-right:36px;cursor:pointer;
  background-color:#fff;
  background-image:url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='%236B6F76' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat:no-repeat;
  background-position:right 13px center;
  accent-color:var(--accent);
  transition:border-color .15s ease,box-shadow .15s ease;
}
.field select:hover{border-color:#DCD8D0}
.field select:focus{border-color:var(--accent);box-shadow:0 0 0 3.5px var(--accent-soft)}
.field select:disabled{background-color:var(--sunk);cursor:default;opacity:.65}
/* Firefox keeps a focus ring inside the control; ours is on the border. */
.field select:-moz-focusring{color:transparent;text-shadow:0 0 0 var(--ink)}
@media (prefers-reduced-motion:reduce){.field select{transition:none}}

/* -------------------------------------------------------------------
   INSTALLED ONLY: back to the large viewport.

   Every full-screen overlay above is sized in dvh so it cannot overshoot a
   browser tab's toolbar. Installed, there is no toolbar and the opposite
   problem applies: the layout viewport can stop above the home indicator,
   and an overlay sized to it ends in a white strip. lvh reaches the real
   screen edge. This is the same split html/body makes at the top of the
   file, applied to the things that cover the screen.
   ------------------------------------------------------------------- */
@media (display-mode:standalone),(display-mode:fullscreen),(display-mode:minimal-ui){
  .shell,.scrim,.authwrap{min-height:100lvh}
}
@media (display-mode:standalone) and (max-width:860px),
       (display-mode:fullscreen) and (max-width:860px),
       (display-mode:minimal-ui) and (max-width:860px){
  .sidebar,.nav-scrim{min-height:100lvh}
}
