:root {
  --bg: #0d1117;
  --bg-alt: #131a24;
  --surface: #171f2b;
  --surface-2: #1d2733;
  --border: #263140;
  --text: #e6edf5;
  --muted: #8b9bb0;
  --brand: #f97316;
  --brand-soft: rgba(249, 115, 22, 0.14);
  --brand-glow: rgba(249, 115, 22, 0.15);
  --green: #34d399;
  --red: #f87171;
  --amber: #fbbf24;
  --blue: #60a5fa;
  --violet: #a78bfa;
  --radius: 12px;
  --radius-sm: 9px;
  --radius-lg: 16px;

  /*
   * Elevation in three steps rather than one, so a resting card, a lifted card
   * and a modal are distinguishable instead of all wearing the same drop
   * shadow. Negative spreads keep the shadow tight under the element and let
   * the blur fall away, which is what stops a panel looking like it is floating
   * on a grey smudge.
   */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.28), 0 2px 6px -2px rgba(0, 0, 0, 0.22);
  --shadow: 0 10px 30px -5px rgba(0, 0, 0, 0.4);
  --shadow-lg: 0 24px 60px -12px rgba(0, 0, 0, 0.55);
  /*
   * The micro-border: a 1px top highlight, inset, that reads as light catching
   * the upper edge of a raised surface. It is the whole difference between a
   * flat rectangle and a panel, and it has to invert in light mode — a white
   * highlight on white is invisible, so light mode makes it a faint shade.
   */
  --hairline: rgba(255, 255, 255, 0.07);
  /* Focus ring, as one token so every control's ring matches the brand. */
  --ring: 0 0 0 3px var(--brand-glow);

  /*
   * Interface font. System-first and entirely local: the app runs offline from
   * a service worker, so a webfont would either block first paint or arrive
   * only for users who happen to be online. 'Inter' is honoured if the desk
   * machine has it installed, which many do.
   */
  --font-sans: 'Inter var', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI Variable Text', 'Segoe UI',
    Roboto, 'Helvetica Neue', Arial, sans-serif;

  color-scheme: dark;

  /*
   * Notch and home-indicator clearance, used wherever the app paints to an
   * edge. Non-zero only in an installed window on a device with insets —
   * index.html asks for viewport-fit=cover and iOS draws under a translucent
   * status bar, so without these the topbar would sit beneath the clock. The
   * 0px fallbacks keep every calc() valid in browsers that don't set them.
   */
  --safe-top: env(safe-area-inset-top, 0px);
  --safe-right: env(safe-area-inset-right, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --safe-left: env(safe-area-inset-left, 0px);
}

/* --------------------------------------------- Library Management Themes */

/* Default Library Aesthetic: Modern Emerald & Abyssal Slate */
body[data-vertical='library'],
[data-brand='library'] {
  --bg: #081017;
  --bg-alt: #0d1722;
  --surface: #13202e;
  --surface-2: #192a3c;
  --border: #22384e;
  --text: #f0fdfa;
  --muted: #799bb0;
  --brand: #10b981;
  --brand-soft: rgba(16, 185, 129, 0.15);
  --brand-glow: rgba(16, 185, 129, 0.22);
  --green: #34d399;
  --red: #f87171;
  --amber: #fbbf24;
  --blue: #38bdf8;
  --violet: #a78bfa;
}

/* Preset 2: Sapphire Night */
body[data-vertical='library'][data-theme='sapphire'],
[data-brand='library'][data-theme='sapphire'] {
  --bg: #070c18;
  --bg-alt: #0d1428;
  --surface: #131c36;
  --surface-2: #1b2647;
  --border: #24355c;
  --text: #f0f6ff;
  --muted: #7b94c4;
  --brand: #38bdf8;
  --brand-soft: rgba(56, 189, 248, 0.15);
  --brand-glow: rgba(56, 189, 248, 0.22);
}

/* Preset 3: Nordic Violet */
body[data-vertical='library'][data-theme='violet'],
[data-brand='library'][data-theme='violet'] {
  --bg: #0d0a18;
  --bg-alt: #131024;
  --surface: #1c1733;
  --surface-2: #241f42;
  --border: #332b57;
  --text: #f5f3ff;
  --muted: #958abf;
  --brand: #a78bfa;
  --brand-soft: rgba(167, 139, 250, 0.16);
  --brand-glow: rgba(167, 139, 250, 0.24);
}

/* Preset 4: Dark Mocha */
body[data-vertical='library'][data-theme='mocha'],
[data-brand='library'][data-theme='mocha'] {
  --bg: #140d0a;
  --bg-alt: #1c130e;
  --surface: #261a14;
  --surface-2: #33231a;
  --border: #473226;
  --text: #fff8f0;
  --muted: #ba9d8a;
  --brand: #f59e0b;
  --brand-soft: rgba(245, 158, 11, 0.16);
  --brand-glow: rgba(245, 158, 11, 0.24);
}

/* Enhancements for Library Mode UI elements */
body[data-vertical='library'] .btn.primary,
[data-brand='library'] .btn.primary {
  color: #051a14;
  font-weight: 700;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22), 0 4px 14px var(--brand-glow);
}
body[data-vertical='library'][data-theme='sapphire'] .btn.primary,
[data-brand='library'][data-theme='sapphire'] .btn.primary {
  color: #041424;
}
body[data-vertical='library'][data-theme='violet'] .btn.primary,
[data-brand='library'][data-theme='violet'] .btn.primary {
  color: #140826;
}
body[data-vertical='library'][data-theme='mocha'] .btn.primary,
[data-brand='library'][data-theme='mocha'] .btn.primary {
  color: #241202;
}

/* The active-item accent and the topbar's glass are both mixed from --bg and
   --brand (see .nav-link.active and .topbar), so every preset and both modes
   get them without a per-theme override here. */

/* ------------------------------------------------ GymBook Management Themes */

/* Preset 1: Flame Orange (Default GymBook Brand) */
body[data-vertical='gym'],
body[data-vertical='gym'][data-theme='flame'] {
  --brand: #f97316;
  --brand-soft: rgba(249, 115, 22, 0.14);
  --brand-glow: rgba(249, 115, 22, 0.15);
  --bg: #0d1117;
  --bg-alt: #131a24;
  --surface: #171f2b;
  --surface-2: #1d2733;
  --border: #263140;
  --text: #e6edf5;
  --muted: #8b9bb0;
}

/* Preset 2: Crimson Power */
body[data-vertical='gym'][data-theme='crimson'] {
  --brand: #ef4444;
  --brand-soft: rgba(239, 68, 68, 0.16);
  --brand-glow: rgba(239, 68, 68, 0.24);
  --bg: #120909;
  --bg-alt: #1c0e0e;
  --surface: #261414;
  --surface-2: #331b1b;
  --border: #472323;
  --text: #fff5f5;
  --muted: #ba8a8a;
}

/* Preset 3: Cyber Volt Lime */
body[data-vertical='gym'][data-theme='cyber'] {
  --brand: #84cc16;
  --brand-soft: rgba(132, 204, 22, 0.16);
  --brand-glow: rgba(132, 204, 22, 0.24);
  --bg: #0a1207;
  --bg-alt: #101b0b;
  --surface: #182811;
  --surface-2: #213617;
  --border: #304c22;
  --text: #f7fce8;
  --muted: #93ba84;
}

/* Preset 4: Ultramarine Blue */
body[data-vertical='gym'][data-theme='ultramarine'] {
  --brand: #3b82f6;
  --brand-soft: rgba(59, 130, 246, 0.16);
  --brand-glow: rgba(59, 130, 246, 0.24);
  --bg: #090e1a;
  --bg-alt: #0e1628;
  --surface: #152038;
  --surface-2: #1d2c4d;
  --border: #283e6b;
  --text: #f0f5ff;
  --muted: #829bc7;
}

/* Enhancements for Gym Mode UI elements */
body[data-vertical='gym'] .btn.primary {
  color: #1a1005;
  font-weight: 700;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.22), 0 4px 14px var(--brand-glow);
}
body[data-vertical='gym'][data-theme='crimson'] .btn.primary {
  color: #240404;
}
body[data-vertical='gym'][data-theme='cyber'] .btn.primary {
  color: #101c03;
}
body[data-vertical='gym'][data-theme='ultramarine'] .btn.primary {
  color: #041026;
}

* { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 15px/1.55 var(--font-sans);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* Installed-app feel on touch devices: no grey flash on every tap, and no
     iOS "text inflation" resizing body copy when a phone turns landscape. */
  -webkit-tap-highlight-color: transparent;
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  /* A long email, note or QR token must wrap rather than widen the page —
     without this a single unbroken string forces sideways scroll on a phone. */
  overflow-wrap: break-word;
}

/* Headings tighten as they grow: at display sizes the default tracking reads as
   gappy, and this is most of what separates a modern product page from a
   default-stylesheet one. */
h1, h2, h3, h4, .stat .value, .dash-greeting { letter-spacing: -0.015em; }
h1, h2 { font-weight: 700; }
h3, h4 { font-weight: 600; }

a { color: var(--brand); text-decoration: none; }
a:hover { text-decoration: underline; }

/* Every icon from renderIcon() in js/ui.js. Sized by its own width/height
   attributes; these rules only stop flex containers from stretching or
   squashing them, and hand the stroke its colour from the parent. */
.svg-icon { flex: none; display: block; stroke: currentColor; }

/* The doubled height/min-height declarations through this file are a fallback
   pair, not a mistake: mobile browser chrome makes 100vh taller than what you
   can actually see, so dvh takes over wherever it's understood. */
.app-loading { display: grid; place-items: center; height: 100vh; height: 100dvh; color: var(--muted); }

/* ------------------------------------------------------------------- login */

.login {
  display: grid;
  place-items: center;
  min-height: 100vh;
  min-height: 100dvh;
  padding: calc(24px + var(--safe-top)) calc(24px + var(--safe-right)) calc(24px + var(--safe-bottom))
    calc(24px + var(--safe-left));
  background: radial-gradient(1000px 600px at 15% -10%, var(--brand-glow), transparent 60%), var(--bg);
}
.login-card {
  width: min(400px, 100%);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 32px;
  box-shadow: var(--shadow-lg), inset 0 1px 0 var(--hairline);
}
.login-card h1 { margin: 0 0 4px; font-size: 26px; display: flex; align-items: center; gap: 10px; }
.login-logo-img { width: 42px; height: 42px; border-radius: 10px; object-fit: cover; border: 1px solid var(--border); flex-shrink: 0; }
/* Stand-in for a gym that has not uploaded a logo — the same tinted tile the
   sidebar brand uses, at login size. */
.login-logo-mark {
  width: 42px; height: 42px; border-radius: 12px;
  background: var(--brand-soft); color: var(--brand);
  border: 1px solid color-mix(in srgb, var(--brand) 22%, transparent);
  box-shadow: inset 0 1px 0 var(--hairline);
  display: grid; place-items: center; flex-shrink: 0;
}
.login-card p.sub { margin: 0 0 24px; color: var(--muted); font-size: 14px; }

/* ------------------------------------------------------------------ layout */

.shell { display: grid; grid-template-columns: 240px 1fr; min-height: 100vh; min-height: 100dvh; }

.sidebar {
  background: var(--bg-alt);
  border-right: 1px solid var(--border);
  padding: calc(20px + var(--safe-top)) 14px calc(20px + var(--safe-bottom)) calc(14px + var(--safe-left));
  display: flex;
  flex-direction: column;
  gap: 4px;
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100dvh;
  overflow-y: auto;
}
.brand { display: flex; align-items: center; gap: 10px; padding: 4px 8px 20px; font-weight: 700; font-size: 19px; letter-spacing: -0.02em; }
.brand .logo {
  width: 34px; height: 34px; border-radius: 10px;
  background: var(--brand-soft); color: var(--brand);
  display: grid; place-items: center; font-size: 18px;
  border: 1px solid color-mix(in srgb, var(--brand) 22%, transparent);
  box-shadow: inset 0 1px 0 var(--hairline), 0 2px 10px -4px var(--brand-glow);
  overflow: hidden;
  flex-shrink: 0;
}
.brand .logo .logo-img {
  width: 100%; height: 100%; object-fit: cover; border-radius: 10px;
}
.settings-logo-container {
  display: flex; align-items: center; gap: 16px;
  margin-bottom: 20px; padding-bottom: 16px;
  border-bottom: 1px solid var(--border);
}
.settings-logo-preview {
  width: 64px; height: 64px; border-radius: 12px;
  background: var(--brand-soft); color: var(--brand);
  display: grid; place-items: center; font-size: 30px;
  overflow: hidden; border: 1px solid var(--border);
  flex-shrink: 0;
}
.settings-logo-preview img {
  width: 100%; height: 100%; object-fit: cover;
}
.nav-link {
  position: relative;
  display: flex; align-items: center; gap: 10px;
  padding: 9px 12px; border-radius: var(--radius-sm);
  color: var(--muted); font-weight: 500; font-size: 14px;
  transition: background 0.15s ease, color 0.15s ease;
}
.nav-link:hover { background: var(--surface); color: var(--text); text-decoration: none; }
/*
 * The active item: a brand wash that fades out across the link, plus an accent
 * bar on the left edge with a soft ambient glow around it. The wash alone reads
 * as a flat selected row; the bar is what makes it look like the page it points
 * at is lit up.
 */
.nav-link.active {
  background: linear-gradient(90deg, var(--brand-soft) 0%, transparent 130%);
  color: var(--brand);
  box-shadow: 0 2px 14px -4px var(--brand-glow);
}
.nav-link.active::before {
  content: '';
  position: absolute; left: 0; top: 50%; transform: translateY(-50%);
  width: 3px; height: 18px; border-radius: 0 3px 3px 0;
  background: var(--brand);
  box-shadow: 0 0 10px var(--brand);
}
.nav-link .icon {
  width: 18px; height: 18px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  opacity: 0.75; transition: opacity 0.15s ease;
}
.nav-link:hover .icon, .nav-link.active .icon { opacity: 1; }

.nav-section {
  margin: 18px 12px 6px;
  font-size: 10.5px; font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase;
  color: var(--muted); opacity: 0.65;
}

.sidebar-footer { margin-top: auto; padding: 12px 6px 0; border-top: 1px solid var(--border); font-size: 13px; color: var(--muted); }
/* The account chip: who is signed in, as an object rather than two loose lines
   of text above the buttons. */
.sidebar-user {
  display: flex; align-items: center; gap: 10px;
  padding: 8px; border-radius: var(--radius-sm);
  background: var(--surface); border: 1px solid var(--border);
  box-shadow: inset 0 1px 0 var(--hairline);
}
.sidebar-user-meta { min-width: 0; }
.sidebar-user-name {
  color: var(--text); font-weight: 600; font-size: 13px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sidebar-user-role { font-size: 11.5px; text-transform: capitalize; letter-spacing: 0.02em; }
/* Two even columns, so four small actions read as one block instead of a
   ragged wrapped row. */
.sidebar-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; margin-top: 10px; }
.sidebar-actions .btn {
  justify-content: flex-start; gap: 6px;
  padding: 7px 9px; font-size: 12.5px;
  min-width: 0; overflow: hidden; white-space: nowrap;
}

.main { min-width: 0; display: flex; flex-direction: column; }
.topbar {
  display: flex; align-items: center; gap: 16px;
  /* The top pad carries the status-bar clearance: this is the first thing
     under the notch in an installed window, and it stays sticky there. */
  padding: calc(16px + var(--safe-top)) 28px 16px; border-bottom: 1px solid var(--border);
  /*
   * Glass, mixed from the active theme's own --bg rather than a hardcoded
   * colour per preset: every gym and library palette, in both modes, gets a
   * topbar that is unmistakably the page's own background with the content
   * blurring through it. The saturate() is what keeps colour in what shows
   * through — a plain blur greys it out.
   */
  background: color-mix(in srgb, var(--bg) 78%, transparent);
  backdrop-filter: blur(14px) saturate(180%);
  -webkit-backdrop-filter: blur(14px) saturate(180%);
  position: sticky; top: 0; z-index: 20;
}
.topbar h1 { margin: 0; font-size: 20px; font-weight: 600; }
.topbar .spacer { flex: 1; }
.content { padding: 24px 28px calc(60px + var(--safe-bottom)); }

/* Drawer handle. Shown only where the drawer exists — see the responsive
   section at the foot of this file. */
.nav-toggle { line-height: 0; padding: 8px 10px; flex: none; }

/* ---------------------------------------------------------------- elements */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  border: 1px solid var(--border); background: var(--surface-2); color: var(--text);
  padding: 8px 14px; border-radius: var(--radius-sm); font-size: 14px; font-weight: 500;
  cursor: pointer;
  /* The inset top highlight — see --hairline. On a button it is what gives the
     surface a lit upper edge instead of a printed-on look. */
  box-shadow: inset 0 1px 0 var(--hairline);
  transition: background 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease,
    transform 0.12s ease, color 0.15s ease;
}
/*
 * Hover tints towards the brand and lifts the border, rather than the old
 * brightness() filter — a filter also washed out the icon inside and behaved
 * backwards in light mode, where "brighter" means less contrast.
 */
.btn:hover {
  background: color-mix(in srgb, var(--brand) 9%, var(--surface-2));
  border-color: color-mix(in srgb, var(--brand) 40%, var(--border));
  text-decoration: none;
}
.btn:active { transform: translateY(1px); }
.btn:focus-visible { outline: none; box-shadow: inset 0 1px 0 var(--hairline), var(--ring); border-color: var(--brand); }
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
.btn:disabled:hover { transform: none; background: var(--surface-2); border-color: var(--border); }
.btn .svg-icon { opacity: 0.9; }
.btn:hover .svg-icon { opacity: 1; }

.btn.primary { background: var(--brand); border-color: var(--brand); color: #1a1005; font-weight: 600; }
.btn.primary:hover { background: var(--brand); border-color: var(--brand); filter: brightness(1.07); }
.btn.primary .svg-icon { opacity: 1; }
.btn.ghost { background: transparent; box-shadow: none; }
.btn.ghost:hover { background: color-mix(in srgb, var(--brand) 8%, var(--surface-2)); }
.btn.danger { color: var(--red); border-color: rgba(248, 113, 113, 0.4); background: rgba(248, 113, 113, 0.08); }
.btn.danger:hover { background: rgba(248, 113, 113, 0.16); border-color: rgba(248, 113, 113, 0.65); }
.btn.sm { padding: 5px 10px; font-size: 13px; gap: 6px; }
/* Square, so a lone icon sits centred instead of off to one side of a pill. */
.btn.icon-only { padding: 7px; line-height: 0; }
.btn.block { width: 100%; justify-content: center; }

/* Fullscreen mode enhancements */
body.is-fullscreen .topbar {
  border-bottom-color: var(--brand-soft);
}


input, select, textarea {
  width: 100%; padding: 9px 12px; border-radius: var(--radius-sm);
  border: 1px solid var(--border); background: var(--bg);
  color: var(--text); font: inherit; font-size: 14px;
  transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}
input:hover:not(:focus), select:hover:not(:focus), textarea:hover:not(:focus) {
  border-color: color-mix(in srgb, var(--brand) 30%, var(--border));
}
/* A soft ring outside the border rather than a hard outline: the ring reads as
   the field lighting up, and unlike outline it follows the border radius. */
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--brand);
  box-shadow: var(--ring);
  background: var(--bg-alt);
}
::placeholder { color: color-mix(in srgb, var(--muted) 78%, transparent); }
textarea { resize: vertical; min-height: 72px; }
label.field { display: block; margin-bottom: 14px; }
label.field > span { display: block; margin-bottom: 5px; font-size: 13px; color: var(--muted); }
.field-error { color: var(--red); font-size: 12px; margin-top: 4px; }
.form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(210px, 100%), 1fr)); gap: 0 16px; }
.form-grid .full { grid-column: 1 / -1; }

.card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius); padding: 18px;
  box-shadow: var(--shadow-sm), inset 0 1px 0 var(--hairline);
  /* Grid/flex items default to min-width:auto, which lets a wide child (a
     table, a chart) push the track wider than the screen. */
  min-width: 0;
}
.card > h3 { margin: 0 0 14px; font-size: 15px; font-weight: 600; }
.card-head { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
.card-head h3 { margin: 0; font-size: 15px; }
.card-head .spacer { flex: 1; }

.grid { display: grid; gap: 16px; }
.grid > * { min-width: 0; }
/*
 * auto-fit + minmax collapses these to fewer columns on its own as the viewport
 * narrows, so there are no per-breakpoint column overrides.
 *
 * The min() around each floor is what makes that safe: a bare
 * minmax(320px, 1fr) holds a 320px track even inside a 290px container, which
 * pushed every card a few px past the right edge of a 320px phone. min(_, 100%)
 * lets the track shrink to the container once it can't fit the ideal width.
 */
.cols-4 { grid-template-columns: repeat(auto-fit, minmax(min(210px, 100%), 1fr)); }
.cols-3 { grid-template-columns: repeat(auto-fit, minmax(min(270px, 100%), 1fr)); }
.cols-2 { grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr)); }
.grid.top { align-items: start; }

.stat {
  display: flex; flex-direction: column; gap: 6px;
  position: relative;
  background: linear-gradient(160deg, var(--surface) 0%, var(--surface-2) 130%);
  transition: transform 0.18s ease, box-shadow 0.18s ease, border-color 0.18s ease;
}
.stat:hover {
  transform: translateY(-2px);
  border-color: color-mix(in srgb, var(--brand) 35%, transparent);
  box-shadow: var(--shadow), 0 0 22px -12px var(--brand), inset 0 1px 0 var(--hairline);
}
.stat .label { color: var(--muted); font-size: 13px; font-weight: 500; }
.stat .value { font-size: 27px; font-weight: 700; letter-spacing: -0.025em; font-variant-numeric: tabular-nums; }
.stat .hint { font-size: 12px; color: var(--muted); }
.stat.accent { border-left: 3px solid var(--brand); }
.stat.clickable { cursor: pointer; }
.stat.clickable:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

.stat-top { display: flex; align-items: center; justify-content: space-between; margin-bottom: 2px; }
/* The metric tile: a brand-tinted square holding the card's icon. The border
   and the inset highlight are what stop it reading as a flat colour patch. */
.stat-icon {
  width: 34px; height: 34px; border-radius: 10px;
  background: var(--brand-soft); color: var(--brand);
  border: 1px solid color-mix(in srgb, var(--brand) 20%, transparent);
  box-shadow: inset 0 1px 0 var(--hairline);
  display: grid; place-items: center; font-size: 15px; flex: none;
  transition: background 0.18s ease, transform 0.18s ease;
}
.stat:hover .stat-icon { background: color-mix(in srgb, var(--brand) 22%, transparent); transform: scale(1.05); }

.trend-pill {
  display: inline-flex; align-items: center; gap: 5px; align-self: flex-start;
  padding: 3px 10px; border-radius: 999px;
  font-size: 11.5px; font-weight: 600;
  border: 1px solid var(--border);
  background: var(--surface-2); color: var(--muted);
}
.trend-pill.pos { background: rgba(52, 211, 153, 0.14); color: var(--green); border-color: rgba(52, 211, 153, 0.26); }
.trend-pill.neg { background: rgba(248, 113, 113, 0.14); color: var(--red); border-color: rgba(248, 113, 113, 0.26); }

/* Small live-activity indicator — used in stat cards and the dashboard header. */
.live-pulse {
  position: relative;
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--green);
  flex: none; display: inline-block;
}
.live-pulse-core {
  position: absolute; inset: 0; border-radius: 50%;
  background: var(--green);
  animation: pulse-ring 1.6s ease-out infinite;
}
@keyframes pulse-ring {
  0% { box-shadow: 0 0 0 0 rgba(52, 211, 153, 0.55); }
  70% { box-shadow: 0 0 0 8px rgba(52, 211, 153, 0); }
  100% { box-shadow: 0 0 0 0 rgba(52, 211, 153, 0); }
}

table { width: 100%; border-collapse: collapse; font-size: 14px; }
/* A faint tinted band, mixed from --muted so it lands on the right side of the
   surface in both modes, marks the header row without a heavy rule. */
th {
  text-align: left; padding: 10px 12px; color: var(--muted);
  font-size: 11.5px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.07em;
  background: color-mix(in srgb, var(--muted) 6%, transparent);
  border-bottom: 1px solid var(--border); white-space: nowrap;
}
th:first-child { border-radius: var(--radius-sm) 0 0 0; }
th:last-child { border-radius: 0 var(--radius-sm) 0 0; }
/* Row rules mixed from --border rather than a fixed rgba: the old hardcoded
   slate stayed dark-mode slate on a white table. */
td {
  padding: 11px 12px; vertical-align: middle;
  border-bottom: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
}
tbody tr { transition: background 0.12s ease; }
tbody tr:hover { background: color-mix(in srgb, var(--brand) 5%, var(--surface-2)); }
tbody tr:last-child td { border-bottom: none; }
tbody tr.clickable { cursor: pointer; }
.table-wrap { overflow-x: auto; }
.num { text-align: right; font-variant-numeric: tabular-nums; }

/*
 * Status pills. Translucent rather than solid, with a border in the same tone
 * so the shape survives on any surface, and inline-flex so a pill can carry an
 * icon or a status dot beside its label.
 */
.badge {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 3px 10px; border-radius: 999px;
  border: 1px solid transparent;
  font-size: 12px; font-weight: 600; white-space: nowrap;
  vertical-align: middle;
}
.badge .svg-icon { opacity: 0.85; }
/* The state dot: same colour as the label, with a soft halo. Added by
   statusBadge() in js/ui.js, where a change of state is what matters. */
.badge.dot::before {
  content: ''; flex: none;
  width: 6px; height: 6px; border-radius: 50%;
  background: currentColor;
  box-shadow: 0 0 0 2px color-mix(in srgb, currentColor 22%, transparent);
}
.badge.green { background: rgba(52, 211, 153, 0.14); color: var(--green); border-color: rgba(52, 211, 153, 0.26); }
.badge.red { background: rgba(248, 113, 113, 0.14); color: var(--red); border-color: rgba(248, 113, 113, 0.26); }
.badge.amber { background: rgba(251, 191, 36, 0.14); color: var(--amber); border-color: rgba(251, 191, 36, 0.28); }
.badge.blue { background: rgba(96, 165, 250, 0.14); color: var(--blue); border-color: rgba(96, 165, 250, 0.26); }
.badge.violet { background: rgba(167, 139, 250, 0.16); color: var(--violet); border-color: rgba(167, 139, 250, 0.28); }
.badge.grey { background: rgba(139, 155, 176, 0.14); color: var(--muted); border-color: rgba(139, 155, 176, 0.24); }

.avatar {
  width: 34px; height: 34px; border-radius: 50%;
  background: var(--brand-soft); color: var(--brand);
  border: 1px solid color-mix(in srgb, var(--brand) 18%, transparent);
  display: grid; place-items: center; font-size: 13px; font-weight: 700; flex: none;
}
.avatar.sm { width: 30px; height: 30px; font-size: 11.5px; }
.avatar.lg { width: 64px; height: 64px; font-size: 22px; }
.person { display: flex; align-items: center; gap: 10px; }
.person .meta { min-width: 0; }
.person .name { font-weight: 600; }
.person .sub { color: var(--muted); font-size: 12px; }

.toolbar { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin-bottom: 16px; }
.toolbar .search { flex: 1; min-width: 220px; max-width: 380px; }
.toolbar select { width: auto; min-width: 140px; }
.toolbar-pair { display: flex; align-items: center; gap: 8px; }
.toolbar-pair > span { font-size: 13px; white-space: nowrap; }

.muted { color: var(--muted); }
.empty { padding: 36px; text-align: center; color: var(--muted); }
.row { display: flex; align-items: center; gap: 10px; }
.wrap { flex-wrap: wrap; }
.pill-row { display: flex; flex-wrap: wrap; gap: 8px; }
.kv { display: grid; grid-template-columns: 130px 1fr; gap: 8px 12px; font-size: 14px; }
.kv dt { color: var(--muted); }
/* min-width: 0 lets the grid cell shrink below its content's natural width;
   overflow-wrap breaks a long unbreakable value (email, long note) onto the
   next line instead of spilling out of the card and getting clipped. */
.kv dd { margin: 0; min-width: 0; overflow-wrap: anywhere; }
.pagination { display: flex; align-items: center; gap: 10px; justify-content: flex-end; margin-top: 14px; font-size: 14px; }

.list { display: flex; flex-direction: column; }
.list-item {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 0; border-bottom: 1px solid color-mix(in srgb, var(--border) 55%, transparent);
}
.list-item:last-child { border-bottom: none; }
.list-item .spacer { flex: 1; }

/* ------------------------------------------------------------------ charts */

.chart { width: 100%; overflow: visible; }
.chart .bar { fill: var(--brand); opacity: 0.85; cursor: pointer; transition: opacity 0.12s, filter 0.12s; }
.chart .bar:hover { opacity: 1; filter: drop-shadow(0 0 8px var(--brand-glow)); }
.chart .axis { stroke: var(--border); }
.chart .grid-line { stroke: var(--border); stroke-dasharray: 4 4; opacity: 0.6; }
.chart text { fill: var(--muted); font-size: 10px; }
.chart .line { fill: none; stroke: var(--brand); stroke-width: 2; filter: drop-shadow(0 0 6px var(--brand-glow)); }
.chart .area { opacity: 0.9; }
.chart .dot { fill: var(--brand); }
.chart .dot.hover-dot { stroke: var(--bg); stroke-width: 2; opacity: 0; transition: opacity 0.1s ease; pointer-events: none; }
.chart .dot.hover-dot.show { opacity: 1; }
.chart .crosshair { stroke: var(--brand); stroke-width: 1; opacity: 0; pointer-events: none; transition: opacity 0.1s ease; }
.chart .crosshair.show { opacity: 0.5; }

/* Floating tooltip shared by barChart() and lineChart() — positioned in JS via
   a translate() on this element, relative to .chart-wrap. */
.chart-wrap { position: relative; }
.chart-tooltip {
  position: absolute; top: 0; left: 0;
  pointer-events: none; opacity: 0;
  background: var(--bg-alt); border: 1px solid var(--border);
  border-radius: 8px; padding: 6px 10px; font-size: 12px;
  box-shadow: var(--shadow); white-space: nowrap; z-index: 5;
  transition: opacity 0.1s ease;
}
.chart-tooltip.show { opacity: 1; }
.chart-tooltip-label { color: var(--muted); font-size: 11px; margin-bottom: 2px; }
.chart-tooltip-value { font-weight: 700; }

.legend { display: flex; flex-wrap: wrap; gap: 12px; font-size: 13px; color: var(--muted); margin-top: 10px; }
.legend .dot { width: 10px; height: 10px; border-radius: 3px; display: inline-block; margin-right: 6px; }

.meter { height: 8px; border-radius: 99px; background: var(--surface-2); overflow: hidden; position: relative; }
.meter > span { display: block; height: 100%; background: var(--brand); transition: width 0.4s ease; }
.meter.gradient { height: 16px; }
.meter.gradient > span { background: linear-gradient(90deg, var(--brand) 0%, var(--green) 100%); border-radius: 999px; }
.meter-pct {
  position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  font-size: 10px; font-weight: 700; color: var(--text); font-style: normal;
}

/* -------------------------------------------------------------- filter tabs */

.filter-tabs { display: flex; gap: 6px; flex-wrap: wrap; }
.filter-tab {
  border: 1px solid var(--border); background: var(--surface-2); color: var(--muted);
  padding: 5px 12px; border-radius: 999px; font-size: 12px; font-weight: 600;
  cursor: pointer; transition: background 0.15s, color 0.15s, border-color 0.15s;
}
.filter-tab:hover { color: var(--text); }
.filter-tab.active { background: var(--brand-soft); color: var(--brand); border-color: var(--brand); }

/* ------------------------------------------------------------ dashboard header */

.dash-header {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  background: linear-gradient(135deg, var(--surface) 0%, var(--brand-soft) 160%);
  border: 1px solid var(--border);
}
.dash-greeting { margin: 0; font-size: 22px; letter-spacing: -0.01em; }

.live-badge {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 7px 14px; border-radius: 999px;
  background: rgba(52, 211, 153, 0.12);
  border: 1px solid rgba(52, 211, 153, 0.3);
  color: var(--green);
  font-size: 12px; font-weight: 700; letter-spacing: 0.03em; text-transform: uppercase;
  white-space: nowrap;
}

/* Glassmorphic cards for SeatBook (library) — translucent panels over the
   abyssal background instead of the gym's flat opaque surfaces. */
body[data-vertical='library'] .card,
[data-brand='library'] .card {
  background: color-mix(in srgb, var(--surface) 72%, transparent);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-color: color-mix(in srgb, var(--text) 10%, var(--border));
}
body[data-vertical='library'] .stat,
[data-brand='library'] .stat {
  background: linear-gradient(
    160deg,
    color-mix(in srgb, var(--surface) 72%, transparent) 0%,
    color-mix(in srgb, var(--surface-2) 72%, transparent) 130%
  );
}

/* -------------------------------------------------------- modal and toasts */

.modal-backdrop {
  position: fixed; inset: 0; background: rgba(3, 6, 11, 0.66);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  display: grid; place-items: start center; padding: 40px 16px; overflow-y: auto; z-index: 60;
  animation: fade 0.14s ease-out;
}
.modal {
  width: min(680px, 100%); background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg), inset 0 1px 0 var(--hairline);
  animation: rise 0.18s ease-out;
}
.modal.wide { width: min(880px, 100%); }
.modal-head { display: flex; align-items: center; gap: 12px; padding: 18px 20px; border-bottom: 1px solid var(--border); }
.modal-head h2 { margin: 0; font-size: 17px; }
.modal-head .spacer { flex: 1; }
.modal-body { padding: 20px; }
.modal-foot { display: flex; gap: 10px; justify-content: flex-end; padding: 16px 20px; border-top: 1px solid var(--border); }
/* Only the modal's own footer gets the tinted tray — buildForm() reuses this
   class for a form's inline button row, which sits in the body. */
.modal > .modal-foot {
  background: color-mix(in srgb, var(--muted) 5%, transparent);
  border-radius: 0 0 var(--radius-lg) var(--radius-lg);
}
.icon-btn {
  background: none; border: 1px solid transparent; color: var(--muted);
  font-size: 20px; cursor: pointer; line-height: 0;
  display: inline-grid; place-items: center;
  padding: 7px; border-radius: var(--radius-sm);
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.icon-btn:hover { background: var(--surface-2); color: var(--text); border-color: var(--border); }
.icon-btn:focus-visible { outline: none; box-shadow: var(--ring); border-color: var(--brand); }
@keyframes fade { from { opacity: 0; } }

.toasts {
  position: fixed;
  right: calc(20px + var(--safe-right));
  bottom: calc(20px + var(--safe-bottom));
  display: flex; flex-direction: column; gap: 10px; z-index: 90;
}
.toast {
  padding: 12px 16px; border-radius: 10px; background: var(--surface-2);
  border: 1px solid var(--border); box-shadow: var(--shadow);
  font-size: 14px; max-width: 340px; animation: rise 0.18s ease-out;
}
.toast.success { border-left: 3px solid var(--green); }
.toast.error { border-left: 3px solid var(--red); }
.toast.info { border-left: 3px solid var(--blue); }
@keyframes rise { from { transform: translateY(8px); opacity: 0; } }

/* ------------------------------------------------------------- check-in UI */

.checkin-box { max-width: 520px; }
.checkin-box input { font-size: 20px; padding: 14px 16px; text-align: center; letter-spacing: 0.08em; }
.checkin-result { margin-top: 18px; padding: 16px; border-radius: 12px; background: var(--bg-alt); border: 1px solid var(--border); }
.checkin-result.ok { border-left: 3px solid var(--green); }
.checkin-result.out { border-left: 3px solid var(--blue); }
.checkin-result.bad { border-left: 3px solid var(--red); }

.timetable { display: grid; grid-template-columns: repeat(7, minmax(150px, 1fr)); gap: 12px; }
.day-col h4 { margin: 0 0 10px; font-size: 13px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; }
.day-col.today h4 { color: var(--brand); }
.class-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 10px; padding: 10px 12px; margin-bottom: 10px; font-size: 13px;
}
.class-card .name { font-weight: 600; font-size: 14px; }
.class-card .time { color: var(--brand); font-size: 12px; font-weight: 600; }
.class-card .sub { color: var(--muted); font-size: 12px; }

/* ---------------------------------------------------------- seat map UI */

.seatmap-tabs {
  display: flex; flex-wrap: wrap; align-items: stretch; gap: 10px; margin-bottom: 16px;
}
.seatmap-tab {
  position: relative;
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  padding: 10px 16px; cursor: pointer; text-align: left; color: var(--text); font: inherit;
  min-width: 128px;
  transition: border-color 0.15s, box-shadow 0.15s, transform 0.15s;
}
.seatmap-tab:hover { border-color: var(--brand); transform: translateY(-1px); }
.seatmap-tab.active { border-color: var(--brand); box-shadow: 0 0 0 2px var(--brand-soft); background: var(--brand-soft); }
.seatmap-tab-name { font-weight: 600; font-size: 14px; }
.seatmap-tab-count { font-size: 12px; color: var(--muted); margin-top: 2px; }
.seatmap-tab-bar { height: 4px; border-radius: 2px; background: var(--border); margin-top: 8px; overflow: hidden; }
.seatmap-tab-bar > i { display: block; height: 100%; background: var(--brand); border-radius: 2px; transition: width 0.3s; }
.seatmap-date { margin-left: auto; max-width: 170px; }

.seatmap-legend { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 16px; }
.seatmap-legend-item {
  display: flex; align-items: center; gap: 6px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 999px;
  padding: 5px 10px 5px 8px; font-size: 12px; color: var(--muted);
}
.seat-tile.swatch { width: 14px; height: 14px; border-radius: 4px; padding: 0; pointer-events: none; flex: none; display: inline-block; }
.seat-tile.swatch .seat-icon-wrap,
.seat-tile.swatch .seat-code { display: none; }

/* The hall scrolls sideways, never the page — a 30-wide row must not widen
   the document on a phone. */
.seatmap-card { overflow-x: auto; display: flex; flex-direction: column; padding: 20px; }
.seatmap-zone { padding: 14px 0; }
.seatmap-zone + .seatmap-zone { border-top: 1px dashed var(--border); }
.seatmap-zone-title {
  display: flex; align-items: baseline; gap: 10px;
  font-size: 12px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted);
  margin-bottom: 14px;
}
.seatmap-zone-count {
  font-size: 11px; font-weight: 600; letter-spacing: normal; text-transform: none;
  color: var(--text); background: var(--surface-2); border-radius: 999px; padding: 2px 9px;
}
.seatmap-row { display: flex; align-items: center; gap: 12px; padding: 3px 4px; border-radius: 8px; }
.seatmap-row:hover { background: var(--surface-2); }
.seatmap-row-label { width: 22px; flex: none; color: var(--muted); font-size: 12px; font-weight: 700; text-align: center; }
.seatmap-row-label.pickable { cursor: pointer; border-radius: 6px; transition: background 0.12s ease, color 0.12s ease; }
.seatmap-row-label.pickable:hover { background: var(--brand); color: #fff; }
.seatmap-row-grid { display: grid; grid-template-columns: repeat(60, 42px); gap: 8px; }

/* Select-mode column header: one button per used col_index, sat above the
   grid at the same grid-column as its seats so it lines up visually. */
.seatmap-col-picker {
  width: 42px; height: 22px; border-radius: 6px; border: 1px dashed var(--border);
  background: var(--surface-2); color: var(--muted); font-size: 11px; font-weight: 700;
  cursor: pointer; transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}
.seatmap-col-picker:hover { background: var(--brand); border-color: var(--brand); color: #fff; }

.seat-tile {
  width: 42px; height: 44px; border-radius: 9px; border: 1px solid var(--border);
  background: var(--surface-2); color: var(--text);
  font-size: 11px; font-weight: 700; cursor: pointer; padding: 4px 2px;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2px; position: relative;
  transition: transform 0.12s ease, box-shadow 0.12s ease, filter 0.12s ease;
}
.seat-icon-wrap { display: flex; align-items: center; justify-content: center; line-height: 1; }
.seat-icon { width: 14px; height: 14px; stroke-width: 2; flex-shrink: 0; opacity: 0.85; transition: transform 0.12s ease, opacity 0.12s ease; }
.seat-code { font-size: 10.5px; font-weight: 700; line-height: 1; letter-spacing: -0.2px; white-space: nowrap; }
.seat-tile:hover { transform: translateY(-2px); box-shadow: 0 4px 10px rgba(0, 0, 0, 0.35); filter: brightness(1.12); }
.seat-tile:hover .seat-icon { transform: scale(1.15); opacity: 1; }
.seat-tile:active { transform: translateY(0) scale(0.94); }
.seat-tile:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.seat-tile.vacant { background: var(--surface-2); border-style: dashed; color: var(--muted); }
.seat-tile.occupied { background: rgba(52, 211, 153, 0.16); border-color: var(--green); color: var(--green); }
.seat-tile.expiring { background: rgba(251, 191, 36, 0.18); border-color: var(--amber); color: var(--amber); }
.seat-tile.expired { background: rgba(248, 113, 113, 0.18); border-color: var(--red); color: var(--red); }
.seat-tile.dues { background: rgba(167, 139, 250, 0.2); border-color: var(--violet); color: var(--violet); }
.seat-tile.frozen { background: rgba(96, 165, 250, 0.16); border-color: var(--blue); color: var(--blue); }
.seat-tile.oos {
  background: repeating-linear-gradient(45deg, var(--surface-2), var(--surface-2) 4px, var(--bg-alt) 4px, var(--bg-alt) 8px);
  color: var(--muted); opacity: 0.45; cursor: not-allowed;
}
.seat-tile.oos:hover { transform: none; box-shadow: none; filter: none; }
/* Edit mode: every open seat gets a dashed brand outline so it's obvious
   the whole map is now clickable-to-edit rather than clickable-to-assign. */
.seat-tile.editing::after {
  content: ''; position: absolute; inset: -4px; border-radius: 11px;
  border: 1px dashed var(--brand); pointer-events: none;
}
/* Select-seats mode: a checked-off tile gets a solid brand fill instead of
   the dashed editing outline, so "picked for deletion" reads at a glance. */
.seat-tile.selected {
  background: var(--brand); border-color: var(--brand); color: #fff;
}
.seat-tile.selected::after { border-style: solid; border-color: #fff; }

.bulk-select-bar {
  display: flex; align-items: center; gap: 12px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  padding: 10px 14px; margin: 0 0 16px;
}

/* ------------------------------------------------------- live seat map */

/* A presence scale, not a tenancy one: red = the student is in the chair,
   yellow = the desk is rented but empty, plain dashed = nobody's desk. It
   deliberately reuses .seat-tile so the grid geometry stays identical to the
   allocation map, and only the fill differs. */
.seat-tile.live-present {
  background: rgba(248, 113, 113, 0.2); border-color: var(--red); color: var(--red);
}
.seat-tile.live-assigned {
  background: rgba(251, 191, 36, 0.2); border-color: var(--amber); color: var(--amber);
}
.seat-tile.live-vacant { background: var(--surface-2); border-style: dashed; color: var(--muted); }

/* The occupant's initials, under the seat code. Only present/assigned tiles
   carry one, so the extra line never shifts a vacant tile's layout. */
.seat-who {
  font-size: 8.5px; font-weight: 700; line-height: 1; letter-spacing: 0.3px;
  opacity: 0.8; text-transform: uppercase;
}
.seat-tile.live-present .seat-icon { opacity: 1; }
/* The one tile state that is genuinely "happening now" gets the only motion
   on the map, so a full hall still reads at a glance. */
.seat-tile.live-present::after {
  content: ''; position: absolute; top: 3px; right: 3px;
  width: 5px; height: 5px; border-radius: 50%; background: var(--red);
  animation: live-seat-pulse 2s ease-in-out infinite;
}
@keyframes live-seat-pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: 0.35; transform: scale(0.8); }
}

/* min-width:0 on the column is what actually lets the hall scroll: without it
   the flex item takes its 60-column grid as its minimum size and shoves the
   whole modal off-screen instead of clipping. */
.live-map { display: flex; flex-direction: column; gap: 14px; min-width: 0; }
.live-map > * { min-width: 0; }
/* The hall grid is wider than any phone, and a grid item sizes its track to
   max-content unless told otherwise — without this the modal itself stretches
   past the viewport instead of letting .seatmap-card scroll inside it. The
   class is stamped on by openLiveSeatMap() rather than matched with :has(),
   since this has to hold on every browser the front desk might run. */
.modal.live-map-modal { min-width: 0; max-width: 100%; }
.modal.live-map-modal .modal-body { min-width: 0; }
.live-map-summary {
  display: flex; align-items: center; gap: 22px; flex-wrap: wrap;
  padding: 12px 14px; border: 1px solid var(--border); border-radius: 12px;
  background: var(--surface-2);
}
.live-map-figure { display: flex; flex-direction: column; gap: 2px; line-height: 1.2; }
.live-map-figure > strong { font-size: 20px; font-weight: 700; }
.live-map-figure > .muted { font-size: 11.5px; text-transform: uppercase; letter-spacing: 0.4px; }
/* The hall is a fixed 60-column grid, far wider than the modal. It scrolls
   inside its own box — the modal itself must never scroll sideways. */
.live-map .seatmap-card {
  border: 1px solid var(--border); border-radius: 12px;
  overflow-x: auto; padding: 14px; max-width: 100%;
}
/* Column count itself is set inline per hall — see openLiveSeatMap(). */
.live-map .seatmap-row-grid { gap: 6px; }
.live-map .seatmap-tabs { overflow-x: auto; padding-bottom: 4px; }
.live-map .seatmap-tab { flex: 0 0 auto; min-width: 130px; }
.live-map-strays { border-top: 1px dashed var(--border); padding-top: 12px; }
.live-map-strays-title {
  font-size: 12px; font-weight: 700; text-transform: uppercase;
  letter-spacing: 0.4px; color: var(--muted); margin-bottom: 6px;
}

@media (prefers-reduced-motion: reduce) {
  .seat-tile.live-present::after { animation: none; }
}

@media (max-width: 640px) {
  .seatmap-row-grid { grid-template-columns: repeat(60, 34px); gap: 5px; }
  /* The live map's column count is inline (and so unbeatable from here); the
     tiles shrink to 34px under the shared .seat-tile rule below, which just
     leaves a little slack in each 40px track. Only the gap needs closing. */
  .live-map .seatmap-row-grid { gap: 5px; }
  .seat-tile { width: 34px; height: 38px; padding: 2px 1px; gap: 1px; }
  .seat-icon { width: 12px; height: 12px; }
  .seat-code { font-size: 9px; }
  .seatmap-date { margin-left: 0; }
  .seat-who { display: none; }
  .live-map-summary { gap: 14px; }
}

/* -------------------------------------------------------- biometric UI */

.bio-card {
  border: 1px solid rgba(167, 139, 250, 0.25);
  background: linear-gradient(135deg, var(--surface) 0%, rgba(167, 139, 250, 0.04) 100%);
}
.bio-card h3 { margin: 0 0 4px; }

.bio-btn {
  position: relative;
  font-size: 16px;
  padding: 14px 20px;
  gap: 10px;
  transition: all 0.2s ease;
}
.bio-btn:not(:disabled):hover {
  box-shadow: 0 0 20px rgba(249, 115, 22, 0.3);
  transform: translateY(-1px);
}
.bio-btn.scanning {
  background: var(--surface-2);
  border-color: var(--violet);
  color: var(--violet);
  cursor: wait;
}
.bio-icon {
  font-size: 20px;
  display: inline-flex;
  align-items: center;
}

.bio-pulse {
  display: inline-block;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--violet);
  animation: bioPulse 1.2s ease-in-out infinite;
  margin-right: 6px;
}

@keyframes bioPulse {
  0%, 100% { transform: scale(1); opacity: 0.7; box-shadow: 0 0 0 0 rgba(167, 139, 250, 0.5); }
  50% { transform: scale(1.2); opacity: 1; box-shadow: 0 0 12px 4px rgba(167, 139, 250, 0.3); }
}

.bio-member-card {
  border: 1px solid rgba(167, 139, 250, 0.2);
}
.bio-member-card .card-head h3 { margin: 0; }

.bio-cred-icon {
  width: 36px;
  height: 36px;
  border-radius: 10px;
  background: rgba(167, 139, 250, 0.12);
  display: grid;
  place-items: center;
  font-size: 18px;
  flex: none;
}

.bio-unsupported {
  opacity: 0.6;
}

/* ------------------------------------------------------- QR cards & scanning */

.qr-card {
  border: 1px solid rgba(96, 165, 250, 0.22);
  background: linear-gradient(135deg, var(--surface) 0%, rgba(96, 165, 250, 0.04) 100%);
}
.qr-card .card-head h3 { margin: 0; }

.qr-preview {
  display: flex;
  gap: 18px;
  align-items: center;
  flex-wrap: wrap;
  padding: 4px 0 12px;
  /* Pairs with the .id-card flex:none below: the card holds its true printed
     size and this scrolls, instead of the page scrolling. Idle at widths where
     the card already fits. */
  overflow-x: auto;
}

.scan-video-wrap {
  position: relative;
  border-radius: 10px;
  overflow: hidden;
  background: #000;
  border: 1px solid var(--border);
  aspect-ratio: 4 / 3;
}
.scan-video-wrap video { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Framing guide for the person holding the card up to the camera. */
.scan-reticle {
  position: absolute;
  inset: 18% 22%;
  border: 2px solid rgba(249, 115, 22, 0.85);
  border-radius: 8px;
  box-shadow: 0 0 0 100vmax rgba(0, 0, 0, 0.35);
}

.scan-result-photo {
  width: 56px; height: 56px; border-radius: 12px;
  object-fit: cover; flex: none; border: 1px solid var(--border);
}

/*
 * The ID card. Sized in inches against CR80 card stock (3.375in x 2.125in) so
 * the on-screen preview and the printed sheet are the same object — screens
 * render at 96 CSS px/in, printers at their own DPI, and both honour `in`.
 */
.id-card {
  width: 3.375in;
  height: 2.125in;
  /* A preview that shrank to fit its container would misrepresent what comes
     out of the printer, so it keeps its inch geometry and .qr-preview scrolls. */
  flex: none;
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 0.08in;
  padding: 0.16in;
  border-radius: 0.1in;
  background: #ffffff;
  color: #111827;
  border: 1px solid #d1d5db;
  overflow: hidden;
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.id-card-main { min-width: 0; display: flex; flex-direction: column; }
.id-card-gym {
  font-size: 0.13in;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #111827;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.id-card-gym-brand { display: flex; align-items: center; gap: 0.06in; min-width: 0; }
.id-card-logo-img { width: 0.22in; height: 0.22in; border-radius: 0.04in; object-fit: cover; flex-shrink: 0; }
.id-card-photo {
  width: 0.62in; height: 0.62in;
  object-fit: cover;
  border-radius: 0.06in;
  border: 1px solid #d1d5db;
  margin: 0.07in 0;
}
.id-card-name {
  font-size: 0.13in;
  font-weight: 700;
  color: #111827;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.id-card-code {
  font-size: 0.1in;
  color: #6b7280;
  font-family: ui-monospace, 'SF Mono', Menlo, monospace;
  margin-top: 0.02in;
}
.id-card-valid { font-size: 0.1in; color: #6b7280; margin-top: 0.03in; }
.id-card-valid.muted { color: #9ca3af; }

.id-card-qr { display: flex; flex-direction: column; align-items: center; justify-content: center; }
.id-card-qr-img { width: 1.15in; height: 1.15in; }
.id-card-qr-img svg { width: 100%; height: 100%; display: block; }
.id-card-hint { font-size: 0.075in; color: #6b7280; margin-top: 0.04in; letter-spacing: 0.02em; }

/* ------------------------------------------------------------------ printing */

#print-root { display: none; }

.receipt {
  width: 3.4in;
  margin: 0 auto;
  padding: 20px;
  background: #ffffff;
  border-radius: 12px;
  color: #111827;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
.receipt-header { text-align: center; margin-bottom: 12px; }
.receipt-gym-brand { display: flex; align-items: center; justify-content: center; gap: 8px; margin-bottom: 4px; }
.receipt-logo-img { width: 28px; height: 28px; border-radius: 6px; object-fit: cover; flex-shrink: 0; }
.receipt-gym { font-size: 18px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; color: #111827; }
.receipt-title { font-size: 13px; color: #6b7280; font-weight: 500; margin-top: 2px; text-transform: uppercase; letter-spacing: 0.04em; }
.receipt-divider { border-bottom: 1px dashed #d1d5db; margin: 12px 0; }
.receipt-divider.thick { border-bottom: 2px solid #111827; margin: 14px 0; }
.receipt-section-title { font-size: 11px; font-weight: 700; text-transform: uppercase; color: #6b7280; margin-bottom: 6px; letter-spacing: 0.05em; }
.receipt-row { display: flex; justify-content: space-between; align-items: baseline; font-size: 13px; margin-bottom: 4px; }
.receipt-label { color: #4b5563; }
.receipt-value { font-weight: 500; color: #111827; text-align: right; }
.receipt-method { font-family: ui-monospace, 'SF Mono', Menlo, monospace; font-size: 12px; }
.receipt-total { display: flex; justify-content: space-between; align-items: center; font-size: 16px; font-weight: 700; color: #111827; margin: 6px 0; }
.receipt-footer { text-align: center; font-size: 12px; color: #6b7280; margin-top: 12px; }
.receipt-timestamp { font-size: 10px; color: #9ca3af; margin-top: 4px; }

@media print {
  /* Only the card sheet or receipt reaches the paper; the app shell, toasts and any open
     modal stay off it. */
  body.printing > #app,
  body.printing > .toasts,
  body.printing > #modal-root { display: none !important; }

  body.printing { background: #fff; }
  body.printing > #print-root {
    display: flex !important;
    flex-wrap: wrap;
    gap: 0.2in;
    padding: 0.3in;
    align-content: flex-start;
    justify-content: center;
  }

  /* Never split a card or receipt across a page break. */
  .id-card-slot, .receipt-page { break-inside: avoid; page-break-inside: avoid; }
  .id-card { border: 1px dashed #9ca3af; box-shadow: none; }
  .receipt { border: 1px solid #9ca3af; box-shadow: none; width: 3.2in; }

  @page { margin: 0.4in; }
}

/* ------------------------------------------------- landing + onboarding */

/*
 * The pre-auth pages: the root domain's landing page, the signup flow, and
 * the operator console. These render full-page with no .shell around them —
 * there is no gym yet for a sidebar to navigate — so each one owns its own
 * page padding and max width rather than inheriting .content's.
 */

.landing, .onboard, .console { min-height: 100vh; min-height: 100dvh; }

.landing-top {
  display: flex; align-items: center; gap: 10px;
  padding: calc(18px + var(--safe-top)) calc(clamp(16px, 5vw, 48px) + var(--safe-right)) 18px
    calc(clamp(16px, 5vw, 48px) + var(--safe-left));
  border-bottom: 1px solid var(--border);
}
.landing-top .brand { padding: 0; font-size: 17px; }

.landing-hero {
  max-width: 780px;
  margin: 0 auto;
  padding: clamp(48px, 9vw, 96px) clamp(16px, 5vw, 48px) clamp(32px, 5vw, 56px);
  text-align: center;
  background: radial-gradient(900px 500px at 50% -20%, var(--brand-glow), transparent 65%);
}
.landing-hero h1 {
  margin: 0 0 14px;
  /* Fluid rather than stepped: the headline is the one element where a
     breakpoint jump is visible as a reflow mid-scroll. */
  font-size: clamp(30px, 6vw, 50px);
  line-height: 1.1;
  letter-spacing: -0.02em;
}
.landing-hero .lede { margin: 0 auto 28px; max-width: 56ch; color: var(--muted); font-size: clamp(15px, 2vw, 17px); }

.landing-cta { display: grid; justify-items: center; gap: 10px; }
.btn.lg { padding: 12px 22px; font-size: 15px; }

.landing-signin {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 8px;
  margin-top: 36px; padding-top: 28px;
  border-top: 1px solid var(--border);
  font-size: 14px;
}
.landing-signin-field, .onboard-slug {
  display: flex; align-items: center;
  background: var(--bg-alt);
  border: 1px solid var(--border);
  border-radius: 10px;
  /* min-width:0 so the input can shrink inside the flex row instead of
     pushing the affixes off a narrow screen. */
  min-width: 0;
  overflow: hidden;
}
.landing-signin-field input, .onboard-slug input {
  border: none; background: none; margin: 0; min-width: 0;
  padding: 9px 4px;
}
/* The wrapper owns the focus ring here — the input is only part of the control,
   so its own ring is suppressed rather than drawn inside the affixes. */
.landing-signin-field input:focus, .onboard-slug input:focus { outline: none; box-shadow: none; background: none; }
.landing-signin-field, .onboard-slug { transition: border-color 0.15s ease, box-shadow 0.15s ease; }
.landing-signin-field:focus-within, .onboard-slug:focus-within {
  border-color: var(--brand);
  box-shadow: var(--ring);
}
.affix { padding: 0 8px; color: var(--muted); font-size: 13px; white-space: nowrap; }

.landing-features {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(280px, 100%), 1fr));
  gap: 14px;
  max-width: 1080px;
  margin: 0 auto;
  padding: 0 clamp(16px, 5vw, 48px);
}
/* Marketing cards lift on hover — the only hover state on the page, and what
   makes a static feature grid feel like a product rather than a brochure. */
.landing-feature { transition: transform 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease; }
.landing-feature:hover {
  transform: translateY(-3px);
  border-color: color-mix(in srgb, var(--brand) 32%, var(--border));
  box-shadow: var(--shadow), inset 0 1px 0 var(--hairline);
}
.landing-feature h3 { margin: 10px 0 6px; font-size: 16px; }
.landing-feature p { margin: 0; font-size: 14px; }
.landing-feature-icon {
  width: 38px; height: 38px; border-radius: 10px;
  background: var(--brand-soft); color: var(--brand);
  border: 1px solid color-mix(in srgb, var(--brand) 20%, transparent);
  box-shadow: inset 0 1px 0 var(--hairline);
  display: grid; place-items: center; font-size: 19px;
  transition: background 0.2s ease, transform 0.2s ease;
}
.landing-feature:hover .landing-feature-icon {
  background: color-mix(in srgb, var(--brand) 22%, transparent);
  transform: scale(1.06);
}

.landing-steps {
  max-width: 720px;
  margin: clamp(48px, 8vw, 88px) auto 0;
  padding: 0 clamp(16px, 5vw, 48px);
  text-align: center;
}
.landing-steps h2 { margin: 0 0 20px; font-size: clamp(22px, 3.4vw, 30px); }
.landing-steps ol {
  margin: 0 0 24px; padding: 0;
  list-style: none;
  display: grid; gap: 10px;
  text-align: left;
  color: var(--muted);
  counter-reset: step;
}
.landing-steps li {
  counter-increment: step;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 16px 14px 48px;
  position: relative;
  font-size: 14px;
}
.landing-steps li::before {
  content: counter(step);
  position: absolute; left: 14px; top: 13px;
  width: 22px; height: 22px; border-radius: 999px;
  background: var(--brand-soft); color: var(--brand);
  display: grid; place-items: center;
  font-size: 12px; font-weight: 700;
}
.landing-steps li strong { color: var(--text); }

.landing-foot {
  margin-top: clamp(48px, 8vw, 88px);
  padding: 24px clamp(16px, 5vw, 48px) 40px;
  border-top: 1px solid var(--border);
  text-align: center;
  font-size: 13px;
}

/* ---- signup ---- */

.onboard {
  display: grid; place-items: start center;
  padding: calc(clamp(24px, 5vw, 56px) + var(--safe-top)) calc(16px + var(--safe-right))
    calc(clamp(24px, 5vw, 56px) + var(--safe-bottom)) calc(16px + var(--safe-left));
  background: radial-gradient(900px 520px at 50% -20%, var(--brand-glow), transparent 62%), var(--bg);
}
.onboard-card {
  position: relative;
  width: min(560px, 100%);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: clamp(22px, 4vw, 34px);
  box-shadow: var(--shadow);
}
.onboard-card h1 { margin: 0 0 4px; font-size: clamp(21px, 3.4vw, 26px); }
.onboard-card p.sub { margin: 0 0 24px; color: var(--muted); font-size: 14px; }
.onboard-back { position: absolute; top: 14px; right: 14px; }

.onboard-section {
  margin: 10px 0 -4px;
  font-size: 12px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--muted);
}
.onboard-section:first-child { margin-top: 0; }

/* The business-type fork. Two equal cards, not a select — it changes every
   label below it, so it has to read as a decision, not a field. */
.type-choice { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.type-card {
  display: flex; align-items: center; gap: 10px;
  padding: 14px; border-radius: var(--radius);
  border: 1px solid var(--border); background: var(--bg-alt);
  cursor: pointer; text-align: left;
  transition: border-color 0.15s ease, box-shadow 0.15s ease, background 0.15s ease;
}
.type-card:hover { border-color: color-mix(in srgb, var(--brand) 40%, var(--border)); }
/* width:auto and flex:none undo the global `input { width: 100% }` — as a flex
   item a full-width radio takes a basis of the whole card and squeezes the icon
   and copy into the remainder. */
.type-card input { margin: 0; width: auto; flex: none; accent-color: var(--brand); }
/* min-width:0 lets the copy wrap inside the card rather than forcing the row
   wider than its column. */
.type-card > div { min-width: 0; flex: 1; }
.type-card .icon {
  width: 36px; height: 36px; border-radius: 10px; flex: none;
  background: var(--brand-soft); color: var(--brand);
  border: 1px solid color-mix(in srgb, var(--brand) 20%, transparent);
  box-shadow: inset 0 1px 0 var(--hairline);
  display: grid; place-items: center;
}
.type-card strong { display: block; font-size: 14px; }
.type-card span.muted { font-size: 12px; }
.type-card:has(input:checked) { border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-soft); }

.onboard-availability { margin-top: 6px; font-size: 12px; min-height: 16px; }
.onboard-availability.ok { color: var(--green); }
.onboard-availability.bad { color: var(--red); }

.onboard-done { text-align: center; }
.onboard-tick {
  width: 52px; height: 52px; margin: 0 auto 14px;
  border-radius: 999px;
  background: rgba(52, 211, 153, 0.16); color: var(--green);
  display: grid; place-items: center;
  font-size: 26px; font-weight: 700;
}
.onboard-url {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 8px;
  margin: 18px 0;
  padding: 14px;
  background: var(--bg-alt);
  border: 1px solid var(--border);
  border-radius: 10px;
  font-size: 13px;
}
.onboard-url code, .onboard-url-inline {
  display: inline-block;
  color: var(--brand);
  font-size: 13px;
  /* A gym address is one unbroken token; let it wrap rather than widen the
     card past the viewport. */
  word-break: break-all;
}
.onboard-url-inline {
  padding: 10px 12px;
  background: var(--bg-alt);
  border: 1px solid var(--border);
  border-radius: 10px;
  width: 100%;
}
.onboard-next {
  margin: 0 0 20px; padding: 0;
  list-style: none;
  display: grid; gap: 8px;
  text-align: left;
  color: var(--muted);
  font-size: 14px;
}
.onboard-next li { padding-left: 22px; position: relative; }
.onboard-next li::before { content: '→'; position: absolute; left: 0; color: var(--brand); }

/* ---- operator console ---- */

.console-body { max-width: 1180px; margin: 0 auto; padding: clamp(16px, 3vw, 28px) clamp(16px, 5vw, 48px) 48px; }

.login-notice {
  margin: 0 0 16px;
  padding: 10px 12px;
  border-radius: 10px;
  background: rgba(251, 191, 36, 0.12);
  border: 1px solid rgba(251, 191, 36, 0.3);
  color: var(--amber);
  font-size: 13px;
}

/* ---------------------------------------------------------------- responsive */

/*
 * Three width breakpoints, plus two capability queries that are deliberately
 * independent of width:
 *
 *   900px  tablet down — sidebar becomes an off-canvas drawer
 *   700px  large phone — topbar and toolbars stack, chart labels grow
 *   560px  phone — modals go full-bleed, toasts span the width
 *
 *   pointer: coarse          — grow every tap target (a 1024px tablet needs
 *                              this as much as a 360px phone does, so it
 *                              can't be tied to a width)
 *   prefers-reduced-motion   — drop the drawer slide and the pulses
 *
 * The fluid grids (.cols-*, .form-grid) and the scroll wrappers already in
 * place (.table-wrap, the timetable) do most of the reflowing on their own;
 * these blocks handle what auto-fit can't express.
 */

/* Opens the drawer below 900px; there is no drawer to open above it. */
.nav-toggle { display: none; }
.nav-scrim { display: none; }

@media (max-width: 900px) {
  .shell { grid-template-columns: 1fr; }

  /*
   * An off-canvas drawer, where this used to lay the sidebar out as a wrapping
   * row of links. That row cost about four lines of vertical space before any
   * page content, and the padding it needed was clawed back by hiding
   * .sidebar-footer — which took "Password" and "Sign out" with it, leaving no
   * way to sign out on a phone. The drawer keeps the full nav, its section
   * headings and the footer.
   */
  .sidebar {
    position: fixed;
    top: 0; bottom: 0; left: 0;
    width: min(284px, 84vw);
    height: auto;
    z-index: 70;
    transform: translateX(-100%);
    transition: transform 0.22s ease;
    box-shadow: var(--shadow);
  }
  .sidebar.open { transform: none; }

  .nav-scrim {
    display: block;
    position: fixed; inset: 0;
    background: rgba(3, 6, 11, 0.62);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.22s ease;
    z-index: 65;
  }
  .nav-scrim.open { opacity: 1; pointer-events: auto; }

  /* Freeze the page behind an open drawer so a scroll gesture aimed at the
     nav doesn't move the page underneath it. */
  body.nav-open { overflow: hidden; }

  .nav-toggle { display: inline-flex; }

  /* Below 900px the main column runs edge to edge, so it — not the sidebar —
     owns the landscape notch clearance. */
  .content, .topbar {
    padding-left: calc(16px + var(--safe-left));
    padding-right: calc(16px + var(--safe-right));
  }
  .kv { grid-template-columns: 1fr; gap: 2px 0; }
  .kv dd { margin-bottom: 8px; }

  /* iOS Safari zooms the whole page when a focused field is under 16px. */
  input, select, textarea { font-size: 16px; }
}

@media (max-width: 700px) {
  .topbar { flex-wrap: wrap; gap: 10px; padding-top: calc(12px + var(--safe-top)); padding-bottom: 12px; }
  .topbar h1 { font-size: 18px; }
  /* Page actions drop onto their own full-width line under the title rather
     than squeezing it. :empty keeps views with no actions from paying for a
     blank row. */
  .topbar > .row { width: 100%; flex-wrap: wrap; }
  .topbar > .row:empty { display: none; }

  .content { padding-top: 18px; padding-bottom: calc(40px + var(--safe-bottom)); }

  .toolbar .search { flex-basis: 100%; min-width: 0; max-width: none; }
  .toolbar select { flex: 1 1 140px; min-width: 0; }

  .stat .value { font-size: 23px; }

  .dash-header { padding: 14px; }
  .dash-greeting { font-size: 18px; }
  .live-badge { flex: 1 1 100%; justify-content: center; }

  /* The address row is prefix + input + button. Below this width the prefix
     ("gymbook.fly.dev/g/") plus a usable input no longer fit on one line, so
     the field takes the full width and the button drops under it. */
  .landing-signin, .onboard-slug { flex-wrap: wrap; }
  .landing-signin-field, .onboard-slug { flex: 1 1 100%; }
  .landing-signin .btn { flex: 1 1 100%; justify-content: center; }

  /*
   * Chart text is sized in viewBox user units, so it shrinks with the chart:
   * 10 units in a 640-unit space rendered at phone width lands near 5px.
   * Safe to grow because every label the app plots is short — a weekday, a
   * month, an hour, a day-of-month.
   */
  .chart text { font-size: 15px; }

  .pagination { justify-content: center; flex-wrap: wrap; }
}

@media (max-width: 560px) {
  /* Full-bleed sheet: a centred card with side gutters spends width the form
     fields need more. */
  .modal-backdrop {
    padding: calc(10px + var(--safe-top)) calc(8px + var(--safe-right)) calc(24px + var(--safe-bottom))
      calc(8px + var(--safe-left));
    justify-items: stretch;
  }
  .modal, .modal.wide { width: 100%; }
  .modal-head { padding: 14px 16px; }
  .modal-body { padding: 16px; }
  .modal-foot { padding: 14px 16px; flex-wrap: wrap; }
  /* Share the row instead of stacking, so neither button lands under a thumb
     that was aiming for the other. */
  .modal-foot .btn { flex: 1 1 130px; justify-content: center; }
  .form-grid { grid-template-columns: 1fr; }

  .toasts {
    left: calc(12px + var(--safe-left));
    right: calc(12px + var(--safe-right));
    bottom: calc(12px + var(--safe-bottom));
  }
  .toast { max-width: none; }

  .content, .topbar {
    padding-left: calc(12px + var(--safe-left));
    padding-right: calc(12px + var(--safe-right));
  }
  .card { padding: 14px; }
  .empty { padding: 28px 16px; }

  .avatar.lg { width: 52px; height: 52px; font-size: 18px; }
  .checkin-box input { font-size: 18px; padding: 12px 14px; }

  .qr-preview { gap: 10px; }

  .timetable { grid-template-columns: repeat(7, minmax(140px, 1fr)); }

  .onboard {
    padding-left: calc(10px + var(--safe-left));
    padding-right: calc(10px + var(--safe-right));
  }
  .onboard-card { padding: 18px 16px; }
  /* The back button sits over the heading once the card narrows, so give it
     the row above instead of overlapping the title. */
  .onboard-back { position: static; display: inline-flex; margin-bottom: 12px; }
  .landing-top {
    padding-left: calc(14px + var(--safe-left));
    padding-right: calc(14px + var(--safe-right));
    flex-wrap: wrap;
  }
}

@media (hover: none) and (pointer: coarse) {
  .btn { padding: 11px 16px; }
  /* Table rows carry two or three of these, so this trades the 44px ideal for
     something that doesn't double the height of every row. */
  .btn.sm { padding: 9px 13px; }
  .btn.icon-only { padding: 10px; }
  .icon-btn { font-size: 22px; padding: 9px; }
  .nav-link { padding: 12px; font-size: 15px; }
  th, td { padding-top: 13px; padding-bottom: 13px; }
  .list-item { padding: 13px 0; }
}

@media (prefers-reduced-motion: reduce) {
  .sidebar, .nav-scrim, .btn, .bio-btn, .stat, .stat-icon, .card, .type-card,
  .landing-feature, .landing-feature-icon, tbody tr, input, select, textarea { transition: none; }
  .toast, .modal, .modal-backdrop, .install-banner { animation: none; }
  .bio-pulse, .live-pulse-core { animation: none; }
  .bio-btn:not(:disabled):hover, .stat:hover, .btn:active,
  .landing-feature:hover, .stat:hover .stat-icon, .landing-feature:hover .landing-feature-icon { transform: none; }
}

/* -------------------------------------------------------- Member Photo Picker */

.photo-picker {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 12px 14px;
  background: var(--surface-2);
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  margin-bottom: 16px;
  grid-column: 1 / -1;
}
.photo-picker-avatar {
  width: 72px;
  height: 72px;
  border-radius: 14px;
  background: var(--bg-alt);
  border: 2px solid var(--border);
  overflow: hidden;
  display: grid;
  place-items: center;
  flex: none;
}
.photo-picker-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.photo-picker-placeholder {
  font-size: 32px;
  color: var(--muted);
}
.photo-picker-controls {
  flex: 1;
  min-width: 0;
}

.photo-camera-container {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 8px 0;
}
.photo-camera-frame {
  position: relative;
  width: min(300px, 100%);
  aspect-ratio: 1 / 1;
  border-radius: 16px;
  overflow: hidden;
  background: #000;
  border: 2px solid var(--brand);
}
.photo-camera-video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.photo-camera-reticle {
  position: absolute;
  inset: 10%;
  border: 2px dashed rgba(249, 115, 22, 0.85);
  border-radius: 50%;
  box-shadow: 0 0 0 100vmax rgba(0, 0, 0, 0.4);
  pointer-events: none;
}

/* ------------------------------------------------------- installed app (PWA) */

/*
 * Everything below is only ever seen on a phone or tablet — either the prompt
 * to install (js/pwa.js) or the trim an installed window needs. Notch and
 * home-indicator clearance is not here; it lives with the layout rules that
 * own each edge, via the --safe-* variables at the top of this file.
 */

/* A one-time nudge. Above the page and its sticky topbar (20), but below the
   modal backdrop (60), the nav drawer (70) and the toasts (90) — it is the
   least urgent thing on screen and must never cover any of them. */
.install-banner {
  position: fixed;
  z-index: 30;
  left: calc(12px + var(--safe-left));
  right: calc(12px + var(--safe-right));
  bottom: calc(12px + var(--safe-bottom));
  margin-inline: auto;
  max-width: 460px;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-left: 3px solid var(--brand);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  animation: rise 0.25s ease;
}
/* The drawer's own scrim would leave it dimly visible underneath, which reads
   as a rendering fault rather than a deliberate layer. */
body.nav-open .install-banner { display: none; }

/* Toasts share the bottom edge with the banner; lift them clear of it while it
   is up (set by js/pwa.js) rather than stacking one on top of the other. */
body.has-install-banner .toasts { bottom: calc(96px + var(--safe-bottom)); }

.install-icon {
  width: 36px; height: 36px; border-radius: 10px; flex: none;
  background: var(--brand-soft); color: var(--brand);
  border: 1px solid color-mix(in srgb, var(--brand) 20%, transparent);
  display: grid; place-items: center;
}
.install-copy { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.install-copy strong { font-size: 14px; }
.install-copy .muted { font-size: 12px; }
.install-banner .btn { flex: none; }

/* The install button in the sidebar footer, hidden until a browser says an
   install is actually possible. */
.install-hidden { display: none !important; }

.install-steps ol { margin: 12px 0 0; padding-left: 20px; }
.install-steps li { margin-bottom: 10px; }
.install-steps strong { color: var(--text); }

/* The update prompt is a toast with buttons rather than a bare message. */
.toast-update { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.toast-update .btn { flex: none; }

@media (display-mode: standalone), (display-mode: fullscreen) {
  /* Nothing above the app to bounce against, so a rubber-band drag on iOS
     reads as the page coming loose from its frame. */
  body { overscroll-behavior-y: none; }
  /* Already installed — pwa.js suppresses the banner too, this covers the
     window being launched from a home-screen icon mid-session. */
  .install-banner { display: none; }
}

/* Removes the tap-then-hover delay browsers keep for double-tap zoom. */
.btn, .icon-btn, .nav-link, .bio-btn { touch-action: manipulation; }

/* A visible, permanent marker while the device has no connection — an
   installed app has no browser chrome to show it (see watchConnection). */
body.is-offline .topbar { border-bottom-color: var(--amber); }
body.is-offline .topbar::after {
  content: 'Offline';
  order: 99;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--amber);
  border: 1px solid rgba(251, 191, 36, 0.4);
  background: rgba(251, 191, 36, 0.1);
  border-radius: 999px;
  padding: 3px 9px;
}

/* ---------------------------------------------------------------- light mode
   Toggled via body[data-mode='light'] (see js/theme.js). Only surface/text
   variables are overridden — --brand* stays whatever the active color preset
   set, so accent colors carry across both modes. Selectors are duplicated
   per preset combo to match or beat that preset's specificity; a plain
   [data-mode='light'] rule would lose the cascade to e.g.
   body[data-vertical='gym'][data-theme='crimson']. */
body[data-mode='light'] {
  --bg: #f7f8fa;
  --bg-alt: #ffffff;
  --surface: #ffffff;
  --surface-2: #f1f3f6;
  --border: #e2e6ec;
  --text: #101418;
  --muted: #5b6472;
  /* Softer and cooler than the dark-mode set: a hard black shadow on a white
     page reads as dirt rather than depth. */
  --shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.05), 0 2px 6px -2px rgba(15, 23, 42, 0.05);
  --shadow: 0 10px 30px -5px rgba(15, 23, 42, 0.1);
  --shadow-lg: 0 24px 60px -12px rgba(15, 23, 42, 0.16);
  /* Inverted: a white top highlight is invisible on a white panel, so light
     mode draws the micro-border as the faintest possible shade instead. */
  --hairline: rgba(15, 23, 42, 0.05);
  color-scheme: light;
}
body[data-vertical='library'][data-mode='light'],
body[data-vertical='library'][data-theme='sapphire'][data-mode='light'],
body[data-vertical='library'][data-theme='violet'][data-mode='light'],
body[data-vertical='library'][data-theme='mocha'][data-mode='light'],
body[data-vertical='gym'][data-mode='light'],
body[data-vertical='gym'][data-theme='flame'][data-mode='light'],
body[data-vertical='gym'][data-theme='crimson'][data-mode='light'],
body[data-vertical='gym'][data-theme='cyber'][data-mode='light'],
body[data-vertical='gym'][data-theme='ultramarine'][data-mode='light'] {
  --bg: #f7f8fa;
  --bg-alt: #ffffff;
  --surface: #ffffff;
  --surface-2: #f1f3f6;
  --border: #e2e6ec;
  --text: #101418;
  --muted: #5b6472;
  --shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.05), 0 2px 6px -2px rgba(15, 23, 42, 0.05);
  --shadow: 0 10px 30px -5px rgba(15, 23, 42, 0.1);
  --shadow-lg: 0 24px 60px -12px rgba(15, 23, 42, 0.16);
  --hairline: rgba(15, 23, 42, 0.05);
  color-scheme: light;
}

/* No light-mode .topbar override: the glass is mixed from --bg, which the
   blocks above have already swapped for the light surface. */

body[data-mode='light'] .modal-backdrop {
  background: rgba(15, 23, 42, 0.45);
}

body[data-mode='light'] .login {
  background: radial-gradient(1000px 600px at 15% -10%, var(--brand-glow), transparent 60%), var(--bg);
}

/* ---------------------------------------------------------------- date field */

/* Replaces the native <input type="date">, whose on-screen digit order
   follows OS/browser locale and can't be forced to DD/MM/YYYY from app code. */
.date-field {
  position: relative;
  display: flex;
  align-items: stretch;
  width: 100%;
  max-width: 160px;
}
.form-grid .date-field { max-width: none; }
.date-field input[type='text'] {
  flex: 1;
  min-width: 0;
  padding-right: 36px;
}
.date-toggle {
  position: absolute;
  right: 4px;
  top: 50%;
  transform: translateY(-50%);
  background: none;
  border: none;
  color: var(--muted);
  cursor: pointer;
  padding: 6px;
  display: inline-grid;
  place-items: center;
  border-radius: var(--radius-sm);
  transition: background 0.15s ease, color 0.15s ease;
}
.date-toggle:hover { background: var(--surface-2); color: var(--text); }
.date-toggle:focus-visible { outline: none; box-shadow: var(--ring); }

.date-panel {
  position: fixed;
  z-index: 1000;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg), inset 0 1px 0 var(--hairline);
  padding: 12px;
  width: 260px;
  max-width: calc(100vw - 16px);
}
.date-panel-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 10px;
  font-size: 13px;
  font-weight: 600;
}
.date-panel-head .icon-btn {
  font-size: 16px;
  padding: 4px 9px;
}
.date-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 3px;
}
.date-weekdays {
  margin-bottom: 4px;
  font-size: 11px;
  color: var(--muted);
  text-align: center;
}
.date-weekdays span { padding: 4px 0; }
.date-cell {
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  background: none;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  color: var(--text);
  font-size: 13px;
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}
.date-cell:hover { background: var(--surface); border-color: var(--border); }
.date-cell.today { border-color: var(--brand); color: var(--brand); font-weight: 600; }
.date-cell.selected { background: var(--brand); color: #fff; border-color: var(--brand); }
.date-cell.empty { cursor: default; }

/* ============================================================ Member Portal
 * The member/student self-service app (public/js/views/portal.js) — a
 * different shell from .shell above: no sidebar, no desktop table density,
 * designed to read as a native phone app first. Every colour here is a
 * var(--...) token already redefined per vertical/theme/light-dark mode
 * elsewhere in this file, so the portal inherits a gym's palette for free.
 * ========================================================================= */

.portal-frame {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  justify-content: center;
  background: var(--bg-alt);
}

.portal-app {
  width: 100%;
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  position: relative;
}

/* On a wide screen the app sits inside a centred "phone" viewport instead of
   stretching edge to edge — stretched, it would read as an admin page. Comes
   after the unconditional .portal-app rule above on purpose: a media-query
   block earlier in the cascade loses to a later plain rule of equal
   specificity even when the query doesn't match, so the override has to sit
   last to actually win at desktop widths. */
@media (min-width: 640px) {
  .portal-frame {
    align-items: center;
    padding: 32px 16px;
    background: radial-gradient(1200px 700px at 50% -10%, var(--brand-glow), transparent 60%), var(--bg-alt);
  }
  .portal-app {
    width: 412px;
    min-height: 0;
    height: min(890px, calc(100vh - 64px));
    border-radius: 40px;
    box-shadow: var(--shadow-lg), 0 0 0 8px var(--bg), 0 0 0 9px var(--border);
    overflow: hidden;
  }
}

.portal-topbar {
  display: flex; align-items: center; gap: 10px;
  padding: calc(14px + var(--safe-top)) 18px 14px;
  border-bottom: 1px solid var(--border);
  background: color-mix(in srgb, var(--bg) 80%, transparent);
  backdrop-filter: blur(14px) saturate(180%);
  -webkit-backdrop-filter: blur(14px) saturate(180%);
  position: sticky; top: 0; z-index: 20; flex: none;
}
.portal-topbar-logo, .portal-topbar-logo-img { width: 28px; height: 28px; border-radius: 8px; flex-shrink: 0; }
.portal-topbar-logo {
  display: grid; place-items: center;
  background: var(--brand-soft); color: var(--brand);
  border: 1px solid color-mix(in srgb, var(--brand) 22%, transparent);
}
.portal-topbar-logo-img { object-fit: cover; border: 1px solid var(--border); }
.portal-topbar-name { font-weight: 700; font-size: 15px; letter-spacing: -0.01em; }

.portal-topbar-actions { display: flex; align-items: center; gap: 8px; flex: none; }
/* The one QR affordance that is on every screen, not just the Pass tab —
   Pass isn't always a bottom tab (see buildTabs in portal.js), so this is
   what keeps "show my code" a single tap from anywhere in the app. */
.portal-qr-btn {
  display: grid; place-items: center; flex: none;
  width: 32px; height: 32px; border-radius: 10px;
  border: 1px solid var(--border); background: var(--surface-2); color: var(--text);
  cursor: pointer; transition: transform 0.15s ease, border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}
.portal-qr-btn:hover { border-color: color-mix(in srgb, var(--brand) 35%, var(--border)); color: var(--brand); }
.portal-qr-btn:active { transform: scale(0.9); }

/* A quiet pulse on the status dot, only when it means "active" — a throbbing
   badge on frozen/expired would read as an alarm, not a status light. */
@keyframes portal-dot-pulse {
  0%, 100% { box-shadow: 0 0 0 2px color-mix(in srgb, currentColor 24%, transparent); }
  50% { box-shadow: 0 0 0 5px color-mix(in srgb, currentColor 14%, transparent); }
}
.portal-topbar-actions .badge.dot.green::before { animation: portal-dot-pulse 2.2s ease-in-out infinite; }

.portal-content {
  flex: 1; overflow-y: auto; -webkit-overflow-scrolling: touch;
  padding: 18px 18px calc(100px + var(--safe-bottom));
}
.portal-tab-body { display: flex; flex-direction: column; gap: 18px; animation: portal-fade-in 0.22s ease; }
@keyframes portal-fade-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }

.portal-tab-title { margin: 0; font-size: 20px; }
.portal-section-title { margin: 4px 0 -6px; font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.04em; }
.portal-loading, .portal-empty {
  display: grid; place-items: center; padding: 40px 0; color: var(--muted); text-align: center; gap: 10px; font-size: 13.5px;
}
.portal-error { display: flex; flex-direction: column; align-items: center; gap: 12px; padding: 40px 0; text-align: center; color: var(--muted); }

/* ---------------------------------------------------------------- tab bar */

.portal-tabbar {
  position: sticky; bottom: 0; z-index: 20; flex: none;
  display: flex; gap: 4px;
  margin: 0 12px calc(12px + var(--safe-bottom));
  padding: 7px;
  border-radius: 22px;
  border: 1px solid var(--border);
  background: color-mix(in srgb, var(--surface) 72%, transparent);
  backdrop-filter: blur(18px) saturate(180%);
  -webkit-backdrop-filter: blur(18px) saturate(180%);
  box-shadow: var(--shadow-lg), inset 0 1px 0 var(--hairline);
}
.portal-tab {
  flex: 1; display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 9px 2px 7px; border: none; background: none; color: var(--muted);
  border-radius: 16px; font-size: 11px; font-weight: 600; cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, transform 0.15s ease;
}
.portal-tab span { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.portal-tab:active { transform: scale(0.92); }
.portal-tab.active { color: var(--brand); background: var(--brand-soft); box-shadow: 0 2px 12px -4px var(--brand-glow); }

/* --------------------------------------------------------------- greeting */

.portal-greeting h2 { margin: 0 0 2px; font-size: 21px; }
.portal-greeting p { margin: 0; color: var(--muted); font-size: 13.5px; }

/* ---------------------------------------------------------------- hero card */

.portal-hero-card {
  position: relative; overflow: hidden;
  border-radius: 22px; padding: 20px;
  border: 1px solid color-mix(in srgb, var(--brand) 30%, var(--border));
  background: linear-gradient(150deg, color-mix(in srgb, var(--brand) 20%, var(--surface)) 0%, var(--surface-2) 65%, var(--surface) 100%);
  box-shadow: var(--shadow), inset 0 1px 0 var(--hairline);
  display: flex; flex-direction: column; gap: 16px;
}
/* The holographic sheen: a soft rotating light, mixed from the brand colour so
   it reads as foil on every theme instead of one hardcoded gradient. */
.portal-hero-glow {
  position: absolute; inset: -40%; z-index: 0; pointer-events: none;
  background: conic-gradient(from 0deg, transparent 0deg, color-mix(in srgb, var(--brand) 35%, transparent) 90deg, transparent 180deg, color-mix(in srgb, #fff 18%, transparent) 260deg, transparent 360deg);
  opacity: 0.5; mix-blend-mode: screen;
  animation: portal-hero-spin 14s linear infinite;
}
@keyframes portal-hero-spin { to { transform: rotate(360deg); } }
.portal-hero-top { position: relative; z-index: 1; display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.portal-hero-info { flex: 1; min-width: 0; }
.portal-hero-label { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; }
.portal-hero-plan { font-size: 19px; font-weight: 700; margin-top: 2px; letter-spacing: -0.01em; }
.portal-hero-ring-wrap { position: relative; flex-shrink: 0; display: grid; place-items: center; }
.portal-ring-track { stroke: color-mix(in srgb, var(--text) 12%, transparent); }
.portal-ring-fill { stroke: var(--brand); stroke-linecap: round; transition: stroke-dashoffset 0.4s ease; }
.portal-hero-ring-text {
  position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; line-height: 1;
}
.portal-hero-ring-text strong { font-size: 17px; }
.portal-hero-ring-text span { font-size: 9.5px; color: var(--muted); text-transform: uppercase; }
.portal-hero-bottom {
  position: relative; z-index: 1; display: flex; align-items: center; justify-content: space-between; gap: 10px;
  font-size: 13px; color: var(--muted);
}
.portal-hero-pill {
  background: color-mix(in srgb, var(--brand) 16%, transparent); color: var(--brand);
  border: 1px solid color-mix(in srgb, var(--brand) 30%, transparent);
  border-radius: 999px; padding: 3px 10px; font-size: 12px; font-weight: 600;
}

/* ------------------------------------------------------------ quick actions */

.portal-quick-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
.portal-quick-btn {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: 12px 4px; border-radius: 16px; border: 1px solid var(--border);
  background: var(--surface); color: var(--text); cursor: pointer;
  font-size: 11px; font-weight: 600; text-align: center;
  box-shadow: inset 0 1px 0 var(--hairline);
  transition: transform 0.12s ease, border-color 0.15s ease, background 0.15s ease;
}
.portal-quick-btn:hover { border-color: color-mix(in srgb, var(--brand) 35%, var(--border)); }
.portal-quick-btn:active { transform: translateY(1px) scale(0.98); }
.portal-quick-icon {
  width: 34px; height: 34px; border-radius: 11px; display: grid; place-items: center;
  background: var(--brand-soft); color: var(--brand);
}

/* -------------------------------------------------------------- mini stats */

.portal-stat-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.portal-mini-stat {
  padding: 12px 10px; border-radius: 16px; border: 1px solid var(--border); background: var(--surface);
  display: flex; flex-direction: column; gap: 4px; box-shadow: inset 0 1px 0 var(--hairline);
}
.portal-mini-stat-icon { color: var(--brand); }
.portal-mini-stat-value { font-size: 19px; font-weight: 700; letter-spacing: -0.01em; }
.portal-mini-stat-label { font-size: 10.5px; color: var(--muted); }

/* ------------------------------------------------------------ today's focus */
/* Home's 1-tap Start Workout card and calorie-ring snippet — stacked full
   width like every other Home card rather than a side-by-side grid, since
   the macro stack needs more room than half a 360px phone gives it. */
.portal-focus-grid { display: flex; flex-direction: column; gap: 10px; }
.portal-focus-card {
  position: relative; overflow: hidden; text-align: left; width: 100%;
  display: flex; flex-direction: column; gap: 10px;
  padding: 16px; border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  background: linear-gradient(150deg, var(--surface) 0%, var(--bg-alt) 100%);
  box-shadow: var(--shadow), inset 0 1px 0 var(--hairline);
  font: inherit; color: inherit;
}
button.portal-focus-card { cursor: pointer; transition: transform 0.15s ease, border-color 0.15s ease; }
button.portal-focus-card:hover { border-color: color-mix(in srgb, var(--brand) 30%, var(--border)); }
button.portal-focus-card:active { transform: scale(0.98); }
.portal-focus-kicker { font-size: 10.5px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; color: var(--brand); }
.portal-focus-title { font-size: 16.5px; font-weight: 700; letter-spacing: -0.01em; }
.portal-focus-chips { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.portal-focus-chips .muted { font-size: 11.5px; }
.portal-focus-diet { flex-direction: row; align-items: center; gap: 16px; }
.portal-focus-diet .portal-macro-stack { min-width: 0; gap: 8px; }

/* ---------------------------------------------------------------- sections */

.portal-section { display: flex; flex-direction: column; gap: 10px; }
.portal-section h3 { margin: 0; font-size: 15px; }

/* -------------------------------------------------------------- seat/class */

.portal-seat-card, .portal-locker-card, .portal-class-card, .portal-payment-row, .portal-plan-card {
  display: flex; align-items: center; gap: 12px;
  padding: 14px; border-radius: 16px; border: 1px solid var(--border); background: var(--surface);
  box-shadow: inset 0 1px 0 var(--hairline);
}
.portal-seat-code {
  width: 46px; height: 46px; border-radius: 13px; flex-shrink: 0;
  display: grid; place-items: center; font-weight: 700; font-size: 14px;
  background: var(--brand-soft); color: var(--brand);
}
.portal-seat-meta { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; font-size: 13px; }
.portal-seat-until { font-size: 12px; color: var(--muted); text-align: right; flex-shrink: 0; }

.portal-locker-icon {
  width: 40px; height: 40px; border-radius: 12px; display: grid; place-items: center; flex-shrink: 0;
  background: var(--brand-soft); color: var(--brand);
}
.portal-locker-code { font-weight: 700; }

.portal-day-strip {
  display: flex; gap: 8px; overflow-x: auto; padding-bottom: 4px;
  scroll-snap-type: x proximity; -webkit-overflow-scrolling: touch;
}
.portal-day-pill {
  scroll-snap-align: start; flex: none; display: flex; flex-direction: column; align-items: center; gap: 2px;
  width: 52px; padding: 8px 0; border-radius: 14px; border: 1px solid var(--border); background: var(--surface);
  color: var(--text); cursor: pointer; font-size: 11px;
}
.portal-day-pill span { color: var(--muted); text-transform: uppercase; font-size: 10px; }
.portal-day-pill strong { font-size: 15px; }
.portal-day-pill.active { background: var(--brand); border-color: var(--brand); color: #1a1005; }
.portal-day-pill.active span { color: inherit; opacity: 0.85; }

.portal-class-list { display: flex; flex-direction: column; gap: 10px; }
.portal-class-time { font-weight: 700; font-size: 13px; width: 56px; flex-shrink: 0; color: var(--brand); }
.portal-class-meta { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.portal-class-name { font-weight: 600; font-size: 14px; }
.portal-class-capacity { font-size: 11.5px; color: var(--green); }
.portal-class-capacity.full { color: var(--red); }

/* ----------------------------------------------------------------- wallet */

.portal-wallet-card {
  display: flex; flex-direction: column; gap: 16px; align-items: center;
  padding: 22px 18px; border-radius: 24px; border: 1px solid var(--border);
  background: linear-gradient(165deg, var(--surface) 0%, var(--surface-2) 100%);
  box-shadow: var(--shadow), inset 0 1px 0 var(--hairline);
}
.portal-wallet-top { width: 100%; display: flex; align-items: center; gap: 12px; }
.portal-wallet-avatar { width: 46px; height: 46px; border-radius: 14px; object-fit: cover; flex-shrink: 0; }
.portal-wallet-avatar-fallback { display: grid; place-items: center; background: var(--brand-soft); color: var(--brand); font-weight: 700; }
.portal-wallet-meta { flex: 1; min-width: 0; }
.portal-wallet-name { font-weight: 700; font-size: 15px; }
.portal-wallet-code { font-size: 12px; color: var(--muted); }
.portal-wallet-top .badge { margin-left: auto; flex-shrink: 0; }

.portal-qr-wrap {
  position: relative; width: 200px; height: 200px; border-radius: 18px; overflow: hidden;
  background: #fff; display: grid; place-items: center; padding: 12px;
}
.portal-qr-img { width: 100%; height: 100%; }
.portal-qr-img svg { width: 100%; height: 100%; display: block; }
/* The "anti-screenshot" radar: a slim conic sweep endlessly circling the
   code — cosmetic, signalling "this is a live, scan-ready pass" the way a
   boarding-pass app pulses its barcode. */
.portal-qr-radar {
  position: absolute; inset: 0; pointer-events: none;
  background: conic-gradient(from 0deg, transparent 0deg, color-mix(in srgb, var(--brand) 55%, transparent) 14deg, transparent 30deg);
  animation: portal-radar-spin 3.2s linear infinite;
  mix-blend-mode: multiply;
}
@keyframes portal-radar-spin { to { transform: rotate(360deg); } }
.portal-qr-ticker {
  font: 11px/1 ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
  letter-spacing: 0.08em; color: var(--muted);
}

.portal-pass-fullscreen {
  position: fixed; inset: 0; z-index: 1000;
  background: #fff; color: #111;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 14px;
  padding: calc(20px + var(--safe-top)) 20px calc(20px + var(--safe-bottom));
}
.portal-pass-fullscreen-qr { width: min(78vw, 340px); height: min(78vw, 340px); }
.portal-pass-fullscreen-qr svg { width: 100%; height: 100%; }
.portal-pass-fullscreen-name { font-size: 18px; font-weight: 700; }
.portal-pass-fullscreen-code { font-size: 13px; color: #555; letter-spacing: 0.04em; }
.portal-pass-fullscreen-close {
  position: absolute; top: calc(16px + var(--safe-top)); right: calc(16px + var(--safe-right));
  width: 36px; height: 36px; border-radius: 50%; border: 1px solid #ddd; background: #f5f5f5; color: #111;
  display: grid; place-items: center; cursor: pointer;
}

/* --------------------------------------------------------------- invoices */

.portal-plan-card { flex-direction: column; align-items: stretch; gap: 8px; }
.portal-plan-name { font-weight: 700; font-size: 15px; }
.portal-plan-row { display: flex; align-items: center; justify-content: space-between; font-size: 13px; color: var(--muted); }
.portal-plan-row span:last-child { color: var(--text); font-weight: 600; }

.portal-due-banner {
  display: flex; align-items: center; gap: 8px;
  padding: 12px 14px; border-radius: 14px; font-size: 13px; font-weight: 600;
  background: rgba(248, 113, 113, 0.12); color: var(--red); border: 1px solid rgba(248, 113, 113, 0.28);
}

.portal-payment-icon {
  width: 36px; height: 36px; border-radius: 11px; display: grid; place-items: center; flex-shrink: 0;
  background: var(--brand-soft); color: var(--brand);
}
.portal-payment-meta { flex: 1; min-width: 0; font-size: 13px; }
.portal-payment-amount { font-weight: 700; font-size: 14px; flex-shrink: 0; }

.portal-plan-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; }
.portal-plan-tile {
  padding: 12px; border-radius: 14px; border: 1px solid var(--border); background: var(--surface);
  box-shadow: inset 0 1px 0 var(--hairline);
}
.portal-plan-tile-name { font-size: 12.5px; font-weight: 600; }
.portal-plan-tile-price { font-size: 17px; font-weight: 700; margin: 2px 0; }
.portal-renew-hint { text-align: center; font-size: 12.5px; margin: -4px 0 0; }

/* ---------------------------------------------------------------- profile */

.portal-profile-card {
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  padding: 22px; border-radius: 20px; border: 1px solid var(--border); background: var(--surface);
  box-shadow: inset 0 1px 0 var(--hairline);
}
.portal-profile-avatar { width: 64px; height: 64px; border-radius: 20px; object-fit: cover; }
.portal-profile-avatar-fallback { display: grid; place-items: center; background: var(--brand-soft); color: var(--brand); font-weight: 700; font-size: 20px; }
.portal-profile-name { font-weight: 700; font-size: 16px; }
.portal-profile-row {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 12px 14px; border-radius: 14px; border: 1px solid var(--border); background: var(--surface);
  font-size: 13px;
}
.portal-pin-banner {
  display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap;
  padding: 12px 14px; border-radius: 14px; font-size: 12.5px; font-weight: 600;
  background: rgba(251, 191, 36, 0.12); color: var(--amber); border: 1px solid rgba(251, 191, 36, 0.28);
}
.portal-pin-banner > div:first-child { display: flex; align-items: center; gap: 6px; }
.portal-settings-btn { justify-content: flex-start; gap: 10px; padding: 12px 14px; }
.portal-support-body p { margin: 0 0 10px; }

/* -------------------------------------------------------- audio & haptics */

.portal-audio-settings {
  display: flex; flex-direction: column; gap: 2px;
  padding: 4px 14px; border-radius: 14px;
  border: 1px solid var(--border); background: var(--surface);
  box-shadow: inset 0 1px 0 var(--hairline);
}
.portal-audio-row {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 12px 0; font-size: 13px;
}
.portal-audio-row + .portal-audio-row { border-top: 1px solid var(--border); }
.portal-audio-row-label { display: flex; align-items: center; gap: 9px; color: var(--text); }
.portal-audio-row-label .svg-icon { color: var(--muted); flex: none; }

.portal-switch {
  flex: none; width: 42px; height: 25px; padding: 2px; border-radius: 999px;
  border: 1px solid var(--border); background: var(--surface-2); cursor: pointer;
  display: flex; align-items: center;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.portal-switch-thumb {
  width: 19px; height: 19px; border-radius: 50%; background: var(--muted);
  transition: transform 0.15s ease, background 0.15s ease;
}
.portal-switch.on { background: var(--brand); border-color: var(--brand); }
.portal-switch.on .portal-switch-thumb { transform: translateX(17px); background: #fff; }
.portal-switch:focus-visible { outline: none; box-shadow: var(--ring); }

.portal-segmented {
  display: inline-flex; padding: 3px; border-radius: 10px;
  border: 1px solid var(--border); background: var(--surface-2); gap: 2px;
}
.portal-segment {
  border: none; background: none; color: var(--muted); cursor: pointer;
  padding: 5px 10px; border-radius: 7px; font-size: 11.5px; font-weight: 700;
}
.portal-segment.active { background: var(--brand); color: #fff; }

.portal-sound-preview-btn:disabled { opacity: 0.5; cursor: default; }

/* ------------------------------------------------------------------ login */

.portal-login {
  min-height: 100vh; min-height: 100dvh;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 26px;
  padding: calc(28px + var(--safe-top)) calc(24px + var(--safe-right)) calc(28px + var(--safe-bottom)) calc(24px + var(--safe-left));
  background: radial-gradient(1000px 600px at 50% -10%, var(--brand-glow), transparent 60%), var(--bg);
}
.portal-login-brand { display: flex; flex-direction: column; align-items: center; gap: 10px; }
.portal-login-logo, .portal-login-logo-img { width: 52px; height: 52px; border-radius: 16px; }
.portal-login-logo {
  display: grid; place-items: center; background: var(--brand-soft); color: var(--brand);
  border: 1px solid color-mix(in srgb, var(--brand) 22%, transparent);
  box-shadow: inset 0 1px 0 var(--hairline);
}
.portal-login-logo-img { object-fit: cover; border: 1px solid var(--border); }
.portal-login-gymname { font-weight: 700; font-size: 17px; }

.portal-login-card {
  width: min(360px, 100%);
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
  padding: 28px 24px; box-shadow: var(--shadow-lg), inset 0 1px 0 var(--hairline);
  display: flex; flex-direction: column; gap: 14px;
}
.portal-login-title { margin: 0; font-size: 21px; }
.portal-login-sub { margin: -8px 0 0; color: var(--muted); font-size: 13px; }
.portal-login-error { margin: 0; color: var(--red); font-size: 13px; }
.portal-login-foot { margin: 4px 0 0; text-align: center; font-size: 13px; }
.portal-back-link {
  align-self: flex-start; border: none; background: none; color: var(--muted); cursor: pointer;
  font-size: 13px; padding: 0; margin-bottom: -4px;
}
.portal-back-link:hover { color: var(--text); }

.portal-field { display: flex; }
.portal-input {
  width: 100%; padding: 12px 14px; border-radius: var(--radius-sm); border: 1px solid var(--border);
  background: var(--bg); color: var(--text); font-size: 15px;
}
.portal-input:focus { outline: none; border-color: color-mix(in srgb, var(--brand) 40%, var(--border)); box-shadow: var(--ring); }

.portal-pin-dots { display: flex; justify-content: center; gap: 12px; padding: 6px 0; }
.portal-pin-dot {
  width: 13px; height: 13px; border-radius: 50%; border: 1.5px solid var(--border); background: none;
  transition: background 0.15s ease, border-color 0.15s ease;
}
.portal-pin-dot.filled { background: var(--brand); border-color: var(--brand); }

.portal-keypad { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.portal-key {
  padding: 14px 0; border-radius: 16px; border: 1px solid var(--border); background: var(--surface-2);
  color: var(--text); font-size: 19px; font-weight: 600; cursor: pointer;
  display: grid; place-items: center;
  box-shadow: inset 0 1px 0 var(--hairline);
  transition: background 0.12s ease, transform 0.1s ease;
}
.portal-key:active { transform: translateY(1px) scale(0.97); background: color-mix(in srgb, var(--brand) 10%, var(--surface-2)); }
.portal-key-empty { visibility: hidden; }
.portal-key-action { color: var(--muted); }

/* ------------------------------------------------------------- responsive */

@media (max-width: 380px) {
  .portal-quick-grid { gap: 6px; }
  .portal-content { padding: 14px 14px calc(100px + var(--safe-bottom)); }
}

/* ══════════════════════════════════════════ Diet & workout tracking ══════ */

/*
 * Everything here is built from the palette tokens rather than literal colours,
 * so it follows the vertical, the colour preset and light/dark mode without a
 * per-theme override — see the theme block above.
 *
 * The three macro colours are fixed roles, not brand accents: protein reads
 * blue, carbs amber, fats coral, in the staff builder and the member's rings
 * alike. A member who learns "the blue bar is protein" must not have that
 * meaning move when the gym picks a different accent.
 */
:root {
  --macro-protein: var(--blue);
  --macro-carbs: var(--amber);
  --macro-fats: var(--red);
  --macro-kcal: var(--brand);
}

.hidden { display: none !important; }

/* ------------------------------------------------------- staff: tab shell */

.fit-tabs {
  display: flex; gap: 6px; flex-wrap: wrap;
  border-bottom: 1px solid var(--border); padding-bottom: 10px;
}
.fit-tab {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 8px 14px; border-radius: 999px;
  border: 1px solid var(--border); background: var(--surface-2); color: var(--muted);
  font-size: 13px; font-weight: 600; cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.fit-tab:hover { color: var(--text); }
.fit-tab.active { background: var(--brand-soft); color: var(--brand); border-color: var(--brand); }
.fit-tab:focus-visible { outline: none; box-shadow: var(--ring); }

/* The tab panel itself owns no layout — each tab renders its own — but it is
   the node paintBody() replaces, so it needs to exist as a stable box. */
.fit-tab-body { min-height: 200px; }

.fit-tab-head {
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
  flex-wrap: wrap; margin-bottom: 16px;
}
.fit-tab-head p { margin: 0; font-size: 13px; max-width: 62ch; }

/* renderIcon draws a block-level <svg> (see .svg-icon above), so any heading
   that carries one has to be its own flex line — otherwise the icon takes a
   whole row above the text. Every card title in this feature has an icon. */
.fit-card-title { display: flex; align-items: center; gap: 7px; }

/* -------------------------------------------------------- staff: plan cards */

.fit-card-grid {
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}
.fit-plan-card { display: flex; flex-direction: column; gap: 12px; }
.fit-plan-head { display: flex; gap: 12px; align-items: flex-start; }
.fit-plan-icon {
  flex: none; width: 40px; height: 40px; border-radius: 12px;
  display: grid; place-items: center;
  background: var(--brand-soft); color: var(--brand);
  box-shadow: inset 0 1px 0 var(--hairline);
}
.fit-plan-icon.diet { background: rgba(52, 211, 153, 0.14); color: var(--green); }
.fit-plan-name { font-size: 15.5px; font-weight: 700; letter-spacing: -0.01em; line-height: 1.3; }
.fit-plan-desc { margin: 0; font-size: 13px; line-height: 1.5; }

.fit-plan-stats {
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 8px;
  padding: 10px 0; border-top: 1px solid var(--border); border-bottom: 1px solid var(--border);
}
.fit-plan-stats > div { display: flex; flex-direction: column; gap: 1px; text-align: center; }
.fit-plan-stats strong { font-size: 17px; font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }
.fit-plan-stats span { font-size: 10.5px; color: var(--muted); }

.fit-macro-pills { display: flex; flex-wrap: wrap; gap: 6px; }
.fit-pill {
  padding: 3px 9px; border-radius: 999px; font-size: 11.5px; font-weight: 700;
  border: 1px solid transparent; font-variant-numeric: tabular-nums;
}
.fit-pill.kcal { background: var(--brand-soft); color: var(--macro-kcal); border-color: var(--brand); }
.fit-pill.protein { background: rgba(56, 189, 248, 0.14); color: var(--macro-protein); border-color: rgba(56, 189, 248, 0.3); }
.fit-pill.carbs { background: rgba(251, 191, 36, 0.14); color: var(--macro-carbs); border-color: rgba(251, 191, 36, 0.3); }
.fit-pill.fats { background: rgba(248, 113, 113, 0.14); color: var(--macro-fats); border-color: rgba(248, 113, 113, 0.3); }

.fit-member-card { display: flex; flex-direction: column; }

/* ---------------------------------------------------- staff: plan builders */

.fit-builder-wrap { margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--border); }
.fit-builder-title { margin: 0 0 12px; font-size: 13px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); }
.fit-builder { display: flex; flex-direction: column; gap: 12px; }

.fit-day-card {
  display: flex; flex-direction: column; gap: 10px;
  padding: 14px; border-radius: var(--radius);
  border: 1px solid var(--border); background: var(--bg-alt);
  box-shadow: inset 0 1px 0 var(--hairline);
}
.fit-day-head { display: flex; align-items: center; gap: 8px; }
.fit-day-name, .fit-day-notes, .fit-meal-time {
  border: 1px solid var(--border); background: var(--surface); color: var(--text);
  border-radius: var(--radius-sm); padding: 8px 10px; font-size: 13.5px; font-family: inherit;
}
.fit-day-name { flex: 1; font-weight: 600; min-width: 0; }
.fit-day-notes { width: 100%; font-size: 12.5px; }
.fit-meal-time { flex: none; width: 110px; }
.fit-day-name:focus, .fit-day-notes:focus, .fit-meal-time:focus {
  outline: none; border-color: var(--brand); box-shadow: var(--ring);
}

/*
 * The set/food grids. Explicit columns rather than a table because every cell
 * is an input and a <table> would fight the wrapping below; the header row
 * shares the same grid so the labels stay over their columns.
 */
.fit-ex-list { display: flex; flex-direction: column; gap: 6px; }
.fit-ex-row, .fit-item-row {
  display: grid; align-items: center; gap: 6px;
}
.fit-ex-row { grid-template-columns: 24px minmax(120px, 1fr) 110px 58px 76px 66px 30px 30px 30px; }
.fit-item-row { grid-template-columns: 24px minmax(120px, 1fr) 90px 66px 62px 62px 62px 30px; }
.fit-ex-header {
  font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--muted); font-weight: 700; padding-bottom: 2px;
}
.fit-ex-handle {
  display: grid; place-items: center; font-size: 11px; color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.fit-cell-name, .fit-cell-group, .fit-cell-num, .fit-cell-reps {
  border: 1px solid var(--border); background: var(--surface); color: var(--text);
  border-radius: var(--radius-sm); padding: 7px 8px; font-size: 13px; font-family: inherit;
  min-width: 0; width: 100%;
}
.fit-cell-num, .fit-cell-reps { text-align: center; font-variant-numeric: tabular-nums; }
.fit-cell-name:focus, .fit-cell-group:focus, .fit-cell-num:focus, .fit-cell-reps:focus {
  outline: none; border-color: var(--brand); box-shadow: var(--ring);
}

.fit-meal-total {
  display: flex; gap: 14px; justify-content: flex-end; align-items: baseline;
  padding-top: 8px; margin-top: 2px; border-top: 1px dashed var(--border);
  font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums;
}
.fit-meal-total strong { font-size: 14px; color: var(--macro-kcal); }

.fit-empty-inline {
  padding: 14px; text-align: center; font-size: 12.5px; color: var(--muted);
  border: 1px dashed var(--border); border-radius: var(--radius-sm);
}

.icon-btn.sm { padding: 5px; font-size: 13px; line-height: 1; }
.icon-btn.danger { color: var(--red); }
.icon-btn.danger:hover { background: rgba(248, 113, 113, 0.1); border-color: rgba(248, 113, 113, 0.35); }
.icon-btn:disabled { opacity: 0.35; cursor: default; }
.icon-btn:disabled:hover { background: none; border-color: transparent; }

/* ------------------------------------------------------- staff: previews */

.fit-preview { display: flex; flex-direction: column; gap: 18px; }
.fit-preview-day h4 { margin: 0 0 8px; font-size: 14px; }
.fit-preview-day p { margin: 0 0 8px; font-size: 12.5px; }

.fit-library-controls { display: flex; flex-direction: column; gap: 10px; margin-bottom: 12px; }
.fit-library-controls input[type='search'] {
  width: 100%; border: 1px solid var(--border); background: var(--surface-2); color: var(--text);
  border-radius: var(--radius-sm); padding: 9px 12px; font-size: 13.5px; font-family: inherit;
}
.fit-library-controls input[type='search']:focus { outline: none; border-color: var(--brand); box-shadow: var(--ring); }
.fit-chip-row { display: flex; gap: 6px; flex-wrap: wrap; }
.fit-chip {
  border: 1px solid var(--border); background: var(--surface-2); color: var(--muted);
  padding: 5px 12px; border-radius: 999px; font-size: 12px; font-weight: 600;
  cursor: pointer; text-transform: capitalize;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.fit-chip:hover { color: var(--text); }
.fit-chip.active { background: var(--brand-soft); color: var(--brand); border-color: var(--brand); }
.fit-count { font-size: 12px; margin-bottom: 8px; }

/* ═══════════════════════════════════════════════ member portal: paywall ══ */

.portal-paywall {
  position: relative; overflow: hidden;
  padding: 26px 20px 20px; border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  background: linear-gradient(160deg, var(--surface) 0%, var(--bg-alt) 100%);
  box-shadow: var(--shadow), inset 0 1px 0 var(--hairline);
  text-align: center;
}
.portal-paywall-glow {
  position: absolute; inset: -60% 20% auto; height: 220px; pointer-events: none;
  background: radial-gradient(circle at 50% 50%, var(--brand-glow) 0%, transparent 70%);
}
.portal-paywall-badge {
  position: relative; display: inline-flex; align-items: center; gap: 5px;
  padding: 4px 11px; border-radius: 999px; margin-bottom: 12px;
  background: var(--brand-soft); color: var(--brand);
  border: 1px solid var(--brand);
  font-size: 11px; font-weight: 800; text-transform: uppercase; letter-spacing: 0.06em;
}
.portal-paywall-title { position: relative; margin: 0 0 6px; font-size: 22px; letter-spacing: -0.02em; }
.portal-paywall-sub { position: relative; margin: 0 0 20px; font-size: 13.5px; color: var(--muted); line-height: 1.5; }

.portal-paywall-features { position: relative; display: flex; flex-direction: column; gap: 12px; text-align: left; }
.portal-paywall-feature { display: flex; gap: 12px; align-items: flex-start; }
.portal-paywall-feature-icon {
  flex: none; width: 34px; height: 34px; border-radius: 10px; display: grid; place-items: center;
  background: var(--surface-2); color: var(--brand); border: 1px solid var(--border);
}
.portal-paywall-feature strong { font-size: 13.5px; }
.portal-paywall-feature p { margin: 2px 0 0; font-size: 12.5px; color: var(--muted); line-height: 1.45; }

.portal-paywall-price {
  position: relative; display: flex; align-items: baseline; justify-content: center; gap: 6px;
  margin: 22px 0 4px;
}
.portal-paywall-price strong { font-size: 32px; letter-spacing: -0.03em; color: var(--brand); }
.portal-paywall-price span { font-size: 13px; color: var(--muted); }
.portal-paywall-cta-note { position: relative; margin: 0 0 14px; font-size: 12.5px; color: var(--muted); }

/* ═══════════════════════════════════════ member portal: workout logger ══ */

.portal-routine-card {
  position: relative; overflow: hidden;
  padding: 18px; border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  background: linear-gradient(150deg, var(--surface) 0%, var(--bg-alt) 100%);
  box-shadow: var(--shadow), inset 0 1px 0 var(--hairline);
}
.portal-routine-glow {
  position: absolute; inset: -70% 30% auto -10%; height: 200px; pointer-events: none;
  background: radial-gradient(circle at 40% 50%, var(--brand-glow) 0%, transparent 68%);
}
.portal-routine-kicker {
  position: relative; font-size: 11px; font-weight: 700; letter-spacing: 0.05em;
  text-transform: uppercase; color: var(--brand);
}
.portal-routine-day { position: relative; margin: 4px 0 0; font-size: 19px; letter-spacing: -0.02em; line-height: 1.25; }
.portal-routine-note { position: relative; margin: 8px 0 0; font-size: 13px; color: var(--muted); line-height: 1.5; }

.portal-routine-exercises {
  position: relative; display: flex; flex-direction: column; gap: 2px;
  margin: 16px 0; padding: 4px 0; border-top: 1px solid var(--border); border-bottom: 1px solid var(--border);
}
.portal-routine-row {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 7px 0; font-size: 13px;
}
.portal-routine-ex-name { flex: 1; min-width: 100px; font-weight: 600; }
.portal-routine-row .muted { font-size: 12px; font-variant-numeric: tabular-nums; }
.portal-routine-prev {
  font-size: 11px; padding: 2px 7px; border-radius: 999px;
  background: var(--surface-2); color: var(--muted); font-variant-numeric: tabular-nums;
}

.portal-muscle-badge {
  flex: none; font-size: 9.5px; font-weight: 800; letter-spacing: 0.04em; text-transform: uppercase;
  padding: 3px 7px; border-radius: 5px;
  background: var(--brand-soft); color: var(--brand);
}

/* -------------------------------------------------------- active session */

.portal-session { position: relative; }

.portal-session-bar {
  position: sticky; top: 0; z-index: 12;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  margin: -4px 0 4px; padding: 10px 12px;
  border-radius: var(--radius);
  border: 1px solid var(--brand);
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  backdrop-filter: blur(14px) saturate(160%);
  -webkit-backdrop-filter: blur(14px) saturate(160%);
  box-shadow: var(--shadow-sm), inset 0 1px 0 var(--hairline);
}
.portal-session-name { font-size: 13.5px; font-weight: 700; letter-spacing: -0.01em; }
.portal-session-clock {
  font-size: 19px; font-weight: 700; letter-spacing: 0.01em;
  font-variant-numeric: tabular-nums; color: var(--brand);
}

.portal-sound-toggle {
  flex: none; width: 32px; height: 32px; border-radius: 9px; display: grid; place-items: center;
  border: 1px solid var(--border); background: var(--surface-2); color: var(--muted); cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.portal-sound-toggle:hover { color: var(--brand); border-color: var(--brand); }

.portal-session-totals {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px;
  padding: 10px; border-radius: var(--radius);
  border: 1px solid var(--border); background: var(--bg-alt);
}
.portal-session-totals > div { display: flex; flex-direction: column; align-items: center; gap: 1px; }
.portal-session-totals strong { font-size: 15px; font-variant-numeric: tabular-nums; }
.portal-session-totals span { font-size: 10px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.04em; }

.portal-ex-card {
  display: flex; flex-direction: column; gap: 8px;
  padding: 14px; border-radius: var(--radius);
  border: 1px solid var(--border); background: var(--surface);
  box-shadow: var(--shadow-sm), inset 0 1px 0 var(--hairline);
}
.portal-ex-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; }
.portal-ex-name { font-size: 15px; font-weight: 700; letter-spacing: -0.01em; }
.portal-ex-meta { display: flex; align-items: center; gap: 7px; margin-top: 4px; flex-wrap: wrap; }
.portal-ex-meta .muted { font-size: 11.5px; }
.portal-ex-actions { display: flex; align-items: center; gap: 4px; flex: none; }
.portal-ex-foot { display: flex; gap: 8px; }

.portal-rest-chip {
  display: inline-flex; align-items: center; gap: 3px;
  padding: 4px 9px; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--border); background: var(--surface-2); color: var(--muted);
  font-size: 11px; font-weight: 700; font-variant-numeric: tabular-nums;
}
.portal-rest-chip:hover { color: var(--brand); border-color: var(--brand); }

/*
 * The set table. A 5-column grid sized for a phone: the two number inputs are
 * the widest things on the row because they are the two the member actually
 * touches mid-set, with a thumb, while out of breath.
 */
.portal-set-row {
  display: grid; grid-template-columns: 52px minmax(58px, 1fr) 1fr 1fr 42px;
  gap: 6px; align-items: center;
}
.portal-set-header {
  font-size: 9.5px; text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--muted); font-weight: 800; padding: 2px 0;
}
.portal-set-header span { text-align: center; }
.portal-set-header span:first-child, .portal-set-header span:nth-child(2) { text-align: left; }
.portal-set-row.completed { background: rgba(52, 211, 153, 0.07); border-radius: var(--radius-sm); }

.portal-set-n { display: flex; align-items: center; gap: 4px; }
.portal-set-n > span { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
.portal-set-type {
  flex: none; width: 22px; height: 22px; border-radius: 6px; border: 1px solid var(--border);
  background: var(--surface-2); color: var(--muted); cursor: pointer;
  font-size: 10px; font-weight: 800; display: grid; place-items: center; padding: 0;
}
.portal-set-type.t-warmup { color: var(--amber); border-color: rgba(251, 191, 36, 0.45); background: rgba(251, 191, 36, 0.12); }
.portal-set-type.t-drop { color: var(--blue); border-color: rgba(56, 189, 248, 0.45); background: rgba(56, 189, 248, 0.12); }
.portal-set-type.t-failure { color: var(--red); border-color: rgba(248, 113, 113, 0.45); background: rgba(248, 113, 113, 0.12); }

.portal-set-prev { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
.portal-set-input {
  width: 100%; min-width: 0; text-align: center;
  border: 1px solid var(--border); background: var(--surface-2); color: var(--text);
  border-radius: var(--radius-sm); padding: 9px 4px;
  font-size: 15px; font-weight: 600; font-variant-numeric: tabular-nums; font-family: inherit;
}
.portal-set-input:focus { outline: none; border-color: var(--brand); box-shadow: var(--ring); }
/* The spinners are unusable at this size and steal the tap target. */
.portal-set-input::-webkit-outer-spin-button,
.portal-set-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.portal-set-input { -moz-appearance: textfield; appearance: textfield; }

.portal-set-check {
  width: 38px; height: 38px; border-radius: 10px; cursor: pointer;
  display: grid; place-items: center;
  border: 1px solid var(--border); background: var(--surface-2); color: var(--muted);
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease, transform 0.1s ease;
}
.portal-set-check:active { transform: scale(0.93); }
.portal-set-check.done { background: var(--green); border-color: var(--green); color: #05231a; }

.portal-set-1rm {
  grid-column: 1 / -1; justify-self: end;
  font-size: 10.5px; color: var(--muted); font-variant-numeric: tabular-nums;
  display: inline-flex; align-items: center; gap: 3px; padding-bottom: 2px;
}
.portal-set-1rm.beats { color: var(--amber); font-weight: 700; }

.portal-add-ex { border-style: dashed; }

/* ------------------------------------------------------------ rest timer */

.portal-rest-timer {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: calc(96px + var(--safe-bottom)); z-index: 40;
  width: min(340px, calc(100vw - 32px));
  display: flex; align-items: center; gap: 10px;
  padding: 10px 12px; border-radius: 16px;
  border: 1px solid var(--brand);
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  backdrop-filter: blur(16px) saturate(180%);
  -webkit-backdrop-filter: blur(16px) saturate(180%);
  box-shadow: var(--shadow-lg), inset 0 1px 0 var(--hairline);
}
.portal-rest-timer.hidden { display: none; }
.portal-rest-icon { flex: none; color: var(--brand); display: grid; place-items: center; }
.portal-rest-body { flex: 1; min-width: 0; }
.portal-rest-label {
  font-size: 9.5px; text-transform: uppercase; letter-spacing: 0.06em;
  font-weight: 800; color: var(--muted);
}
.portal-rest-time { font-size: 20px; font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }
.portal-rest-bar { height: 3px; border-radius: 2px; background: var(--border); overflow: hidden; margin-top: 4px; }
.portal-rest-bar > i { display: block; height: 100%; background: var(--brand); border-radius: 2px; }
.portal-rest-actions { display: flex; gap: 4px; flex: none; }
.portal-rest-btn {
  border: 1px solid var(--border); background: var(--surface-2); color: var(--muted);
  border-radius: 9px; padding: 6px 8px; cursor: pointer;
  font-size: 11px; font-weight: 700; display: grid; place-items: center;
}
.portal-rest-btn:hover { color: var(--text); border-color: var(--brand); }

.portal-rest-timer.urgent { border-color: var(--amber); animation: rest-pulse 1s ease-in-out infinite; }
.portal-rest-timer.urgent .portal-rest-time, .portal-rest-timer.urgent .portal-rest-icon { color: var(--amber); }
.portal-rest-timer.urgent .portal-rest-bar > i { background: var(--amber); }
.portal-rest-timer.done { border-color: var(--green); }
.portal-rest-timer.done .portal-rest-time, .portal-rest-timer.done .portal-rest-icon { color: var(--green); }

@keyframes rest-pulse {
  0%, 100% { box-shadow: var(--shadow-lg), inset 0 1px 0 var(--hairline), 0 0 0 0 rgba(251, 191, 36, 0.4); }
  50% { box-shadow: var(--shadow-lg), inset 0 1px 0 var(--hairline), 0 0 0 8px rgba(251, 191, 36, 0); }
}
/* A single sharp flash on each of the 3-2-1 tick sounds, layered on top of
   the steadier rest-pulse loop so the countdown's last beats read louder. */
.portal-rest-timer.tick .portal-rest-time { animation: rest-tick-flash 0.22s ease-out; }
@keyframes rest-tick-flash {
  0% { transform: scale(1.35); color: var(--amber); }
  100% { transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .portal-rest-timer.urgent { animation: none; }
  .portal-rest-timer.tick .portal-rest-time { animation: none; }
}

/* ------------------------------------------------- finish summary and PRs */

.portal-summary { text-align: center; display: flex; flex-direction: column; gap: 16px; }
.portal-summary-burst { font-size: 46px; line-height: 1; animation: summary-pop 0.5s cubic-bezier(0.2, 1.4, 0.4, 1); }
.portal-summary h3 { margin: 0; font-size: 18px; letter-spacing: -0.01em; }
@keyframes summary-pop {
  from { transform: scale(0.4) rotate(-12deg); opacity: 0; }
  to { transform: scale(1) rotate(0); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .portal-summary-burst { animation: none; }
}

.portal-summary-grid {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px;
  padding: 12px 0; border-top: 1px solid var(--border); border-bottom: 1px solid var(--border);
}
.portal-summary-grid > div { display: flex; flex-direction: column; gap: 2px; }
.portal-summary-grid strong { font-size: 15px; font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }
.portal-summary-grid span { font-size: 9.5px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.04em; }

.portal-summary-prs { display: flex; flex-direction: column; gap: 8px; text-align: left; }
.portal-pr-line {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 9px 11px; border-radius: var(--radius-sm);
  background: rgba(251, 191, 36, 0.1); border: 1px solid rgba(251, 191, 36, 0.3);
  color: var(--amber); font-size: 12.5px;
}
.portal-pr-line strong { color: var(--text); }
.portal-pr-line > span:not(.portal-pr-delta) { color: var(--text); font-variant-numeric: tabular-nums; }
.portal-pr-delta { margin-left: auto; font-weight: 700; font-variant-numeric: tabular-nums; }

.portal-pr-wall { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
.portal-pr-card {
  position: relative; padding: 12px; border-radius: var(--radius);
  border: 1px solid var(--border); background: var(--surface);
  box-shadow: var(--shadow-sm), inset 0 1px 0 var(--hairline);
}
.portal-pr-trophy { color: var(--amber); margin-bottom: 6px; }
.portal-pr-name { font-size: 12px; font-weight: 600; line-height: 1.3; margin-bottom: 4px; }
.portal-pr-value { font-size: 15px; font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }
.portal-pr-1rm { font-size: 10.5px; color: var(--muted); font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------- workout history */

.portal-log-row {
  display: flex; align-items: center; gap: 11px; width: 100%; text-align: left;
  padding: 11px 13px; border-radius: var(--radius); cursor: pointer;
  border: 1px solid var(--border); background: var(--surface); color: var(--text);
  box-shadow: inset 0 1px 0 var(--hairline);
}
.portal-log-row:hover { border-color: var(--brand); }
.portal-log-icon {
  flex: none; width: 32px; height: 32px; border-radius: 9px; display: grid; place-items: center;
  background: var(--brand-soft); color: var(--brand);
}
.portal-log-meta { flex: 1; min-width: 0; }
.portal-log-name { font-size: 13.5px; font-weight: 600; }
.portal-log-meta .muted { font-size: 11.5px; }
.portal-log-right { flex: none; display: flex; flex-direction: column; align-items: flex-end; gap: 3px; }
.portal-log-volume { font-size: 13px; font-weight: 700; font-variant-numeric: tabular-nums; }

.portal-log-detail { display: flex; flex-direction: column; gap: 16px; }
.portal-log-ex h4 { margin: 0 0 6px; font-size: 13.5px; }
.portal-log-set {
  display: flex; align-items: center; gap: 9px; padding: 4px 0;
  font-size: 12.5px; font-variant-numeric: tabular-nums;
}
.portal-log-set .portal-set-type { cursor: default; }

/* ══════════════════════════════════════════ member portal: diet tracker ══ */

.portal-diet-hero {
  display: flex; align-items: center; gap: 18px; flex-wrap: wrap;
  padding: 18px; border-radius: var(--radius-lg);
  border: 1px solid var(--border);
  background: linear-gradient(150deg, var(--surface) 0%, var(--bg-alt) 100%);
  box-shadow: var(--shadow), inset 0 1px 0 var(--hairline);
}

.portal-cal-ring { position: relative; flex: none; display: grid; place-items: center; }
.portal-cal-track { stroke: var(--border); }
.portal-cal-fill { stroke: var(--macro-kcal); transition: stroke-dashoffset 0.5s cubic-bezier(0.4, 0, 0.2, 1); }
.portal-cal-fill.over { stroke: var(--red); }
.portal-cal-center {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 1px; pointer-events: none;
}
.portal-cal-center strong { font-size: 30px; font-weight: 700; letter-spacing: -0.03em; font-variant-numeric: tabular-nums; }
.portal-cal-center span { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; font-weight: 700; }
.portal-cal-center small { font-size: 10.5px; color: var(--muted); margin-top: 3px; font-variant-numeric: tabular-nums; }
/* The Home "Today's focus" snippet borrows this ring at a smaller size —
   the number and label need to shrink with it or they overrun a ring a
   third the diameter of the Diet tab's hero. */
.portal-cal-ring.sm .portal-cal-center strong { font-size: 16px; }
.portal-cal-ring.sm .portal-cal-center span { font-size: 7.5px; letter-spacing: 0.03em; }
.portal-cal-ring.sm .portal-cal-center small { display: none; }

.portal-macro-stack { flex: 1; min-width: 190px; display: flex; flex-direction: column; gap: 12px; }
.portal-macro { min-width: 0; }
.portal-macro-top { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; margin-bottom: 5px; }
.portal-macro-label { font-size: 12px; font-weight: 700; }
.portal-macro-value { font-size: 11.5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.portal-macro-bar { height: 7px; border-radius: 999px; background: var(--border); overflow: hidden; }
.portal-macro-bar > i {
  display: block; height: 100%; border-radius: 999px;
  transition: width 0.45s cubic-bezier(0.4, 0, 0.2, 1);
}
.portal-macro-bar.protein > i { background: var(--macro-protein); }
.portal-macro-bar.carbs > i { background: var(--macro-carbs); }
.portal-macro-bar.fats > i { background: var(--macro-fats); }

.portal-diet-hint {
  display: flex; align-items: center; gap: 7px;
  font-size: 12px; color: var(--muted);
  padding: 8px 11px; border-radius: var(--radius-sm);
  background: var(--surface-2); border: 1px solid var(--border);
}

/* ---------------------------------------------------------------- water */

.portal-water-card {
  display: flex; flex-direction: column; gap: 11px;
  padding: 14px; border-radius: var(--radius);
  border: 1px solid var(--border); background: var(--surface);
  box-shadow: var(--shadow-sm), inset 0 1px 0 var(--hairline);
}
.portal-water-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.portal-water-head > div { display: flex; align-items: center; gap: 6px; color: var(--blue); }
.portal-water-head strong { font-size: 13.5px; color: var(--text); }
.portal-water-head .muted { font-size: 12px; font-variant-numeric: tabular-nums; }
.portal-water-glasses { display: flex; flex-wrap: wrap; gap: 5px; }
.portal-glass {
  width: 26px; height: 30px; border-radius: 6px 6px 9px 9px;
  display: grid; place-items: center;
  border: 1px solid var(--border); background: var(--surface-2); color: var(--muted);
  transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}
.portal-glass.filled {
  background: rgba(56, 189, 248, 0.16); border-color: rgba(56, 189, 248, 0.45); color: var(--blue);
}

/* ---------------------------------------------------------------- meals */

.portal-meal-card {
  display: flex; flex-direction: column; gap: 10px;
  padding: 14px; border-radius: var(--radius);
  border: 1px solid var(--border); background: var(--surface);
  box-shadow: var(--shadow-sm), inset 0 1px 0 var(--hairline);
}
.portal-meal-head { display: flex; align-items: center; gap: 10px; }
.portal-meal-icon {
  flex: none; width: 30px; height: 30px; border-radius: 9px; display: grid; place-items: center;
  background: var(--brand-soft); color: var(--brand);
}
.portal-meal-title { flex: 1; font-size: 14px; font-weight: 700; letter-spacing: -0.01em; }
.portal-meal-kcal { font-size: 13px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--macro-kcal); }

.portal-meal-planned {
  padding: 9px 11px; border-radius: var(--radius-sm);
  background: var(--surface-2); border: 1px dashed var(--border);
}
.portal-meal-planned-label {
  font-size: 9.5px; text-transform: uppercase; letter-spacing: 0.05em;
  font-weight: 800; color: var(--muted); margin-bottom: 5px;
}
.portal-planned-row {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  font-size: 12px; padding: 2px 0;
}
.portal-planned-row .muted { font-variant-numeric: tabular-nums; flex: none; }

.portal-meal-entries { display: flex; flex-direction: column; }
.portal-entry-row {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 0; border-bottom: 1px solid var(--border);
}
.portal-entry-row:last-child { border-bottom: none; }
.portal-entry-row > div:first-child { flex: 1; min-width: 0; }
.portal-entry-name { font-size: 13px; font-weight: 600; }
.portal-entry-row .muted { font-size: 11px; }
.portal-entry-kcal { flex: none; font-size: 13px; font-weight: 700; font-variant-numeric: tabular-nums; }

.portal-add-food {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  padding: 9px; border-radius: var(--radius-sm); cursor: pointer;
  border: 1px dashed var(--border); background: none; color: var(--brand);
  font-size: 12.5px; font-weight: 700; font-family: inherit;
}
.portal-add-food:hover { border-color: var(--brand); background: var(--brand-soft); }

/* ------------------------------------------------------- search sheets */

.portal-food-sheet { display: flex; flex-direction: column; gap: 12px; }
.portal-sheet-switch { display: flex; gap: 6px; }
.portal-sheet-tab {
  flex: 1; padding: 8px; border-radius: var(--radius-sm); cursor: pointer;
  border: 1px solid var(--border); background: var(--surface-2); color: var(--muted);
  font-size: 12.5px; font-weight: 700; font-family: inherit;
}
.portal-sheet-tab.active { background: var(--brand-soft); color: var(--brand); border-color: var(--brand); }

.portal-food-results {
  display: flex; flex-direction: column; gap: 4px;
  max-height: 46vh; overflow-y: auto; -webkit-overflow-scrolling: touch;
}
.portal-food-row {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  padding: 9px 11px; border-radius: var(--radius-sm); cursor: pointer;
  border: 1px solid transparent; background: var(--surface-2); color: var(--text);
}
.portal-food-row:hover { border-color: var(--brand); }
.portal-food-row.active { border-color: var(--brand); background: var(--brand-soft); }
.portal-food-row > div:first-child { flex: 1; min-width: 0; }
.portal-food-name { font-size: 13px; font-weight: 600; }
.portal-food-row .muted { font-size: 11px; }
.portal-food-kcal { flex: none; font-size: 13.5px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--macro-kcal); }
.portal-food-section {
  font-size: 9.5px; text-transform: uppercase; letter-spacing: 0.05em;
  font-weight: 800; color: var(--muted); padding: 8px 0 2px;
}

.portal-food-detail {
  display: flex; flex-direction: column; gap: 12px;
  padding: 13px; border-radius: var(--radius);
  border: 1px solid var(--brand); background: var(--bg-alt);
}
.portal-food-detail-name { font-size: 14.5px; font-weight: 700; letter-spacing: -0.01em; }
.portal-food-qty-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.portal-food-qty-row .muted { font-size: 12px; }
.portal-qty { flex: none; width: 92px; text-align: center; font-variant-numeric: tabular-nums; }
.portal-food-macros {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px;
  padding: 10px 0; border-top: 1px solid var(--border); border-bottom: 1px solid var(--border);
}
.portal-food-macros > div { display: flex; flex-direction: column; align-items: center; gap: 1px; }
.portal-food-macros strong { font-size: 15px; font-variant-numeric: tabular-nums; }
.portal-food-macros span { font-size: 9.5px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.04em; }

.portal-chip-row { display: flex; gap: 6px; flex-wrap: wrap; }

/* ----------------------------------------------------------- responsive */

/*
 * A gym's portal carries seven tabs (Home, Pass, Schedule, Workout, Diet, Pay,
 * Profile), which is two more than the bar was built for — tighten it rather
 * than let the labels clip.
 */
@media (max-width: 420px) {
  .portal-tabbar { gap: 0; padding: 5px; margin-left: 8px; margin-right: 8px; }
  .portal-tab { padding: 7px 1px 5px; font-size: 9px; gap: 1px; }
}

@media (max-width: 480px) {
  .portal-pr-wall { grid-template-columns: 1fr; }
  .portal-diet-hero { justify-content: center; }
  .portal-summary-grid { grid-template-columns: repeat(2, 1fr); }
  /*
   * The builders' fixed grids stop being grids on a phone: an eight-column row
   * of inputs at 360px gives every cell 30px. Each field goes full width with
   * its own label instead (the header row is hidden, since it no longer sits
   * above anything).
   */
  .fit-ex-header { display: none; }
  .fit-ex-row, .fit-item-row {
    grid-template-columns: repeat(2, 1fr);
    padding: 10px; border: 1px solid var(--border); border-radius: var(--radius-sm);
    background: var(--surface);
  }
  .fit-cell-name { grid-column: 1 / -1; }
  .fit-ex-handle { justify-self: start; }
}
