/* ============================================================================
   Commandery single stylesheet (app.css)

   Loaded by every HTML page. Replaces the former three-file split
   (shared.css, auth.css, style.css). See CLAUDE.md for the design system.

   ── @layer order ──
     components : tokens (:root) + element resets ([hidden], html, body,
                  the universal reset) + all universal primitives +
                  auth-family components
     page       : main-app components (index.html — body.page-main scoped)
     utilities  : bounded .u-* one-off nudges

   The page layer wins over components. Utilities wins over both. Source
   order within each layer determines precedence for same-layer rules.

   ── Page-class scoping ──
   The body styling is universal; per-page intent opts in via a body class:
     body.page-main    — index.html              → font-size: 15px
     body.page-narrow  — auth-box pages          → flex-centered
     body.page-admin   — admin.html              → scopes admin rules
     (no class)        — pod-manage / profile /  → default block layout
                         styleguide

   ── TOC ──
   Components block (universal primitives + auth-family)
     • :root tokens (colors, radius, spacing, type, motion)
     • [hidden] reset
     • Tags (.tag, .tag--*)
     • Small buttons (.btn-sm, .btn-sm--{primary,ghost,danger})
     • Extra-small buttons (.btn-xs, .btn-xs--{primary,danger})
     • Icon buttons (.icon-btn, .icon-btn--{edit,delete,restore,resend})
     • Spinner (.spinner, .spinner--{sm,muted}, @keyframes spin)
     • Stat tiles (.pc-stats, .pc-stat, .pc-stat-label)
     • Mana pips (.color-pips, .mana-pip)
     • Section heads (.section-head, .section-title, .section-subtitle)
     • Stat cards (.stat-cards, .stat-card, .stat-card-{val,label,sub})
     • List-card rows (.list-card, .cmd-entry, .cmd-art, .game-num,
       .cmd-name, .cmd-rate*, .cmd-wins-hero, .cmd-count-sub,
       .cmd-last-played)
     • Card hover tooltip (#card-tooltip, #card-tooltip-img)
     • Generic tooltip (#tooltip)
     • Toasts (.toast-stack, .toast, .toast--*, .toast-progress*,
       @keyframes toast-tick/in/out)
     • Native form controls (input[type=date|number], select)
     • Date range (.range-inputs)
     • Picker options (.picker-opt)
     • Popup year row (.popup-year-row, .popup-custom)
     • Popup chrome (.popup-overlay, .popup-box, .popup-header,
       .popup-close-btn)
     • Filter triggers (.btn-range-open, .custom-multiselect,
       .custom-multiselect-{btn,panel}, .ms-{label,chevron})
     • Apply button (.btn-apply)

   Auth-family components block
     • Body + reset + box-sizing
     • Auth box (.auth-box, .auth-box--{sm,lg})
     • Branding (.auth-logo, .auth-tagline, .auth-hero, .auth-heading)
     • Form fields (.form-row, .form-group, .form-input, .form-checkbox)
     • Custom select (.custom-select, .custom-select-{btn,panel,opt})
     • Data table (.data-table-wrap, .data-table, .cell-name, .cell-sub,
       .td-actions, .table-section-row)
     • Password (.pw-wrap, .pw-toggle, .strength-{wrap,bar,fill,text})
     • Field/form messages (.field-error, .form-msg, .form-msg--*)
     • Primary button (.auth-btn)
     • Links & footer (.auth-link, .auth-footer, .remember-me-row)
     • Loading (.auth-loading)
     • Pod list / cards (.pod-list, .pod-card, .pod-card-{body,name,meta,role})
     • Invites (.invite-card, .invite-actions, .btn-accept, .btn-reject,
       .inv-count)
     • Section labels (.section-label, .section-heading, .form-section)
     • Create pod (.btn-create-pod, .create-pod-{form,row})
     • Profile card (.profile-card, .profile-field*)
     • Utility classes (.logout-form, .auth-{centered,hint,btn--link},
       .card-leaving, .row-fading)
     • Page heading + header (.page-heading, .page-header)
     • Tab bar (.tab-bar, .tab-btn, .tab-panel)
     • Full-width page (.page-wrap, .page-card)
     • Modal (.modal-overlay, .modal-box, .modal-{title,body,actions})
     • Mobile (max-width: 600px) media query

   Main-app components block (index.html — wrapped in @layer page)
     • :root tokens (--rank1-gradient, --sp-section, etc.)
     • Body + form-element font reset
     • Header (.header-main, .header-meta)
     • Global nav (.gnav, .gnav-link, .gnav-brand — defined near end of file)
     • Section nav (#section-nav-wrap, .snav-*)
     • Sticky filters (.snav-filters, .snav-chips, .snav-filter-sheet-btn,
       .gf-cmd-search, .picker-input, .ms-mode-{toggle,btn})
     • Main layout (main, .controls, .ng-label,
       .sort-{wrap,btn,panel,opt})
     • Filters (.custom-color-panel, .check-pill, .color-option,
       input[type=checkbox])
     • Player cards (#player-cards, .player-card, .pc-{identity,winrate,
       form,color-pips}, .form-pip*, .streak-label)
     • H2H (.h2h-layout, .h2h-nav, .h2h-opp-*, .h2h-detail-*)
     • Cmd entry overrides (.cmd-entry, .cmd-name, .cmd-details)
     • Game rows (.game-row, .commanders-cell, .result-pills)
     • Empty states (.empty-state, .lb-error, .empty-clear-btn)
     • Colour statistics (.color-stats-grid, .color-stat-*, .cs-donut*)
     • Form timeline (#timeline-rows, .tl-*)
     • Game log + session headers (.session-header, .session-date,
       .session-rule, .game-num, .commanders-cell, .player-result)
     • Modals (#new-game-modal, #pp-*-modal)
     • Section loader, FAB (#new-game-fab, .fab-label, #data-panel,
       .data-panel-btn, .nav-invite-badge)
     • Speed-dial (.speed-dial-*)
     • Players side panel (#players-panel, .players-panel-box, .pp-*)
     • Card art skeleton + section skeleton (@keyframes skeleton-shimmer)
     • New-game modal cards (.ng-pcard, .ng-add-card, .ng-trophy-btn,
       .ng-cmd-*, .ng-partner-btn, .ng-unknown-*)
     • Rich tooltips (#att-tooltip, #streak-tooltip, #skill-tooltip,
       #bayesian-tooltip, .skill-tip-*)
     • Mobile media queries (max-width: 900px, max-width: 600px)
   ============================================================================ */

@layer components, page, utilities;

/* ── Inter — self-hosted (GDPR: no Google Fonts CDN) ─────────────────────────
   Two files of the Inter v20 VARIABLE font (weights 400–800 in one file),
   downloaded from fonts.gstatic.com into /fonts. unicode-range keeps the
   latin-ext file lazy — browsers fetch it only when a page actually renders
   those characters. Pages preload /fonts/inter-latin.woff2 in <head>.
   @font-face must live at the top level, outside @layer. */
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("/fonts/inter-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("/fonts/inter-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* Cross-document view transitions. Because every authenticated page injects
   the exact same .gnav bar, naming it (see .gnav { view-transition-name })
   lets the browser hold the bar perfectly still while it crossfades only the
   page body — an SPA-like "the nav didn't reload" feel with no client router.
   Same-origin navigations only; unsupporting browsers just navigate normally.
   Must live at the top level — @view-transition can't sit inside @layer. */
@view-transition { navigation: auto; }

/* Honour reduced-motion: keep the seamless swap, drop the animation. */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}


/* ╔═══════════════════════════════════════════════════════════════════════╗
   ║ RETIRED CLASSES — never use these                                     ║
   ║                                                                       ║
   ║ Enforced by scripts/pre-commit. The replacements below all live in    ║
   ║ this file; grep for the retired name to find the section that hosts   ║
   ║ its replacement.                                                      ║
   ║                                                                       ║
   ║   btn-icon              → icon-btn icon-btn--{modifier}               ║
   ║   profile-edit-btn      → icon-btn icon-btn--edit                     ║
   ║   pod-icon-btn          → icon-btn icon-btn--{modifier}               ║
   ║   admin-tabs            → tab-bar                                     ║
   ║   admin-tab             → tab-btn                                     ║
   ║   admin-loading         → auth-loading                                ║
   ║   admin-table-wrap      → data-table-wrap                             ║
   ║   admin-table           → data-table                                  ║
   ║   pod-leave-btn         → btn-xs btn-xs--danger                       ║
   ║   game-edit-btn         → icon-btn icon-btn--edit                     ║
   ║   game-delete-btn       → icon-btn icon-btn--delete                   ║
   ║   game-restore-btn      → btn-xs btn-xs--primary                      ║
   ║   game-perm-delete-btn  → btn-xs btn-xs--danger                       ║
   ║   dd-select-wrap        → custom-multiselect-btn  (main app)          ║
   ║   select-wrap           → custom-select           (auth pages)        ║
   ║   auth-box--wide        → auth-box / auth-box--sm / auth-box--lg      ║
   ║                            or page-card for full-width pages          ║
   ║   tag--warn             → tag--gold  (one gold chip — warn IS gold;   ║
   ║                            the duplicate alias was collapsed)         ║
   ║   panel-separator       → border-top: 1px solid var(--divider)        ║
   ║   btn-sm--accent        → btn-sm--primary  (primary buttons are now   ║
   ║                            neutral; the ember is reserved — never a   ║
   ║                            button fill)                               ║
   ║   btn-xs--accent        → btn-xs--primary  (xs affirmative action;    ║
   ║                            neutral fill, never ember)                 ║
   ║   tl-dot                → tl-bar > tl-seg tl-seg--{win|loss}          ║
   ║                            (session bars replaced per-game dots)      ║
   ╚═══════════════════════════════════════════════════════════════════════╝ */


/* ╔═══════════════════════════════════════════════════════════════════════╗
   ║ COMPONENTS — tokens (:root) + universal primitives                    ║
   ╚═══════════════════════════════════════════════════════════════════════╝ */
@layer components {
/* Ensure [hidden] always wins, even on flex/grid containers */
[hidden] { display: none !important; }

:root {
  /* ── Color tokens ──
     Near-monochrome cool-neutral ramp on a near-black base; a single warm
     "spark" (--accent) is the only chromatic UI colour and appears at most
     once or twice per screen. --win/--loss/--gold are functional status hues
     only, never decoration. */
  --bg:           #0d0f11;   /* page base — near-black, faintly cool */
  --surface:      #1e242b;   /* cards, panels */
  --surface-2:    #2b323a;   /* nested / elevated tier on top of --surface */
  --surface-hover:#353d46;   /* interactive surface hover */
  --border:       rgba(255,255,255,0.12);  /* hairline rim — art tiles, colour boxes, floating overlays, fields. General surfaces are BORDERLESS (fill tier + shadow separate them) */
  --divider:      rgba(255,255,255,0.11);  /* subtle section dividers */
  --accent:       #f2622e;   /* the single spark — deep enough for white text/icons on top */
  --accent-h:     #ff7344;
  --gold:         #f0c040;   /* rank-1 / milestones / hot streak — functional only */
  --win:          #4cc88f;
  --loss:         #e5484d;
  --loss-h:       #f25c60;
  --loss-text:    #ff8a8d;   /* softer red for text/inline status */
  --text:         #edf0f2;   /* primary text — near-white, faintly cool */
  --muted:        #9aa3ab;   /* secondary text, labels — neutral grey */
  --on-accent:    #ffffff;   /* white text/icons on the ember spark fills (FAB, badges) */
  --on-loss:      #ffffff;   /* white text on solid red danger fills */
  --on-light:     #0d0f11;   /* dark text on neutral near-white primary buttons */
  --primary:      #eef1f2;   /* neutral high-contrast primary button fill */
  --primary-h:    #ffffff;   /* primary button hover */
  --scrim:        rgba(6,8,10,0.82);       /* modal / popup overlay */
  --accent-soft:  rgba(242,98,46,0.15);    /* accent background tint */
  --loss-soft:    rgba(229,72,77,0.13);
  --win-soft:     rgba(76,200,143,0.12);
  --warn-soft:    rgba(240,192,64,0.14);
  --muted-soft:   rgba(154,163,171,0.18);
  --gold-glow:    rgba(240,192,64,0.45);
  --loss-border:  rgba(229,72,77,0.40);
  --win-border:   rgba(76,200,143,0.30);
  --focus-ring:   0 0 0 2px rgba(242,98,46,0.60);

  /* ── Radius scale ── (softer, larger — modern rounded rhythm) */
  --radius-xs: 4px;
  --radius-sm: 6px;
  --radius-md: 8px;
  --radius-lg: 12px;
  --radius:    16px;
  --edge-gap:  1.5rem;
  --gnav-h:    3.25rem;   /* global-nav top app-bar height (desktop) */
  --gnav-h-mobile: 4rem;   /* global-nav bottom bar height (mobile) — taller than the
                              icon+label content so the centred active pill has clear
                              breathing room above/below (it's the slack, not padding) */

  /* ── Spacing scale (padding, margin, gap only — NOT font-size, width, height) ── */
  --sp-1: 0.25rem;
  --sp-2: 0.5rem;
  --sp-3: 0.75rem;
  --sp-4: 1rem;
  --sp-5: 1.25rem;
  --sp-6: 1.5rem;
  --sp-7: 2rem;
  --sp-8: 3rem;

  /* ── Font stacks ── */
  --font-ui:      'Inter', system-ui, -apple-system, sans-serif;   /* everything, incl. the Commandery wordmark */

  /* ── Letter-spacing tokens ──
     All-caps is reserved for chips (.tag, .snav-chip) and avatar initials only.
     --ls-caps is the gentle tracking those chips use; --ls-display slightly
     tightens large sentence-case headings (Inter) the modern way. */
  --ls-caps:    0.02em;
  --ls-display: -0.01em;

  /* ── Type scale ──
     A LIST HEADING IS NEVER SMALLER THAN THE ROWS IT HEADS. When a label heads
     a plain-text LIST or MENU — footer link columns, table columns, a stack of
     text rows — it takes --sz-body (weight 700, --muted, --ls-caps); hierarchy
     comes from weight + colour, not from shrinking the heading below its own
     content. Members: .section-label, .data-table th, .table-section-row td,
     .lp-footer-col-title. A new list/menu heading joins them.

     This is DELIBERATELY narrow. --sz-label keeps every label that is not
     heading a text list, and bumping those makes the surface top-heavy:
       • labels riding on a card / box / graphic tile — .color-stat-label,
         .session-date, .stat-card-label, .pc-winrate-label, .streak-label
       • standalone label→value pairs — .profile-field-label
       • control labels next to one input — .form-group label, .ng-label,
         .snav-sheet-label, .popup-custom label
       • meta/sub lines and chip/pill controls — .cell-sub, .pod-card-meta,
         .invite-card-sub, .snav-link, .pod-card-role
     If the thing below the label is boxes, art or a single value rather than
     plain text rows, the label stays small. */
  --sz-h1:    1.6rem;
  --sz-h2:    1.25rem;
  --sz-h3:    1.1rem;
  --sz-body:  1rem;
  --sz-sm:    0.85rem;
  --sz-label: 0.75rem;
  --sz-micro: 0.68rem;

  /* ── Motion tokens ── */
  --motion-fast: 150ms;
  --motion-base: 220ms;
  --ease-out:    cubic-bezier(0.2, 0.7, 0.2, 1);
  /* ── Elevation scale ── (drop shadows only — glows/rims are bespoke)
     Two-layer, lower-opacity shadows: a tight contact layer + a soft ambient
     layer. Reads as quiet depth rather than a heavy dark halo. */
  --shadow-1: 0 1px 2px rgba(0,0,0,0.30), 0 3px 8px rgba(0,0,0,0.22);    /* resting float — FABs, tooltips, small popovers */
  --shadow-2: 0 4px 12px rgba(0,0,0,0.34), 0 12px 28px rgba(0,0,0,0.30); /* raised — dropdown panels, side panels, toasts */
  --shadow-3: 0 8px 24px rgba(0,0,0,0.40), 0 24px 56px rgba(0,0,0,0.46); /* overlay — modals, popups, page/auth cards */
  --shadow-lift: 0 2px 10px rgba(0,0,0,0.28);  /* hover affordance — soft diffuse shadow, no positional shift */
}

/* ── Tags ─────────────────────────────────────────────────────────────────── */
.tag {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  font-size: var(--sz-micro);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--ls-caps);
  padding: var(--sp-1) var(--sp-2);
  border-radius: var(--radius-sm);
  white-space: nowrap;
  line-height: 1;
  vertical-align: middle;
}
.tag--accent { color: var(--accent);    background: var(--accent-soft); }
.tag--win    { color: var(--win);       background: var(--win-soft); }
.tag--loss   { color: var(--loss-text); background: var(--loss-soft); }
/* One gold chip — rank-1, milestones, winner, unverified. There is no
   .tag--warn alias; warn IS gold (see the Color cheat sheet in CLAUDE.md). */
.tag--gold   { color: var(--gold);      background: var(--warn-soft); }
.tag--muted  { color: var(--muted);     background: var(--muted-soft); }

/* ── Small inline buttons (.btn-sm) ───────────────────────────────────────── */
.btn-sm {
  padding: var(--sp-2) var(--sp-4);
  border: 1px solid transparent;
  border-radius: var(--radius-lg);
  font-family: inherit;
  font-size: var(--sz-sm);
  font-weight: 600;
  cursor: pointer;
  flex-shrink: 0;
  transition: background var(--motion-fast) var(--ease-out), border-color var(--motion-fast) var(--ease-out), color var(--motion-fast) var(--ease-out);
}
.btn-sm:focus-visible { outline: none; box-shadow: var(--focus-ring); }
/* Modifiers set fill + label only — the transparent border, focus ring and
   disabled treatment all come from the .btn-sm base. */
.btn-sm--primary { background: var(--primary); color: var(--on-light); }
.btn-sm--primary:hover:not(:disabled) { background: var(--primary-h); }
.btn-sm--ghost  { background: var(--surface); color: var(--text); }
.btn-sm--ghost:hover:not(:disabled)  { background: var(--surface-hover); }
.btn-sm--danger { background: var(--surface); color: var(--loss-text); }
.btn-sm--danger:hover:not(:disabled) { background: var(--loss-soft); }
.btn-sm:disabled { opacity: 0.45; cursor: not-allowed; }

/* Quiet controls rest ONE TIER ABOVE their local background (same
   lighter-is-raised rule as the card model): --surface on the near-black
   --bg body, stepped up to --surface-2 inside a --surface panel. Keep this
   rule at 0,2,0 specificity and ABOVE the .btn-xs--primary and
   .modal-box .btn-sm--danger overrides so those still win by source order. */
:is(.page-card, .modal-box, .popup-box, .auth-box, .players-panel-box, .list-card, .empty-state, .lb-error, .lp-hero, .lp-cta-box) :is(.btn-sm--ghost, .btn-sm--danger, .btn-xs) { background: var(--surface-2); }

/* ── Extra-small action buttons (.btn-xs) ─────────────────────────────────── */
/* Use inside .action-row inside a .td-actions cell (admin), or as standalone
   row actions elsewhere. Mixed-case label — not uppercase. */
.btn-xs {
  padding: var(--sp-1) var(--sp-2);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--text);
  font-family: inherit;
  font-size: var(--sz-label);
  font-weight: 600;
  cursor: pointer;
  transition: border-color var(--motion-fast) var(--ease-out), color var(--motion-fast) var(--ease-out), background var(--motion-fast) var(--ease-out);
  white-space: nowrap;
}
/* Hover shifts the FILL only — each modifier owns its label colour, so no
   hover rule needs to restate it. The doubled .btn-xs.btn-xs--* selectors are
   load-bearing: they must out-specify the :is() step-up rule above. */
.btn-xs:hover:not(:disabled)                { background: var(--surface-hover); }
.btn-xs.btn-xs--danger                      { color: var(--loss-text); }
.btn-xs.btn-xs--danger:hover:not(:disabled) { background: var(--loss-soft); }
.btn-xs.btn-xs--primary                      { background: var(--primary); color: var(--on-light); }
.btn-xs.btn-xs--primary:hover:not(:disabled) { background: var(--primary-h); }
.btn-xs:disabled { opacity: 0.45; cursor: not-allowed; }
.action-row { display: flex; gap: var(--sp-1); align-items: center; justify-content: flex-end; }

/* ── Icon buttons (.icon-btn) ─────────────────────────────────────────────── */
/* Always pair with a semantic modifier. Hover backgrounds carry the colour. */
.icon-btn {
  background: none;
  border: none;
  color: var(--muted);
  width: 1.8rem;
  height: 1.8rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-sm);
  cursor: pointer;
  line-height: 0;
  flex-shrink: 0;
  transition: background var(--motion-fast) var(--ease-out), color var(--motion-fast) var(--ease-out);
}
.icon-btn--delete:hover:not(:disabled)  { background: var(--loss-soft);  color: var(--loss-text); }
.icon-btn--edit:hover:not(:disabled),
.icon-btn--restore:hover:not(:disabled) { background: var(--muted-soft); color: var(--text); }
.icon-btn--resend:hover:not(:disabled)  { background: var(--win-soft);   color: var(--win); }

/* ── Spinner ──────────────────────────────────────────────────────────────── */
.spinner {
  display: inline-block;
  width: 14px; height: 14px;
  border: 2px solid rgba(255,255,255,0.3);
  border-top-color: #fff;
  border-radius: 50%;
  animation: spin 0.7s linear infinite;
  vertical-align: middle;
  margin-right: 0.4rem;
}
.spinner--sm    { width: 10px; height: 10px; margin: 0; }
.spinner--muted { border-color: rgba(255,255,255,0.14); border-top-color: var(--muted); }
@keyframes spin { to { transform: rotate(360deg); } }

/* ── Stat tiles (.pc-stats > .pc-stat) ───────────────────────────────────────
   Row of compact metric tiles — big value on top, small-caps label underneath.
   A universal primitive (@layer components) so profile/auth-family pages can
   reuse the same idiom the main app uses for player-card stats. Callers can
   override the container (border-top, padding) when used outside a card. */
.pc-stats {
  display: flex;
  justify-content: center;
  gap: var(--sp-7);
  width: 100%;
  padding: var(--sp-4) 0;
  border-top: 1px solid var(--divider);
}
.pc-stat {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-1);
}
.pc-stat .stat-val { font-size: var(--sz-h2); font-weight: 800; color: var(--text); letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.pc-stat-label {
  font-size: var(--sz-micro);
  letter-spacing: var(--ls-caps);
  color: var(--muted);
}

/* ── Mana pips (WUBRG colour-identity swatches) ──────────────────────────────
   The `img` carries the actual coloured SVG (Scryfall card-symbols). A universal
   primitive (@layer components) so profile/auth pages can render colour
   identities exactly the way the main app does. Context-specific size overrides
   (e.g. `.pc-color-pips .mana-pip`, `.color-stat-box .mana-pip`) live in the
   page layer. */
.color-pips {
  display: inline-flex;
  align-items: center;
  gap: 1px;
  margin-left: 5px;
  vertical-align: middle;
}
.mana-pip {
  width: 13px;
  height: 13px;
  display: inline-block;
}

/* ── Section header (.section-head > .section-head-main > .section-title + .section-subtitle)
   A universal primitive (@layer components) so auth-family pages get the same
   major heading idiom the main app uses for "Top Players", "Top Commanders",
   etc. — display caps with a 3px accent bar on the left and an optional muted
   subtitle underneath. */
.section-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--sp-6);
  margin-bottom: var(--sp-4);
  flex-wrap: wrap;
}
.section-head-main {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  min-width: 0;
}
.section-title {
  font-size: var(--sz-h2);
  line-height: 1.2;
  font-weight: 700;
  letter-spacing: var(--ls-display);
  color: var(--text);
}
.section-head .section-title {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
}
.section-head .section-title::before {
  content: '';
  display: inline-block;
  width: 3px;
  height: 1.1em;
  background: var(--accent);  /* ember heading tick — on the sanctioned spark list */
  border-radius: 2px;
  flex-shrink: 0;
}
.section-subtitle {
  font-size: var(--sz-sm);
  font-weight: 400;
  color: var(--muted);
  text-transform: none;
  letter-spacing: 0;
  white-space: normal;
}
.section-head h2 {
  white-space: nowrap;
  padding-top: var(--sp-1);
}

/* Top-level stats sections stack vertically with a thin divider between
   them. Mirrors the `main > section + section` rule the main app uses for
   "Top Players → Form → H2H → Commanders → Colours → Games" — first
   section sits flush, every subsequent one gets a 1px --divider line above
   it and matching vertical breathing room. */
.stats-section + .stats-section {
  margin-top: var(--sp-7);
  border-top: 1px solid var(--divider);
  padding-top: var(--sp-7);
}

/* ── Stat cards (.stat-cards > .stat-card)
   Boxed metric tiles for dashboard overviews. Each card is a --surface box
   with consistent padding and a fixed minimum height so a row of cards stays
   aligned regardless of value length. Value on top (display-weight), small-caps
   label underneath. Distinct from .pc-stats (which is a borderless row of
   tile *contents* inside a parent card) — .stat-card stands on its own surface. */
.stat-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--sp-3);
  margin-bottom: var(--sp-6);
}
.stat-card {
  background: var(--surface);
  border-radius: var(--radius);
  padding: var(--sp-4) var(--sp-3);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: 6.5rem;
  text-align: center;
}
/* Inside .page-card (which is --surface), nested tiles step UP to --surface-2
   so they read as a lighter raised tile — borderless card model, same in
   every context. */
.page-card .stat-card { background: var(--surface-2); }
.stat-card-val {
  font-size: 1.9rem;
  font-weight: 800;
  color: var(--text);
  line-height: 1;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}
.stat-card-val--sm { font-size: var(--sz-h3); }
.stat-card-label {
  font-size: var(--sz-label);
  font-weight: 700;
  letter-spacing: var(--ls-caps);
  color: var(--muted);
  line-height: 1.2;
}
.stat-card-sub {
  font-size: var(--sz-micro);
  color: var(--muted);
  letter-spacing: var(--ls-caps);
}

/* ── List-card row pattern (ol > li.list-card.cmd-entry)
   Universal primitive (@layer components). The main app uses this for the Top
   Commanders and H2H opponent lists; profile-page sections reuse it so their
   leaderboards look identical instead of dropping into a tabular grid. .cmd-art
   lives in the page layer because only the main app has Scryfall card art to
   render. */
ol.list-stack {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: 0;
  margin: 0; /* section gap + .btn-show-more margin own the rhythm, like main's ol */
}
.list-card {
  background: var(--surface);
  border-radius: var(--radius-md);
  transition: background var(--motion-fast) var(--ease-out),
              box-shadow var(--motion-base) var(--ease-out);
}
/* Inside .page-card (which is --surface), rows step UP to --surface-2 so they
   read as lighter raised rows (unified card model). The main-app click-to-expand
   hover shadow (page layer) is suppressed here because most profile rows are
   display-only — the expandable h2h opponents list opts back in (page layer,
   next to the .h2h-opp-row rules). */
.page-card .list-card { background: var(--surface-2); }
.page-card .list-card:hover { background: var(--surface-2); box-shadow: none; }

/* Expandable-row chevron (markup + wiring: stats-markup.js rowChevronHtml /
   wireExpandableRow). Sits in the trailing "chev" grid column spanning the
   collapsed rows — so it stays centred on the header part when the detail
   panel opens — and flips when the row is open. Wired rows are keyboard
   buttons (tabindex), so they take the standard focus ring. */
.row-chevron {
  grid-area: chev;
  display: inline-flex;
  align-items: center;
  color: var(--muted);
  transition: transform var(--motion-base) var(--ease-out);
}
.list-card.open > .row-chevron { transform: rotate(180deg); }
.list-card[tabindex]:focus-visible { outline: none; box-shadow: var(--focus-ring); }

.cmd-entry {
  display: grid;
  grid-template-columns: auto 1fr auto;
  grid-template-areas:
    "num name stat"
    "num meta sub";
  align-items: center;
  column-gap: var(--sp-3);
  row-gap: var(--sp-1);
  padding: var(--sp-3) var(--sp-4);
  font-size: var(--sz-body);
}
.cmd-entry.cmd-entry--with-art {
  grid-template-columns: auto auto 1fr auto;
  grid-template-areas:
    "num art name stat"
    "num art meta sub";
}
/* Click-to-expand variant (main app) appends the chevron column; the profile's
   display-only rows keep the base templates (no dead trailing gap). */
.cmd-entry.cmd-entry--expandable {
  grid-template-columns: auto 1fr auto auto;
  grid-template-areas:
    "num name stat chev"
    "num meta sub chev";
}
.cmd-entry.cmd-entry--with-art.cmd-entry--expandable {
  grid-template-columns: auto auto 1fr auto auto;
  grid-template-areas:
    "num art name stat chev"
    "num art meta sub chev";
}
.cmd-entry > .game-num        { grid-area: num; }
.cmd-entry > .cmd-art         { grid-area: art; }

/* Scryfall card art crop on a list-card row. Both pages use the same size /
   border / placement; the actual artwork is set via inline `background-image`
   from the cached image URL (main: scryfallCache; profile: API response). */
.cmd-art {
  width: 4.5rem;
  min-height: 3rem;
  align-self: stretch;
  border-radius: var(--radius-sm);
  background-color: var(--bg);
  background-size: cover;
  background-position: center 30%;
  border: 1px solid var(--border);
}
.cmd-art--empty { background: var(--bg); }

/* ── Avatars ───────────────────────────────────────────────────────────────
   Circular planeswalker portraits (lib/avatars.js). Used as the centred
   profile identity (with an edit badge that opens the picker modal) and as
   the head-to-head opponent thumbnail; `--mono` is the deterministic initials
   fallback for players without a chosen avatar. Image files are 256×256
   face-focused crops in /avatars/. Markup comes from avatarHtml() — never
   hand-roll an .avatar; keep this the single source. */
.avatar {
  box-sizing: border-box; /* selected ring thickens the border without changing diameter */
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  overflow: hidden;
  background-color: var(--bg);
  border: 1px solid var(--border);
  color: var(--text);
  font-weight: 600;
  line-height: 1;
  user-select: none;
}
.avatar-img { width: 100%; height: 100%; object-fit: cover; display: block; }
.avatar--mono { text-transform: uppercase; }
.avatar--sm { width: 2rem;   height: 2rem;   font-size: 0.7rem; }
.avatar--md { width: 3rem;   height: 3rem;   font-size: var(--sz-label); }
.avatar--lg { width: 4.5rem; height: 4.5rem; font-size: var(--sz-body); }
.avatar--xl { width: 6rem;   height: 6rem;   font-size: 1.6rem; }

/* Avatar identity (profile Account tab) — centred avatar with a corner edit
   badge; clicking opens the picker modal (progressive disclosure). */
.profile-field--avatar {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2);
}
.avatar-edit-trigger {
  position: relative;
  padding: 0;
  background: none;
  border: 0;
  cursor: pointer;
  border-radius: 50%;
  line-height: 0;
}
.avatar-edit-trigger:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.avatar-edit-trigger:hover .avatar { border-color: var(--text); }
.avatar-edit-badge {
  position: absolute;
  right: 0;
  bottom: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.85rem;
  height: 1.85rem;
  border-radius: 50%;
  background: var(--accent);
  color: var(--on-accent);
  border: 2px solid var(--surface);
  transition: background var(--motion-fast) var(--ease-out);
}
.avatar-edit-trigger:hover .avatar-edit-badge { background: var(--accent-h); }
.avatar-identity-name { font-weight: 600; color: var(--text); }

/* Avatar picker gallery (inside the modal). Fixed-width tracks spread with
   space-between so the left/right columns sit flush against the modal content
   edges — i.e. the outer circles line up with the Cancel/Save buttons below.
   Circles are a touch larger than the h2h size so the art is easy to read. */
.avatar-gallery {
  display: grid;
  grid-template-columns: repeat(auto-fill, 3.75rem);
  justify-content: space-between;
  gap: var(--sp-5) var(--sp-2);
  max-height: 60vh;
  overflow-y: auto;
  padding-block: var(--sp-1);
}
.avatar-option {
  width: 3.75rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-1);
  padding: 0;
  background: none;
  border: 0;
  cursor: pointer;
  color: var(--muted);
  font: inherit;
  transition: color var(--motion-fast) var(--ease-out);
}
.avatar-option .avatar { width: 3.75rem; height: 3.75rem; font-size: var(--sz-body); }
.avatar-option:hover { color: var(--text); }
.avatar-option:hover .avatar { border-color: var(--text); }
.avatar-option.selected { color: var(--text); }
.avatar-option.selected .avatar { border: 3px solid var(--accent); }
.avatar-option:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.avatar-option-name {
  max-width: 100%;
  font-size: var(--sz-label);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.cmd-entry > .cmd-name        { grid-area: name; }
.cmd-entry > .cmd-win-group   { grid-area: stat; justify-self: end; }
.cmd-entry > .cmd-last-played { grid-area: meta; }
.cmd-entry > .cmd-count-sub   { grid-area: sub; justify-self: end; }

.game-num {
  width: 2.5rem;
  flex-shrink: 0;
  font-size: var(--sz-label);
  font-weight: 600;
  color: var(--muted);
  text-align: left;
}
.game-num--milestone { color: var(--gold); }

.cmd-entry .cmd-name {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  color: var(--text);
  font-weight: 600;
  min-width: 0;
}
.cmd-entry .cmd-name.cmd-top { color: var(--gold); font-weight: 700; }

.cmd-win-group {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  white-space: nowrap;
}
.cmd-rate {
  color: var(--muted);
  font-size: var(--sz-sm);
  font-weight: 400;
}
.cmd-entry .cmd-rate {
  font-size: var(--sz-h3);
  font-weight: 700;
}
.cmd-rate--hot  { color: var(--gold); }
.cmd-rate--good { color: var(--win); }
.cmd-rate--avg  { color: var(--text); }
.cmd-rate--cold { color: var(--loss-text); }

.cmd-wins-hero {
  font-size: var(--sz-h3);
  font-weight: 700;
  color: var(--text);
}

.cmd-count-sub {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  color: var(--muted);
  font-size: var(--sz-sm);
  font-weight: 400;
  white-space: nowrap;
}

.cmd-last-played {
  font-size: var(--sz-sm);
  color: var(--muted);
}

/* ── Scryfall card hover tooltip (large card preview) ────────────────────────
   Element pair: <div id="card-tooltip" hidden><img id="card-tooltip-img"></div>
   Both pages position it via a mousemove listener following the cursor. */
#card-tooltip {
  position: fixed;
  pointer-events: none;
  z-index: 1000;
  border-radius: var(--radius-lg);
  overflow: hidden;
  border: 1px solid var(--border);
  width: 200px;
}
#card-tooltip-img { display: block; width: 100%; height: auto; }

/* ── Generic tooltip ──────────────────────────────────────────────────────── */
#tooltip {
  position: fixed;
  pointer-events: none;
  z-index: 1000;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--sp-1) var(--sp-3);
  font-size: var(--sz-sm);
  color: var(--text);
  white-space: pre-line;
  box-shadow: var(--shadow-1);
  max-width: 280px;
}

/* ── Toast notifications ──────────────────────────────────────────────────────
   Universal action feedback. Anchored bottom-center, click-to-dismiss, auto-
   dismiss when the circular progress ring completes. See toast.js. */
.toast-stack {
  position: fixed;
  bottom: var(--sp-6);
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2);
  z-index: 2000;
  pointer-events: none;
}
.toast {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  background: var(--surface);
  border: 1px solid var(--border);
  border-left-width: 3px;
  border-radius: var(--radius);
  padding: 0.6rem 0.85rem;
  font-size: var(--sz-sm);
  color: var(--text);
  box-shadow: var(--shadow-2);
  cursor: pointer;
  pointer-events: auto;
  max-width: min(420px, calc(100vw - var(--sp-7)));
  animation: toast-in var(--motion-base) var(--ease-out);
}
.toast--leaving      { animation: toast-out var(--motion-base) ease-in forwards; }
.toast--success      { border-left-color: var(--win);  }
.toast--error        { border-left-color: var(--loss); }
.toast--info         { border-left-color: var(--muted); }
.toast-msg           { flex: 1; line-height: 1.4; }
.toast-progress      { width: 16px; height: 16px; flex-shrink: 0; transform: rotate(-90deg); }
.toast-progress-track { fill: none; stroke: var(--border); stroke-width: 2.5; }
.toast-progress-bar {
  fill: none;
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-dasharray: 62.83;
  animation: toast-tick var(--toast-duration, 3000ms) linear forwards;
}
.toast--success .toast-progress-bar { stroke: var(--win);  }
.toast--error   .toast-progress-bar { stroke: var(--loss); }
.toast--info    .toast-progress-bar { stroke: var(--muted); }
@keyframes toast-tick { from { stroke-dashoffset: 0; } to { stroke-dashoffset: 62.83; } }
@keyframes toast-in   { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
@keyframes toast-out  { from { opacity: 1; transform: translateY(0); } to { opacity: 0; transform: translateY(8px); } }

/* ── Native form controls (cross-page) ─────────────────────────────────────
   Universal primitives (@layer components) so auth-family pages get matching
   date / number / select chrome without redefining them. */
input[type=date],
input[type=number],
select {
  appearance: none;
  -webkit-appearance: none;
  color-scheme: dark;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--sp-2) var(--sp-3);
  color: var(--text);
  font-size: var(--sz-sm);
  cursor: pointer;
  transition: border-color var(--motion-fast) var(--ease-out), box-shadow var(--motion-fast) var(--ease-out);
}
select option { background: var(--surface); color: var(--text); }
input[type=date]:hover,
input[type=number]:hover,
select:hover { border-color: var(--muted); }
input[type=date]:focus,
input[type=number]:focus,
select:focus { outline: none; box-shadow: var(--focus-ring); }
input[type=date]::-webkit-calendar-picker-indicator {
  filter: invert(0.7) sepia(0.3);
  cursor: pointer;
}
input[type=number] {
  width: 70px;
  text-align: center;
  -moz-appearance: textfield;
}
input[type=number]::-webkit-inner-spin-button { display: none; }

/* ── Date range inputs (from – to row) ─────────────────────────────────── */
.range-inputs {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}
.range-inputs input[type=date] { flex: 1 1 0; min-width: 0; }
.range-inputs span { color: var(--muted); font-size: var(--sz-sm); flex: 0 0 auto; }

/* ── Picker options (selectable row inside a picker dropdown) ──────────── */
.picker-opt {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-2);
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  background: transparent;
  font: inherit;
  text-align: left;
  cursor: pointer;
  font-size: var(--sz-sm);
  color: var(--text);
  user-select: none;
  transition: border-color var(--motion-fast) var(--ease-out), background var(--motion-fast) var(--ease-out);
}
.picker-opt:hover, .picker-opt:focus-visible {
  background: var(--surface-hover);
  outline: none;
}
.picker-opt.active {
  border-color: var(--text);
  background: var(--muted-soft);
  color: var(--text);
}

/* ── Date-range popup inner layout ─────────────────────────────────────── */
.popup-year-row {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  margin-bottom: var(--sp-4);
}
.popup-year-row > label,
.popup-custom label {
  font-size: var(--sz-label);
  font-weight: 600;
  letter-spacing: var(--ls-caps);
  color: var(--muted);
}
.popup-custom label { display: block; margin-bottom: var(--sp-2); }

/* ── Popup chrome (overlay + box + header + close) ─────────────────────── */
.popup-overlay { position: absolute; inset: 0; background: var(--scrim); cursor: pointer; }
.popup-box {
  position: relative;
  background: var(--surface);
  border-radius: var(--radius);
  padding: var(--sp-6);
  width: min(380px, calc(100vw - 2 * var(--edge-gap)));
  max-height: 90vh;
  max-height: 90dvh;
  overflow-y: auto;
  z-index: 1;
}
.popup-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: var(--sp-4);
}
.popup-close-btn {
  background: none;
  border: none;
  color: var(--muted);
  font-size: 1.3rem;
  cursor: pointer;
  line-height: 1;
  width: 2.4rem;
  height: 2.4rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-sm);
  padding: 0;
  margin: calc(var(--sp-2) * -1);
  transition: background var(--motion-fast) var(--ease-out), color var(--motion-fast) var(--ease-out);
}
.popup-close-btn:hover { color: var(--text); background: var(--surface-hover); }
.popup-close-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }

/* ── Filter-trigger buttons (range trigger + multiselect trigger) ──────── */
.btn-range-open,
.custom-multiselect-btn {
  background: var(--surface);
  border: 1px solid transparent;
  border-radius: var(--radius-lg);
  color: var(--text);
  /* Explicit height, not line-height arithmetic — Firefox ignores line-height
     on single-line <input>, so a shared height is the only way the filter-bar
     controls stay equal. Keep in sync with .sort-btn and .gf-cmd-search. */
  height: 2.3rem;
  padding: 0 var(--sp-4);
  font-size: var(--sz-sm);
  cursor: pointer;
  transition: background var(--motion-fast) var(--ease-out), border-color var(--motion-fast) var(--ease-out);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
}
.btn-range-open:hover,
.custom-multiselect-btn:hover { background: var(--surface-hover); }
.btn-range-open:focus-visible,
.custom-multiselect-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }

.custom-multiselect { position: relative; }
.custom-multiselect-btn { width: 220px; }
.ms-label {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: 1.4em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1;
  text-align: left;
}
.ms-chevron {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  color: var(--muted);
  line-height: 0;
}

.custom-multiselect-panel {
  display: none;
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-2);
  border-radius: var(--radius-lg);
  padding: var(--sp-2);
  z-index: 100;
  flex-direction: column;
  min-width: 220px;
  gap: var(--sp-1);
  max-height: 55vh;
  overflow-y: auto;
}
.custom-multiselect-panel.open { display: flex; }

/* ── Apply button (inside filter popup) ────────────────────────────────── */
.btn-apply {
  display: block;
  width: 100%;
  margin-top: var(--sp-3);
  background: var(--primary);
  border: none;
  border-radius: var(--radius-lg);
  color: var(--on-light);
  padding: var(--sp-2) var(--sp-4);
  font-size: var(--sz-sm);
  font-weight: 700;
  cursor: pointer;
  transition: background var(--motion-fast) var(--ease-out);
}
.btn-apply:hover:not(:disabled) { background: var(--primary-h); }
.btn-apply:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.btn-apply:disabled { opacity: 0.45; cursor: not-allowed; }

/* Filter triggers inside a .page-card sit on --surface; drop them to --bg
   so they read as inset controls (matches the .page-card .stat-card and
   .page-card .list-card pattern). */
.page-card .btn-range-open,
.page-card .custom-multiselect-btn { background: var(--bg); }

/* Year row inside a popup: full-width button + inline (non-overlay) panel
   so the popup-box's overflow-y: auto can scroll without clipping it. */
.popup-year-row .custom-multiselect { width: 100%; }
.popup-year-row .custom-multiselect-btn { width: 100%; background: var(--bg); }
.popup-year-row .custom-multiselect-panel {
  position: static;
  width: 100%;
  margin-top: var(--sp-1);
  box-shadow: none;
}
}

/* ╔═══════════════════════════════════════════════════════════════════════╗
   ║ COMPONENTS — auth-family + base resets (html, *, body)                ║
   ║ Universal body styling; .page-narrow opts in to flex-centered layout. ║
   ╚═══════════════════════════════════════════════════════════════════════╝ */
@layer components {
html {
  overflow-x: clip;
  /* iOS Safari inflates text it deems "primary" when no adjust is declared —
     blows up labels inside tight components and reads as cramped. Type is
     already sized responsively; opt out of the auto-inflation. */
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; font-family: inherit; }

/* ── Body base (every page) ──────────────────────────────────────────────────
   Universal body styling. Optional class modifiers tweak per-page intent:
     .page-main   — index.html (main app):  font-size: 15px
     .page-narrow — auth-box pages (login, signup, etc.):  flex-centered
   Other auth-family pages (admin, pod-manage, profile, styleguide) use the
   bare body — default block layout, no centering, no font-size override.   */
body {
  font-family: var(--font-ui);
  font-weight: 400;
  font-variant-numeric: tabular-nums;
  color-scheme: dark;
  color: var(--text);
  min-height: 100vh;
  /* Faint static ember glow from the top — a consistent ambient warmth on every
     page, with no image fetch. (On the main app it sits behind the header art.) */
  background:
    radial-gradient(160% 120% at 50% -20%, rgba(242,98,46,0.14) 0%, rgba(242,98,46,0.06) 38%, transparent 80%),
    var(--bg);
  background-repeat: no-repeat;
  background-attachment: fixed;
}

body.page-narrow {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: var(--sp-7) var(--edge-gap);
  /* glow comes from the base body rule — no per-page background needed */
}

/* Force --font-ui on form elements — browsers override font-family by default */
button, input, select, textarea, option, optgroup {
  font-family: var(--font-ui);
  font-weight: 400;
  font-variant-numeric: tabular-nums;
}

/* ── Box ── */
.auth-box {
  background: var(--surface);
  border-radius: var(--radius);
  padding: 2.1rem 2.25rem 1.9rem;
  width: min(430px, 100%);
  box-shadow: var(--shadow-3);
}
.auth-box--lg     { width: min(720px, 100%); }   /* long-form prose (legal pages) — comfortable reading measure */
.auth-box--sm     { width: min(480px, 100%); }   /* pod-select */

/* ── Branding ── */
.auth-logo {
  font-family: var(--font-ui);
  font-weight: 800;
  font-size: var(--sz-h1);
  color: var(--accent);
  text-align: center;
  letter-spacing: -0.02em;
  margin: 0 0 0.15rem;
  line-height: 1;
}
.auth-tagline {
  font-size: var(--sz-label);
  color: var(--muted);
  text-align: center;
  margin-bottom: 1.6rem;
  letter-spacing: 0.03em;
}

/* ── Hero branding (login / signup) ── */
.auth-hero {
  text-align: center;
  margin-bottom: var(--sp-6);
}
.auth-hero .auth-logo {
  font-size: 3.2rem;
  margin-bottom: 0.35rem;
}
.auth-hero .auth-tagline {
  font-size: var(--sz-body);
  margin-bottom: 0;
}
.auth-heading {
  font-size: var(--sz-h3);
  line-height: 1.25;
  font-weight: 700;
  color: #fff;
  margin-bottom: 1.1rem;
}

/* ── Form fields ── */
.form-row {
  display: flex;
  gap: 0.65rem;
}
.form-row .form-group { flex: 1; min-width: 0; }

.form-group {
  margin-bottom: 0.9rem;
}
.form-group label {
  display: block;
  font-size: var(--sz-label);
  font-weight: 600;
  color: var(--muted);
  letter-spacing: var(--ls-caps);
  margin-bottom: var(--sp-2);
}
.form-input {
  width: 100%;
  padding: 0.6rem 0.95rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  color: var(--text);
  font-family: inherit;
  font-size: var(--sz-sm);
  outline: none;
  transition: border-color var(--motion-fast) var(--ease-out), box-shadow var(--motion-fast) var(--ease-out);
}
.form-input:focus { box-shadow: var(--focus-ring); outline: none; }
.form-input::placeholder { color: var(--muted); }
.form-input.invalid { box-shadow: 0 0 0 2px var(--loss-border); }

.form-checkbox {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--sz-sm);
  color: var(--text);
  cursor: pointer;
  accent-color: var(--text);
}
.form-checkbox-hint { color: var(--muted); }

/* ── Custom single-select dropdown (no native <select>) ─────────────────────── */
/* Mirrors the main-app .custom-multiselect-btn (page layer). Use everywhere a
   dropdown is needed on auth pages — never use a native <select> directly. */
.custom-select { position: relative; }
.custom-select-btn {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  width: 100%;
  padding: 0.6rem 0.95rem; /* mirrors .form-input so both render equal-height in a form */
  background: var(--bg);
  border: 1px solid transparent;
  border-radius: var(--radius-lg);
  color: var(--text);
  font-family: inherit;
  font-size: var(--sz-sm);
  cursor: pointer;
  transition: border-color var(--motion-fast) var(--ease-out);
  text-align: left;
}
.custom-select-btn:hover { border-color: var(--border); }
.custom-select-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.custom-select-btn .ms-label {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--muted);
  /* Cancel the components-layer 1.4em min-height (pip-only guard for the
     main-app multiselect) — here the label is always text, and the extra
     height made the select taller than .form-input beside it. */
  min-height: 0;
}
.custom-select-btn.has-value .ms-label { color: var(--text); }
.custom-select-panel {
  display: none;
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 0.35rem;
  z-index: 200;
  flex-direction: column;
  gap: 0.15rem;
  max-height: 220px;
  overflow-y: auto;
  box-shadow: var(--shadow-2);
}
.custom-select-panel.open { display: flex; }
.custom-select-opt {
  padding: 0.45rem 0.65rem;
  border-radius: var(--radius-md);
  font-size: var(--sz-sm);
  color: var(--text);
  cursor: pointer;
  transition: background var(--motion-fast) var(--ease-out);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.custom-select-opt:hover { background: var(--muted-soft); }
.custom-select-opt.selected { color: var(--text); }

/* ── Data table ─────────────────────────────────────────────────────────── */
/* Canonical table for all auth pages (admin, pod-manage, etc.).
   Use data-table-wrap > data-table. All column headers left-aligned.
   Action cells: add class="td-actions" for right-align + vertical-align middle.
   Section divider rows: <tr class="table-section-row"><td colspan="N">Label</td></tr> */
.data-table-wrap {
  background: var(--surface);
  border-radius: var(--radius);
  overflow: hidden;
}
.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--sz-body);
}
.data-table thead { background: var(--bg); }
/* Column headings are group headings — same size as the cells beneath them
   (--sz-body); the muted colour + weight 700 carry the distinction. */
.data-table th {
  text-align: left;
  font-size: var(--sz-body);
  font-weight: 700;
  letter-spacing: var(--ls-caps);
  color: var(--muted);
  padding: 0.65rem var(--sp-4);
  white-space: nowrap;
}
.data-table td {
  padding: 0.7rem var(--sp-4);
  border-top: 1px solid var(--border);
  vertical-align: middle;
}
/* The dark --bg thead already separates the header from the body, so the first
   body row's top hairline directly beneath it is a redundant second divider —
   drop it. Inter-row dividers below stay. (Header-agnostic: any table without a
   thead just loses its leading top border, which reads cleaner too.) */
.data-table tbody tr:first-child td { border-top: none; }
.data-table tbody tr:hover td { background: rgba(255,255,255,0.04); }
.data-table tbody tr.row-fading { opacity: 0; transition: opacity var(--motion-base) var(--ease-out); }
.data-table .table-section-row td {
  font-size: var(--sz-body);
  font-weight: 700;
  letter-spacing: var(--ls-caps);
  color: var(--muted);
  background: var(--bg);
  padding: var(--sp-2) var(--sp-4);
  text-align: left;
}
/* Inside .page-card (which is --surface), the table wrap steps UP to --surface-2
   so it reads as a distinct raised block — matches the .page-card .stat-card /
   .list-card / .color-stat-box step-ups. The wrap is the ONLY place data tables
   live (admin, pod-manage), and both nest it in a --surface page-card, so without
   this the wrap's --surface fill vanished into the card. thead/section rows stay
   --bg (darker inset header) — now an even crisper strip on the raised wrap. */
.page-card .data-table-wrap { background: var(--surface-2); }
.cell-name { font-weight: 600; font-size: var(--sz-body); color: var(--text); }
.cell-sub  { font-size: var(--sz-label); color: var(--muted); margin-top: 0.1rem; }
/* Action column cell — right-aligned, vertically centered */
.td-actions { text-align: right; white-space: nowrap; vertical-align: middle; }


/* Fix browser autofill overriding dark background/text */
.form-input:-webkit-autofill,
.form-input:-webkit-autofill:hover,
.form-input:-webkit-autofill:focus {
  -webkit-box-shadow: 0 0 0 1000px var(--bg) inset;
  -webkit-text-fill-color: var(--text);
  caret-color: var(--text);
  border-color: var(--border);
  transition: background-color 9999s ease-in-out 0s;
}

/* Password show/hide */
.pw-wrap { position: relative; }
.pw-wrap .form-input { padding-right: 2.6rem; }
.pw-toggle {
  position: absolute;
  right: 0.7rem;
  top: 50%;
  transform: translateY(-50%);
  background: none;
  border: none;
  color: var(--muted);
  cursor: pointer;
  padding: 0.1rem;
  line-height: 1;
  transition: color var(--motion-fast) var(--ease-out);
}
.pw-toggle:hover { color: var(--text); }

/* Password strength bar */
.strength-wrap {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin-top: 0.35rem;
}
.strength-bar {
  flex: 1;
  height: 3px;
  background: var(--border);
  border-radius: 2px;
  overflow: hidden;
}
.strength-fill {
  height: 100%;
  width: 0;
  border-radius: 2px;
  transition: width var(--motion-base) var(--ease-out), background-color var(--motion-base) var(--ease-out);
}
.strength-text {
  font-size: var(--sz-label);
  color: var(--muted);
  min-width: 3.5rem;
  text-align: right;
  transition: color var(--motion-fast) var(--ease-out);
}

/* Field-level error */
.field-error {
  font-size: var(--sz-sm);
  color: var(--loss-text);
  margin-top: 0.22rem;
  display: none;
}
.field-error.show { display: block; }

/* Form-level messages */
.form-msg {
  font-size: var(--sz-sm);
  border-radius: var(--radius-lg);
  padding: 0.55rem 0.8rem;
  margin-bottom: 0.85rem;
  display: none;
  line-height: 1.45;
}
.form-msg.show { display: block; }
.form-msg--error { color: var(--loss-text); background: var(--loss-soft); border: 1px solid var(--loss-border); }
.form-msg--success { color: var(--win); background: var(--win-soft); border: 1px solid var(--win-border); }
.form-msg--info  { color: var(--text); background: var(--muted-soft); }

/* ── Primary button ── */
.auth-btn {
  width: 100%;
  padding: 0.65rem;
  background: var(--primary);
  border: none;
  border-radius: var(--radius-lg);
  color: var(--on-light);
  font-family: inherit;
  font-size: var(--sz-sm);
  font-weight: 600;
  cursor: pointer;
  transition: background var(--motion-fast) var(--ease-out), opacity var(--motion-fast) var(--ease-out);
  margin-top: 0.2rem;
}
.auth-btn:hover:not(:disabled) { background: var(--primary-h); }
.auth-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.auth-btn:disabled { opacity: 0.45; cursor: not-allowed; }

/* ── Links & footer ── */
/* Remember me row (login page) — checkbox label + forgot password link */
.remember-me-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-top: -0.35rem;
  margin-bottom: var(--sp-4);
}
.remember-me-row label {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: var(--sz-sm);
  color: var(--muted);
  cursor: pointer;
  accent-color: var(--text);
}
.remember-me-row .auth-link { font-size: var(--sz-sm); font-weight: 400; }

.auth-footer {
  text-align: center;
  font-size: var(--sz-sm);
  color: var(--muted);
  margin-top: 1.1rem;
}
.auth-link {
  color: var(--text);
  font-weight: 600;
  text-decoration: none;
}
.auth-link:hover { text-decoration: underline; }

/* ── Info icon box (verify-email, etc.) ── */
.auth-icon-wrap {
  text-align: center;
  margin: 1.2rem 0 0.8rem;
}
.auth-icon-wrap svg {
  color: var(--text);
}

/* ── Descriptive body text (help text under headings) ── */
.auth-body-text {
  font-size: var(--sz-body);
  color: var(--muted);
  line-height: 1.6;
  margin-bottom: 0.9rem;
}

/* ── Loading state ── */
.auth-loading[hidden] { display: none; } /* beats display:flex below when [hidden] is set */
.auth-loading {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.6rem;
  padding: var(--sp-8) 0;
  color: var(--muted);
  font-size: var(--sz-body);
}

/* ── Pod card body (groups name + meta, fills flex space) ── */
.pod-card-body { flex: 1; min-width: 0; }

/* ── Back-nav link (Back to pod, etc.) inside .form-section ── */
.form-section .auth-link { font-size: var(--sz-sm); }

/* ── Pod-select cards ── */
.pod-list {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  margin-bottom: var(--sp-2);
}
.pod-card {
  background: var(--surface-2);
  border-radius: var(--radius);
  padding: 0.85rem var(--sp-4);
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  cursor: pointer;
  transition: border-color var(--motion-fast) var(--ease-out),
              background var(--motion-fast) var(--ease-out),
              box-shadow var(--motion-base) var(--ease-out);
}
.pod-card:hover { background: var(--surface-hover); box-shadow: var(--shadow-lift); }
.pod-card-name {
  font-weight: 600;
  font-size: var(--sz-body);
  flex: 1;
}
.pod-card-meta {
  font-size: var(--sz-label);
  color: var(--muted);
}
.pod-card-role {
  font-size: var(--sz-label);
  font-weight: 700;
  letter-spacing: var(--ls-caps);
  color: var(--text);
  background: var(--muted-soft);
  border-radius: var(--radius-sm);
  padding: 0.15rem 0.4rem;
}
/* Invitation count badge */
.inv-count {
  display: inline-block;
  background: var(--accent);
  color: var(--on-accent);
  font-size: var(--sz-label);
  font-weight: 700;
  line-height: 1;
  padding: 0.2em 0.5em;
  border-radius: 999px;
  vertical-align: middle;
  margin-left: 0.4rem;
  position: relative;
  top: -1px;
}

/* Invitation card */
.invite-card {
  background: var(--surface-2);
  border-radius: var(--radius);
  padding: 0.85rem var(--sp-4);
}
.invite-card-header {
  font-weight: 600;
  font-size: var(--sz-body);
  margin-bottom: 0.2rem;
}
.invite-card-sub {
  font-size: var(--sz-label);
  color: var(--muted);
  margin-bottom: 0.7rem;
}
.invite-actions { display: flex; gap: var(--sp-2); }
.btn-accept {
  flex: 1;
  padding: 0.42rem;
  background: var(--primary);
  border: none;
  border-radius: var(--radius-lg);
  color: var(--on-light);
  font-family: inherit;
  font-size: var(--sz-sm);
  font-weight: 600;
  cursor: pointer;
  transition: background var(--motion-fast) var(--ease-out);
}
.btn-accept:hover { background: var(--primary-h); }
.btn-reject {
  flex: 1;
  padding: 0.42rem;
  background: var(--surface-2);
  border: 1px solid transparent;
  border-radius: var(--radius-lg);
  color: var(--text);
  font-family: inherit;
  font-size: var(--sz-sm);
  cursor: pointer;
  transition: background var(--motion-fast) var(--ease-out), color var(--motion-fast) var(--ease-out);
}
.btn-reject:hover { background: var(--loss-soft); color: var(--loss-text); }

/* Group heading — sits above a list of rows ("Your pods", "Invitations"), so it
   takes the group-heading size (--sz-body, never smaller than its items). */
.section-label {
  display: block;
  font-size: var(--sz-body);
  font-weight: 700;
  letter-spacing: var(--ls-caps);
  color: var(--muted);
  margin-bottom: var(--sp-2);
}
.section-heading {
  font-size: var(--sz-h3);
  line-height: 1.25;
  font-weight: 600;
  color: #fff;
  margin-bottom: var(--sp-3);
  letter-spacing: 0.01em;
}
.form-section { margin-bottom: 1.4rem; }

/* Create pod button */
.btn-create-pod {
  width: 100%;
  padding: var(--sp-3) var(--sp-4);
  background: none;
  border: 2px dashed var(--border);
  border-radius: var(--radius);
  color: var(--muted);
  font-family: inherit;
  font-size: var(--sz-sm);
  cursor: pointer;
  transition: border-color var(--motion-fast) var(--ease-out), color var(--motion-fast) var(--ease-out);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  white-space: nowrap;
}
.btn-create-pod:hover { border-color: var(--text); color: var(--text); }

/* Create pod inline form */
.create-pod-form {
  background: var(--surface-2);
  border-radius: var(--radius);
  padding: 0.9rem var(--sp-4);
  display: none;
  flex-direction: column;
  gap: 0.6rem;
}
.create-pod-form.show { display: flex; }
.create-pod-row { display: flex; gap: var(--sp-2); justify-content: space-between; }

/* ── Profile page card ───────────────────────────────────────────────────── */
.profile-card {
  background: var(--surface-2);
  border-radius: var(--radius);
  overflow: hidden;
}
.profile-field {
  padding: 0.85rem var(--sp-4);
  border-bottom: 1px solid var(--divider);
}
.profile-field:last-child { border-bottom: none; }
.profile-field-view {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
}
.profile-field-info { flex: 1; min-width: 0; }
/* Primary field label — row identifier inside a card.
   Use for labels that ARE the main text of a row (e.g. "Email", "Password").
   A standalone label→value pair, not a list heading, so it keeps --sz-sm —
   at --sz-body the label and its value read as one flat two-line block.
   Distinct from .section-label, which heads a whole list of rows. */
.profile-field-label {
  font-size: var(--sz-sm);
  font-weight: 600;
  color: var(--muted);
  margin-bottom: 0.2rem;
}
.profile-field-value {
  font-size: var(--sz-body);
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.profile-field-edit { margin-top: 0.9rem; }
.profile-edit-actions {
  display: flex;
  justify-content: space-between;
  gap: var(--sp-2);
  margin-top: var(--sp-3);
}


/* ── Utility classes ── */
.logout-form        { margin: var(--sp-6) 0 0; }
.logout-form .btn-sm { width: 100%; }
.auth-centered      { text-align: center; }
.auth-hint          { font-size: var(--sz-sm); color: var(--muted); line-height: 1.45; }
.auth-btn--link     { display: block; text-decoration: none; text-align: center; }
.form-msg--centered { text-align: center; }
.card-leaving       { opacity: 0.4; pointer-events: none; transition: opacity var(--motion-base) var(--ease-out); }
.row-fading         { opacity: 0; transition: opacity var(--motion-base) var(--ease-out); }

/* ── Page-level identity heading (pod name, user name) ── */
.page-heading {
  font-size: var(--sz-h1);
  line-height: 1.15;
  font-weight: 700;
  color: #fff;
  margin: 0;
}

/* ── Subpage header: back link left, title centered ── */
.page-header {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 2.25rem;
}
.page-header .auth-link {
  position: absolute;
  left: 0;
}

/* ── Tab navigation ─────────────────────────────────────────────────────── */
/* Usage: .tab-bar contains .tab-btn[data-tab="id"] elements.
   Each .tab-panel[id="id"] is shown/hidden via .active class.
   JS: see tab-switcher pattern in pod-manage.html. */
.tab-bar {
  display: flex;
  border-bottom: 1px solid var(--divider);
  margin-bottom: var(--sp-6);
}
.tab-btn {
  padding: var(--sp-2) var(--sp-4);
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  color: var(--muted);
  font-family: inherit;
  font-size: var(--sz-body);
  font-weight: 600;
  cursor: pointer;
  transition: color var(--motion-fast) var(--ease-out), border-color var(--motion-fast) var(--ease-out);
}
.tab-btn:hover:not(:disabled) { color: var(--text); }
.tab-btn.active { color: var(--text); border-bottom-color: var(--text); }
.tab-panel { display: none; }
.tab-panel.active { display: block; }

/* Leave pod inline confirm row */
.pod-leave-confirm {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-shrink: 0;
}
.pod-leave-confirm-text {
  font-size: var(--sz-sm);
  color: var(--muted);
  white-space: nowrap;
}
/* ── Pod-manage page ────────────────────────────────────────────────────── */

/* ── Full-width page layout (admin, pod-manage, profile, etc.) ──────────── */
/* Usage: <div class="page-wrap"><div class="page-card">…</div></div>        */
.page-wrap {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: var(--sp-8) var(--edge-gap);
}
.page-card {
  background: var(--surface);
  border-radius: var(--radius);
  padding: 2.1rem 2.25rem 1.9rem;
  width: 100%;
  max-width: 1350px;
  box-shadow: var(--shadow-3);
}

/* ── Modal overlay ───────────────────────────────────────────────────────── */
/* Usage: <div class="modal-overlay"><div class="modal-box">…</div></div>    */
.modal-overlay {
  position: fixed;
  inset: 0;
  background: var(--scrim);
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 100;
  padding: var(--edge-gap);
}
.modal-overlay.show { display: flex; }
.modal-box {
  background: var(--surface);
  border-radius: var(--radius);
  padding: var(--sp-6) 1.75rem;
  width: min(420px, 100%);
  box-shadow: var(--shadow-3);
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
}
.modal-title   { font-size: var(--sz-body); font-weight: 600; color: var(--text); }
.modal-body    { font-size: var(--sz-sm); color: var(--muted); line-height: 1.5; }
.modal-body strong { color: var(--text); }
.modal-actions { display: flex; justify-content: space-between; gap: var(--sp-2); }
/* Destructive confirm — solid red btn inside a modal (overrides ghost default) */
.modal-box .btn-sm--danger                    { background: var(--loss);   border-color: var(--loss);   color: var(--on-loss); }
.modal-box .btn-sm--danger:hover:not(:disabled) { background: var(--loss-h); border-color: var(--loss-h); }
/* A disabled confirm button (e.g. delete until the pod name is typed exactly)
   reads as clearly inactive — neutral grey, not a faded red/primary fill — and
   only switches to its live colour once enabled. */
.modal-box .btn-sm:disabled {
  background: var(--surface-2);
  color: var(--muted);
  opacity: 1;
  cursor: not-allowed;
}

/* ── Mobile ─────────────────────────────────────────────────────────────── */
@media (max-width: 600px) {
  :root { --edge-gap: var(--sp-4); }

  /* Full-width page layout: tighter vertical padding on small screens */
  .page-wrap { padding-top: var(--sp-6); padding-bottom: var(--sp-6); }
  .page-card { padding: var(--sp-6) var(--sp-5) var(--sp-5); }

  /* Auth box: reduce side padding */
  .auth-box { padding: 1.75rem var(--sp-6) var(--sp-6); }

  /* Hero logo (login/signup/pod-select): 3.2rem is aggressive on narrow phones */
  .auth-hero .auth-logo { font-size: 2.6rem; }

  /* First/last name row: stack instead of side-by-side */
  .form-row { flex-direction: column; gap: 0; }

  /* Modal: tighten internal padding since the overlay already pads by --edge-gap */
  .modal-box { padding: var(--sp-5) var(--sp-5); }

  /* Tables: allow horizontal scroll when columns overflow on mobile */
  .data-table-wrap { overflow-x: auto; }

  /* Tab bar: scroll horizontally if tabs exceed viewport width */
  .tab-bar { overflow-x: auto; scrollbar-width: none; flex-wrap: nowrap; }
  .tab-bar::-webkit-scrollbar { display: none; }

  /* ── Tap targets (auth-family / shared primitives) ──
     Row action buttons are sized for a cursor on desktop; give fingers a
     fairer hit area without reflowing the rows around them. */
  .icon-btn   { width: 2.5rem; height: 2.5rem; }
  .btn-xs     { padding: var(--sp-2) var(--sp-3); }
  .pw-toggle  { padding: var(--sp-2); right: 0.45rem; }
}

}

/* ╔═══════════════════════════════════════════════════════════════════════╗
   ║ PAGE — main-app components (index.html)                               ║
   ║ body.page-main scoping; main-app token augmentations.                 ║
   ╚═══════════════════════════════════════════════════════════════════════╝ */
@layer page {
/* Main-app token augmentations (rank-1 gradient, section spacing) */
:root {
  /* THE art scrim — every commander-art background (player cards, colour-stat
     boxes, game-log seat tiles, new-game player cards) layers one of these
     overlays over the image; JS composes `var(--*-overlay), url(...)`.
     One global strength, no per-component variants: darkens toward the bottom
     where the text sits, lighter up top so the art reads against the page. */
  --card-bg-overlay:  linear-gradient(rgba(13,15,17,0.78) 0%, rgba(13,15,17,0.90) 100%);
  --rank1-bg-overlay: linear-gradient(160deg, rgba(13,15,17,0.74) 0%, rgba(60,30,12,0.86) 100%);
  --ng-card-overlay:  linear-gradient(to top, rgba(13,15,17,0.97) 0%, rgba(13,15,17,0.82) 50%, rgba(13,15,17,0.38) 100%); /* new-game player card art scrim (game-form.js) — bottom-anchored so the form controls stay readable */
  --rank1-gradient:   linear-gradient(160deg, #242a31 0%, #3a2413 100%);
  --sp-section:       4rem;  /* 64px — gap between top-level sections in main */
}

body.page-main {
  font-size: 15px;
  /* The header band is the only ember at the top here — suppress the body glow
     so the two don't stack and pull in different directions. */
  background: var(--bg);
}

/* ── Header ── */
/* Slim ember-gradient band — no photo. Warmth diffuses from the top-left
   (behind the title) into the near-black base; a hairline divides it from
   the content below. */
header {
  background:
    linear-gradient(115deg, rgba(242,98,46,0.11) 0%, rgba(242,98,46,0.05) 52%, transparent 92%),
    var(--bg);
  border-bottom: 1px solid var(--divider);
  padding: var(--sp-8) max(var(--edge-gap), var(--sp-8)) var(--sp-6);
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--sp-4);
  position: relative;
}
/* Film-grain grit overlay — adds tactile texture to the flat ember band */
header::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  opacity: 0.11;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
.header-main { position: relative; z-index: 1; display: flex; flex-direction: column; min-width: 0; flex: 1; }
header h1 {
  font-size: 3rem;
  line-height: 1.05;
  font-family: var(--font-ui);
  font-weight: 800;
  letter-spacing: var(--ls-display);
  color: var(--text);
}

.header-meta {
  font-size: var(--sz-sm);
  color: var(--muted);
  margin-top: var(--sp-2);
  letter-spacing: var(--ls-caps);
}

/* ── Section Nav ── */
#section-nav-wrap {
  position: sticky;
  top: 0;
  z-index: 50;
  background: var(--bg);
  border-bottom: 1px solid var(--divider);
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  row-gap: var(--sp-2);
  gap: var(--sp-3);
  padding: var(--sp-2) var(--edge-gap);
}
/* Active-filter chip row: full-width below the nav, shown only when any filter is non-default */
.snav-chips {
  flex: 1 1 100%;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-2);
  /* Match parent #section-nav-wrap's padding-bottom (var(--sp-2)) so the
     visual space above the chips equals the space below them. */
  padding-top: var(--sp-2);
  border-top: 1px solid var(--divider);
}
.snav-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  background: var(--muted-soft);
  color: var(--text);
  font-size: var(--sz-micro);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--ls-caps);
  padding: var(--sp-1) var(--sp-2);
  border-radius: 999px;
  white-space: nowrap;
}
.snav-chip--neutral { background: var(--muted-soft); color: var(--text); }
.snav-chip-clear {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-1);
  background: var(--muted-soft);
  color: var(--muted);
  cursor: pointer;
  font: inherit;
  font-size: var(--sz-micro);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--ls-caps);
  /* Same padding + no border so total height matches .snav-chip exactly */
  padding: var(--sp-1) var(--sp-2);
  border: 0;
  border-radius: 999px;
  transition: color var(--motion-fast) var(--ease-out), background var(--motion-fast) var(--ease-out);
}
.snav-chip-clear svg { width: 0.7rem; height: 0.7rem; flex-shrink: 0; }
.snav-chip-clear:hover { color: var(--text); background: var(--surface-hover); }
#section-nav {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: var(--sp-1);
  flex: 1;
  min-width: 0;
  overflow-x: auto;
  scrollbar-width: none;
}
#section-nav::-webkit-scrollbar { display: none; }
.snav-link {
  font-size: var(--sz-label);
  font-weight: 600;
  letter-spacing: var(--ls-caps);
  color: var(--muted);
  text-decoration: none;
  padding: var(--sp-1) var(--sp-3);
  border-radius: 999px;
  white-space: nowrap;
  transition: color var(--motion-fast) var(--ease-out), background var(--motion-fast) var(--ease-out);
}
.snav-link:hover { color: var(--text); }
.snav-link.active {
  color: var(--text);
  background: var(--muted-soft);
}

/* ── Sticky-nav filter cluster (right side of #section-nav-wrap) ── */
.snav-filters {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  flex-shrink: 0;
}
.snav-filters .custom-multiselect-panel { left: auto; right: 0; }
.snav-filters .custom-multiselect-btn { width: auto; min-width: 130px; max-width: 220px; }
.snav-filters #cmd-color-btn { min-width: 110px; }

/* ── Phase 3 / 6 / 7: mobile filter sheet trigger + count badge ── */
/* Typography matches .snav-link so it aligns optically with the anchor row. */
.snav-filter-sheet-btn {
  position: relative;
  display: none; /* hidden on desktop; shown ≤600px via media query */
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-3);
  background: var(--surface);
  border: 1px solid transparent;
  border-radius: 999px;
  color: var(--muted);
  font: inherit;
  font-size: var(--sz-label);
  font-weight: 600;
  letter-spacing: var(--ls-caps);
  cursor: pointer;
  transition: background var(--motion-fast) var(--ease-out), border-color var(--motion-fast) var(--ease-out), color var(--motion-fast) var(--ease-out);
}
.snav-filter-sheet-btn:hover { color: var(--text); background: var(--surface-hover); }
.snav-filter-sheet-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.snav-filter-sheet-btn svg { flex-shrink: 0; }
.snav-filter-sheet-btn.has-active { color: var(--text); background: var(--muted-soft); }
.snav-filters-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 1.1rem;
  height: 1.1rem;
  padding: 0 var(--sp-1);
  border-radius: 999px;
  background: var(--accent);
  color: var(--on-accent);
  font-size: var(--sz-micro);
  font-weight: 700;
  line-height: 1;
}

/* Per-field labels and bottom-sheet footer — visible only inside the open sheet */
.snav-sheet-label,
.snav-sheet-footer { display: none; }
.gf-btn--active {
  border-color: var(--text) !important;
}
/* Global commander search input in the sticky filter bar — matches the
   --surface fill of its sibling sort/range/multiselect triggers */
.gf-cmd-search {
  background: var(--surface);
  border: 1px solid transparent;
  border-radius: var(--radius-lg);
  color: var(--text);
  height: 2.3rem; /* equal height with .custom-multiselect-btn / .btn-range-open */
  padding: 0 var(--sp-3);
  font-size: var(--sz-sm);
  font-family: inherit;
  outline: none;
  width: 150px;
  transition: border-color var(--motion-fast) var(--ease-out), box-shadow var(--motion-fast) var(--ease-out);
}
.gf-cmd-search:hover { border-color: var(--muted); }
.gf-cmd-search:focus { box-shadow: var(--focus-ring); }
.gf-cmd-search::placeholder { color: var(--muted); }
/* ── Shared picker styles (search inputs + option rows) ──────────────────── */
/* Add .picker-input to any search input inside a picker dropdown.           */
.picker-input {
  background: var(--bg);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  color: var(--text);
  font-size: var(--sz-sm);
  padding: var(--sp-2) var(--sp-3);
  outline: none;
  transition: border-color var(--motion-fast) var(--ease-out), box-shadow var(--motion-fast) var(--ease-out);
}
.picker-input:focus { box-shadow: var(--focus-ring); }
.picker-input::placeholder { color: var(--muted); }


/* Search input positioned sticky inside a scrollable panel */
.gf-player-search { width: 100%; position: sticky; top: 0; z-index: 1; }
/* Segmented Any / Together toggle, pinned to top of the player panel.
   Disabled "Together" stays in the layout but is dimmed; tooltip explains why. */
.ms-mode-toggle {
  display: flex;
  background: var(--bg);
  border-radius: var(--radius-md);
  padding: 2px;
  margin-bottom: var(--sp-2);
  gap: 2px;
}
.ms-mode-btn {
  flex: 1;
  background: transparent;
  border: none;
  color: var(--muted);
  padding: var(--sp-1) var(--sp-2);
  font-size: var(--sz-sm);
  font-family: inherit;
  font-weight: 600;
  cursor: pointer;
  border-radius: var(--radius-xs);
  transition: background var(--motion-fast) var(--ease-out), color var(--motion-fast) var(--ease-out);
}
.ms-mode-btn:hover:not(.active):not(:disabled) { color: var(--text); }
.ms-mode-btn.active { background: var(--text); color: var(--on-light); }
.ms-mode-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.ms-mode-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }

/* ── Main layout ── */
main {
  max-width: 1350px;
  margin: 0 auto;
  padding: var(--sp-7) var(--edge-gap);
  display: flex;
  flex-direction: column;
  gap: 0;
  min-width: 0;
}
/* Anchor target offset: clears the sticky #section-nav (~2.7rem tall) */
main > section {
  scroll-margin-top: 3rem;
}
/* Divider + balanced spacing between top-level stats sections */
main > section + section {
  margin-top: var(--sp-7);
  border-top: 1px solid var(--divider);
  padding-top: var(--sp-7);
  /* padding-top pushes heading down — subtract it so heading still lands below nav */
  scroll-margin-top: calc(3rem - var(--sp-7));
}

/* First section sits directly below sticky nav — strip the double-border */
#leaderboard {
  margin-top: 0;
  padding-top: var(--sp-6);
  border-top: none;
}


/* ── Controls ── */
.controls {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
  align-items: flex-start;
}
.players-add-field label,
.ng-label {
  font-size: var(--sz-label);
  font-weight: 600;
  letter-spacing: var(--ls-caps);
  color: var(--muted);
}
.ng-label { display: block; margin-bottom: var(--sp-2); }



/* ── Sort dropdown (button + menu) ── */
.sort-wrap { position: relative; }
.sort-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  background: var(--surface);
  border: 1px solid transparent;
  border-radius: var(--radius-lg);
  color: var(--text);
  height: 2.3rem; /* equal height with .custom-multiselect-btn / .btn-range-open */
  padding: 0 var(--sp-3);
  font-size: var(--sz-sm);
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  transition: border-color var(--motion-fast) var(--ease-out), background var(--motion-fast) var(--ease-out);
}
.sort-btn:hover,
.sort-btn[aria-expanded="true"] { background: var(--surface-hover); }
.sort-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }
.sort-btn .sort-dir {
  color: var(--text);
  font-weight: 600;
  line-height: 1;
  margin-right: var(--sp-1);
}
.sort-btn .sort-chev {
  display: inline-flex;
  align-items: center;
  color: var(--muted);
  opacity: 0.7;
  transition: transform var(--motion-fast) var(--ease-out), opacity var(--motion-fast) var(--ease-out);
}
.sort-btn[aria-expanded="true"] .sort-chev { transform: rotate(180deg); opacity: 1; }
.sort-opt .sort-dir {
  color: var(--text);
  font-weight: 600;
  margin-right: var(--sp-1);
}
.sort-opt.active .sort-dir { color: inherit; }

/* Floating dropdown panels (sort menu + colour filter) — one anchored-panel
   shape, two anchors. Add a new panel to this selector list rather than
   copying the block. */
.sort-panel,
.custom-color-panel {
  display: none;
  position: absolute;
  top: calc(100% + 4px);
  background: var(--surface);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-2);
  border-radius: var(--radius-lg);
  padding: var(--sp-2);
  z-index: 100;
  flex-direction: column;
  gap: var(--sp-1);
}
.sort-panel.open,
.custom-color-panel.open { display: flex; }
.sort-panel         { right: 0; min-width: 9rem; }
.custom-color-panel { left: 0;  min-width: 140px; max-height: 290px; overflow-y: auto; }

.sort-opt {
  background: var(--bg);
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  color: var(--text);
  padding: var(--sp-1) var(--sp-3);
  font-size: var(--sz-sm);
  cursor: pointer;
  text-align: left;
  white-space: nowrap;
  font-family: inherit;
  transition: border-color var(--motion-fast) var(--ease-out), background var(--motion-fast) var(--ease-out), color var(--motion-fast) var(--ease-out);
}
.sort-opt:hover { background: var(--surface-hover); }

/* ── Check pills & colour options (shared base) ── */
.check-pill,
.color-option {
  display: flex;
  align-items: center;
  padding: var(--sp-1) var(--sp-3);
  background: var(--bg);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);   /* match the picker-input search box in the same dropdown */
  font-size: var(--sz-sm);
  cursor: pointer;
  user-select: none;
  transition: border-color var(--motion-fast) var(--ease-out), background var(--motion-fast) var(--ease-out);
}
.check-pill { gap: var(--sp-2); }

/* Symbol-only color filter options — pips are the identifier; no name text */
.color-option {
  gap: 0;
  white-space: nowrap;
  justify-content: center;
  min-width: 3rem;
}
.color-option .color-pips { margin: 0; }
.color-option .mana-pip { width: 20px; height: 20px; }
/* "All colors" option falls back to text label */
.color-option--all {
  justify-content: flex-start;
  color: var(--muted);
  font-weight: 600;
  letter-spacing: var(--ls-caps);
  font-size: var(--sz-label);
}
/* ── Custom themed checkboxes ── */
input[type=checkbox] {
  appearance: none;
  -webkit-appearance: none;
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  border: 1.5px solid var(--muted);
  border-radius: var(--radius-xs);
  background: var(--surface);
  cursor: pointer;
  /* Flex-centre the checkmark so it never drifts to a corner */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  vertical-align: middle;
  transition: border-color var(--motion-fast) var(--ease-out), background var(--motion-fast) var(--ease-out);
}
input[type=checkbox]:hover { border-color: var(--text); }
input[type=checkbox]:checked {
  background: var(--text);
  border-color: var(--text);
}
input[type=checkbox]:checked::after {
  content: '';
  width: 4px;
  height: 8px;
  border: 2px solid var(--bg);
  border-top: none;
  border-left: none;
  transform: rotate(45deg);
  margin-bottom: 2px;   /* optical centre — checkmark sits a touch high */
}

.check-pill input[type=checkbox] { cursor: pointer; }
.check-pill-all {
  margin-bottom: var(--sp-1);
}
.custom-color-filter { position: relative; }
.color-option:hover { background: var(--surface-hover); }

/* THE picked-option state, shared by every option row in the main app: a
   neutral high-contrast border signal on a muted-soft fill (never the ember —
   see the Color cheat sheet). Must stay AFTER the :hover rules above so a
   picked row keeps its signal while hovered. */
.sort-opt.active,
.check-pill:has(input:checked),
.color-option.selected {
  border-color: var(--text);
  background: var(--muted-soft);
  color: var(--text);
}

/* ── Player Cards ── */
#player-cards {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--sp-4);
  align-items: stretch;
}
.player-card {
  background: var(--surface);
  border-radius: var(--radius);
  padding: var(--sp-5);
  text-align: center;
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  box-shadow: var(--shadow-1);
}
.player-card.rank-1 {
  background: var(--rank1-gradient);
  box-shadow: 0 0 0 1.5px var(--gold-glow), var(--shadow-1);
}
.player-card--has-art {
  box-shadow:
    inset 0 0 0 1px rgba(255,255,255,0.10),
    0 4px 24px rgba(0,0,0,0.45);
}
.player-card--has-art.rank-1 {
  box-shadow:
    0 0 0 1.5px var(--gold-glow),
    0 4px 24px rgba(0,0,0,0.45);
}

/* Rank badge */
.player-card .rank {
  position: absolute;
  top: var(--sp-2);
  left: var(--sp-3);
  font-size: var(--sz-label);
  font-weight: 700;
  color: var(--muted);
}
.player-card.rank-1 .rank { color: var(--gold); font-weight: 800; }

/* Identity section */
.pc-identity {
  margin-top: var(--sp-4);
  margin-bottom: var(--sp-5);
}
.player-card .name {
  font-weight: 700;
  font-size: var(--sz-h3);
}
.player-card .player-epithet {
  font-size: var(--sz-label);
  font-style: italic;
  color: var(--muted);
}
.pc-color-pips { position: absolute; top: var(--sp-3); right: var(--sp-3); display: flex; gap: var(--sp-1); }
.pc-color-pips .mana-pip { width: 14px; height: 14px; }

/* Win rate section — the number is the hero: large, tight, tabular numerals.
   (Previously an outlined P/T-style box; the skeuomorphic border was dropped
   in favour of clean numerals — see the numeric-hero rule in CLAUDE.md.) */
.pc-winrate {
  margin-bottom: var(--sp-5);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2);
}
.player-card .win-pct {
  font-size: 2.6rem;
  font-weight: 800;
  color: var(--win);
  line-height: 1;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  display: inline-block;
}
.player-card.rank-1 .win-pct { color: var(--gold); }
.pc-skill--uncertain { opacity: 0.5; }


/* Form section (recent + streak) */
.pc-form {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2);
  padding-top: var(--sp-3);
  border-top: 1px solid var(--divider);
}
.pc-winrate-label,
.recent-label {
  font-size: var(--sz-micro);
  letter-spacing: var(--ls-caps);
  color: var(--muted);
}
.pc-winrate-label { font-size: var(--sz-label); font-weight: 600; }
.pc-winrate-record { font-size: var(--sz-micro); letter-spacing: var(--ls-caps); color: var(--muted); }
.form-pips {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: var(--sp-1);
  margin-top: var(--sp-1);
}
.form-pip {
  font-size: var(--sz-sm);
  line-height: 1;
}
.form-pip--loss { opacity: 0.8; }
.player-card .streak-label {
  font-size: var(--sz-label);
  font-weight: 700;
  color: var(--gold);
  letter-spacing: var(--ls-caps);
  margin-bottom: var(--sp-1);
}
.player-card .streak-label--drought { color: var(--loss-text); }

/* ── H2H custom player dropdown ── */
.h2h-player-wrap { width: 100%; margin-top: var(--sp-2); }
.h2h-player-wrap .custom-multiselect-btn { width: 100%; }
.h2h-player-panel { width: 100%; }
/* .h2h-player-opt uses .picker-opt for all visual style */

/* ── Head-to-Head ── */
.h2h-layout {
  display: flex;
  gap: var(--sp-7);
  align-items: flex-start;
}
.h2h-nav {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  flex-shrink: 0;
  position: sticky;
  top: var(--sp-4);
}
.h2h-card-col {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2);
}
.h2h-nav .player-card {
  width: 240px;
  cursor: default;
}
.h2h-opponents {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.h2h-opp-row {
  border-left: 3px solid transparent;
}
.h2h-opp-row.h2h-row--winning { border-left-color: var(--win); }
.h2h-opp-row.h2h-row--losing  { border-left-color: var(--loss); }
/* Same as .cmd-entry .cmd-name */
.h2h-opp-name { color: var(--text); font-weight: 600; min-width: 0; }
/* Win/loss values inside cmd-win-group. The % and its label sit in fixed-width
   slots (% right-aligned, label left-aligned) so the win/loss pair form clean
   columns within and across rows instead of rag-anchoring on label width. */
.h2h-stat-win  { color: var(--win);       font-weight: 700; font-size: var(--sz-body); }
.h2h-stat-loss { color: var(--loss-text); font-weight: 700; font-size: var(--sz-body); }
.h2h-stat-sub  { color: var(--muted); font-size: var(--sz-label); }
.h2h-opp-row .h2h-stat-win,
.h2h-opp-row .h2h-stat-loss {
  min-width: 3rem;
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.h2h-opp-row .h2h-stat-sub { min-width: 4.6rem; }
/* ── Proportion bar ──────────────────────────────────────────────────────────
   Slim horizontal meter for expanded list-card detail panels: the H2H matchup
   split (win / loss / other) and the commander pilot-share fill. Track is the
   inset --bg field; segments are functional status hues. Segment widths are the
   one dynamic property — set at runtime via el.style.width (never inline HTML). */
.prop-bar {
  display: flex;
  height: 0.5rem;
  background: var(--bg);
  border-radius: var(--radius-xs);
  overflow: hidden;
}
.prop-bar-seg { height: 100%; box-sizing: border-box; transition: width var(--motion-base) var(--ease-out); }
/* Crisp cut between adjacent segments (track-coloured) so neighbouring hues
   don't blend. box-sizing keeps the border inside the JS-set width. */
.prop-bar-seg + .prop-bar-seg { border-left: 2px solid var(--bg); }
.prop-bar-seg--win   { background: var(--win); }
.prop-bar-seg--cowin { background: var(--gold); }   /* shared victory (both won) */
.prop-bar-seg--loss  { background: var(--loss); }
.prop-bar-seg--muted { background: var(--surface-2); }

/* Expanded detail — spans full grid width. The matchup bar visualises the
   my-win / co-win / their-win / other split; the legend restates the counts in
   status hues (co-win = gold, the shared-victory bucket). */
.h2h-opp-detail {
  display: none;
  grid-column: 1 / -1;
  flex-direction: column;
  border-top: 1px solid var(--divider);
  padding-top: var(--sp-3);
  margin-top: var(--sp-3);
  gap: var(--sp-2);
}
.h2h-opp-row.open .h2h-opp-detail { display: flex; }
.h2h-matchup-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-1) var(--sp-5);
  font-size: var(--sz-sm);
  color: var(--muted);
}
/* Every legend number is the SAME size (--sz-sm, inherited) — distinction comes
   from weight + status colour, never from a font-size step (which made the
   baseline jump). Colours mirror the matching bar segment. */
.h2h-legend-item { white-space: nowrap; }
.h2h-legend-num { font-weight: 700; font-variant-numeric: tabular-nums; }
.h2h-legend-num--win   { color: var(--win); }
.h2h-legend-num--cowin { color: var(--gold); }
.h2h-legend-num--loss  { color: var(--loss-text); }
.h2h-legend-num--other { color: var(--muted); }
.h2h-matchup-total { font-size: var(--sz-sm); color: var(--muted); }

/* .list-card and the ol reset are universal primitives (@layer components).
   Main-app overrides (this page layer): index.html keeps cursor:pointer + a
   hover shadow over --surface-hover (rows are click-to-expand); the art-bearing
   form of .cmd-entry adds a 5th column for the card art and a spanning details
   row. Auth-family pages use the base art-less 3-column form and have the hover
   shadow suppressed via the `.page-card .list-card:hover` rule above. */
.list-card { cursor: pointer; }
.list-card:hover { background: var(--surface-hover); box-shadow: var(--shadow-lift); }
/* Inside .page-card (profile) rows are display-only — no pointer, no hover
   lift — EXCEPT the h2h opponents list, which is click-to-expand and keeps
   the full clickable affordance. */
.page-card .list-card { cursor: default; }
.page-card .h2h-opp-row { cursor: pointer; }
.page-card .h2h-opp-row:hover { background: var(--surface-hover); box-shadow: var(--shadow-lift); }
ol { list-style: none; display: flex; flex-direction: column; gap: var(--sp-2); }

/* Commander-entry grids (base 3-col and the art-bearing 4-col --with-art
   variant) live in @layer components — renderers emit cmd-entry--with-art
   when the row carries art. Forcing the art grid on ALL .cmd-entry here made
   art-less rows (profile "By pod") render an empty art column with a doubled
   rank→name gap. Only main-app value tweaks remain below. */
.cmd-entry > .cmd-win-group   { justify-self: start; }
.cmd-entry > .cmd-count-sub   { justify-self: start; }
/* On profile rows the sub line ("X wins · N plays") is much wider than the %
   above it, so start-alignment leaves the rate floating mid-column — keep the
   cluster flush with the row's right edge there instead. */
.page-card .cmd-entry > .cmd-win-group,
.page-card .cmd-entry > .cmd-count-sub { justify-self: end; }
.cmd-entry > .cmd-details     { grid-column: 1 / -1; }

.h2h-opp-row {
  display: grid;
  grid-template-columns: auto 1fr auto auto;
  grid-template-areas:
    "num name top chev"
    "num last bot chev";
  align-items: center;
  column-gap: var(--sp-3);
  row-gap: var(--sp-1);
  padding: var(--sp-3) var(--sp-4);
  font-size: var(--sz-body);
  border-left: 3px solid transparent;
}
.h2h-opp-row > .game-num       { grid-area: num; }
.h2h-opp-row > .h2h-opp-name   { grid-area: name; }
.h2h-opp-row > .cmd-win-group  { grid-area: top; justify-self: start; }
.h2h-opp-row > .h2h-opp-games  { grid-area: last; }
.h2h-opp-row > .cmd-count-sub  { grid-area: bot; justify-self: start; }
.h2h-opp-row > .h2h-opp-detail { grid-column: 1 / -1; }

/* Avatar variant — adds a leading art column for the opponent's avatar,
   mirroring .cmd-entry--with-art on the commander list. */
.h2h-opp-row--with-art {
  grid-template-columns: auto auto 1fr auto auto;
  grid-template-areas:
    "num art name top chev"
    "num art last bot chev";
}
/* The avatar uses --md (3rem), which equals .cmd-art's height (min-height 3rem)
   so it reads at the same size as the commander-list card art. A fixed circle
   is used rather than align-self:stretch + aspect-ratio, because a stretched
   circle lets the 256px <img> drive the flex width and balloons the avatar. */
.h2h-opp-row > .h2h-opp-art { grid-area: art; }

.game-row {
  display: grid;
  grid-template-columns: auto 1fr auto auto;
  grid-template-areas:
    "num cmds milestones chev"
    "num pills milestones chev";
  align-items: center;
  column-gap: var(--sp-3);
  row-gap: var(--sp-1);
  padding: var(--sp-3) var(--sp-4);
  font-size: var(--sz-body);
}
.game-row > .game-num          { grid-area: num; }
.game-row > .commanders-cell   { grid-area: cmds; }
.game-row > .result-pills      { grid-area: pills; }
.game-row > .game-milestones   { grid-area: milestones; align-self: center; display: flex; flex-direction: column; gap: var(--sp-1); align-items: flex-end; }
.game-row > .game-deck-details { grid-column: 1 / -1; }


/* .cmd-entry .cmd-name (base) is in @layer components. This main-app variant
   uses an inner .cmd-name-text span so the name itself is the hover target. */
.cmd-entry .cmd-name { cursor: default; }
.cmd-entry .cmd-name-text { cursor: default; }
.cmd-entry .cmd-name-text:hover { color: var(--text); }
.cmd-entry .cmd-name.cmd-top .cmd-name-text { color: var(--gold); font-weight: 700; }

/* ── Shared expanded detail panels (span full grid width) ── */
.cmd-details,
.game-deck-details {
  display: none;
  font-size: var(--sz-body);
  border-top: 1px solid var(--divider);
  padding-top: var(--sp-3);
  margin-top: var(--sp-3);
}
.cmd-details {
  grid-template-columns: auto auto 1fr auto;
  align-items: center;
  gap: var(--sp-2) var(--sp-3);
}
.cmd-details.open { display: grid; }
/* Pilot row: avatar · name · win-rate bar · "Z% (x/y) · W wins". Rows use
   display:contents so all four columns share the ONE parent grid — the bar (1fr)
   column is then identical width on every row, so the bar tracks are equal
   length and the name/value columns line up. The fill = each pilot's win rate
   over their complete games with this commander, shown with its record (x/y)
   so it visibly reconciles with the total win count beside it (which includes
   unrated wins and sums to the commander's headline count). Pilots with no
   complete games show the win count alone with an empty bar track (present but
   unfilled — keeps the column consistent, states no misleading rate). */
.cprow { display: contents; }

.h2h-opp-games { font-size: var(--sz-sm); color: var(--muted); }

/* Shared panel header row */
.cprow-tracked {
  grid-column: 1 / -1;
  font-size: var(--sz-label);
  font-weight: 700;
  letter-spacing: var(--ls-caps);
  color: var(--muted);
  opacity: 0.6;
  margin-bottom: 0;
}
.cprow-name { color: var(--text); font-size: var(--sz-body); font-weight: 600; min-width: 4.5rem; }
.cprow-pct  { color: var(--muted); font-size: var(--sz-sm); text-align: right; }

.btn-show-more {
  display: block;
  margin: var(--sp-3) auto 0;
  background: var(--surface);
  border: none;
  border-radius: var(--radius-md);
  color: var(--text);
  padding: var(--sp-1) var(--sp-6);
  font-size: var(--sz-sm);
  cursor: pointer;
  width: auto;
  transition: background var(--motion-fast) var(--ease-out), color var(--motion-fast) var(--ease-out);
}
.btn-show-more:hover { background: var(--surface-hover); }
/* Inside .page-card (profile) the wrapper is already --surface, so the pill
   steps UP a tier to stay visible (controls one tier above local bg). */
.page-card .btn-show-more { background: var(--surface-2); }
.page-card .btn-show-more:hover { background: var(--surface-hover); }




.game-delete-confirm {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--sz-sm);
  color: var(--muted);
  flex-shrink: 0;
}
.game-delete-yes,
.game-delete-no {
  border: none;
  border-radius: var(--radius-sm);
  padding: var(--sp-1) var(--sp-2);
  font-size: var(--sz-label);
  cursor: pointer;
  transition: opacity var(--motion-fast) var(--ease-out);
}
.game-delete-yes { background: var(--loss); color: var(--on-loss); font-weight: 700; }
.game-delete-yes:hover { background: var(--loss-h); }
.game-delete-no  { background: var(--bg); color: var(--muted); }
.game-delete-no:hover  { background: var(--surface-hover); }
.game-deck-details {
  flex-direction: column;
  gap: var(--sp-2);
}
.game-deck-details.open { display: flex; }
/* Seats laid out as a responsive grid of raised tiles (one per player) rather
   than a flat vertical list. Nested on the --surface game row, tiles step up to
   --surface-2; the winner tile takes a gold ring + gold name. */
.seat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
  gap: var(--sp-2);
}
.seat-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--sp-2);
  padding: var(--sp-3);
  background: var(--surface-2);
  border-radius: var(--radius-md);
}
/* Winner tile takes the top-player rank-1 treatment: faint ember-warm gradient
   + gold-glow ring + resting float (mirrors .player-card.rank-1). */
.seat-tile--win {
  background: var(--rank1-gradient);
  box-shadow: 0 0 0 1.5px var(--gold-glow), var(--shadow-1);
}
/* Tiles carrying commander art join the layered-art-rim family — the exact
   .player-card--has-art / .color-stat-box--has-art recipe (inset white rim +
   soft drop) lifts them off the plain game row; the winner keeps its gold
   ring over the same drop (mirrors .player-card--has-art.rank-1). */
.seat-tile--has-art {
  box-shadow:
    inset 0 0 0 1px rgba(255,255,255,0.10),
    0 4px 24px rgba(0,0,0,0.45);
}
.seat-tile--win.seat-tile--has-art {
  box-shadow:
    0 0 0 1.5px var(--gold-glow),
    0 4px 24px rgba(0,0,0,0.45);
}
.seat-name { font-weight: 600; color: var(--text); }
.seat-tile--win .seat-name { color: var(--gold); }
.seat-cmds {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-1);
  font-size: var(--sz-sm);
  color: var(--muted);
}
/* Commander pips always drop to their own line, centred under the name;
   top margin clears the name's descenders (y/g) */
.seat-cmds .color-pips { display: flex; justify-content: center; margin: var(--sp-1) 0 0; }
.seat-result { position: absolute; top: var(--sp-2); right: var(--sp-2); font-size: var(--sz-label); }
.deck-unknown { color: var(--muted); font-style: italic; }
.game-delete-area {
  display: flex;
  gap: var(--sp-2);
  align-items: center;
  justify-content: flex-end;
}

/* ── Deleted game rows ── */
.game-row--deleted {
  border-left: 3px solid var(--loss);
  pointer-events: auto;
}

/* .color-pips + .mana-pip are universal primitives (@layer components). The
   context-specific size overrides below (.color-option, .pc-color-pips,
   .color-stat-box) are main-app rules in @layer page. */


/* ── Pod welcome / empty state ── */
#pod-empty-state[hidden] { display: none; }
#pod-empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: var(--sp-8) var(--sp-4);
  gap: var(--sp-4);
}
#pod-empty-state .section-title {
  font-size: var(--sz-h1);
  color: var(--gold);
}
#pod-empty-state .pp-section-hint {
  max-width: 30ch;
  font-size: var(--sz-body);
}

/* ── Empty / no-results state (shared across all sections) ── */
.empty-state,
.lb-error {
  color: var(--muted);
  font-size: var(--sz-body);
  padding: var(--sp-3) var(--sp-4);
  text-align: center;
  border-radius: var(--radius-md);
  list-style: none;
  background: var(--surface);
}
/* Nested on a .page-card (profile list empties) — step up a tier. */
.page-card .empty-state { background: var(--surface-2); }
.lb-error { margin-bottom: var(--sp-3); }
.empty-state .pp-section-hint { margin-bottom: 0; }
.empty-clear-btn {
  background: var(--surface-2);
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  color: var(--text);
  padding: var(--sp-1) var(--sp-3);
  font-size: var(--sz-sm);
  font-family: inherit;
  cursor: pointer;
  margin-left: var(--sp-2);
  transition: border-color var(--motion-fast) var(--ease-out), background var(--motion-fast) var(--ease-out);
}
.empty-clear-btn:hover { background: var(--surface-hover); }
.empty-clear-btn:focus-visible { outline: none; box-shadow: var(--focus-ring); }

/* ── Colour Statistics ── */
.color-stats-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--sp-4);
}
.color-stat-box {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--sp-4);
  display: flex;
  flex-direction: column;
}
/* Inside .page-card (profile), nested boxes step UP to --surface-2 so they read
   as lighter raised tiles — the unified card model (faint rim + raised surface). */
.page-card .color-stat-box { background: var(--surface-2); }
.color-stat-box--has-art {
  box-shadow:
    inset 0 0 0 1px rgba(255,255,255,0.10),
    0 4px 24px rgba(0,0,0,0.45);
}
/* Rides on the colour-stat box (pips + art) — NOT a plain-text list heading,
   so it stays --sz-label; at --sz-body the small box reads cramped/top-heavy. */
.color-stat-label {
  font-size: var(--sz-label);
  font-weight: 700;
  letter-spacing: var(--ls-caps);
  color: var(--muted);
  margin-bottom: var(--sp-2);
}
.color-stat-body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.color-stat-row {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}
.color-stat-rank {
  font-size: var(--sz-label);
  font-weight: 700;
  color: var(--muted);
  min-width: 0.9rem;
  text-align: left;
  flex-shrink: 0;
}
.color-stat-body .color-stat-row:first-child .color-stat-rank { color: var(--gold); }
.color-stat-pips { line-height: 1; flex-shrink: 0; }
.color-stat-box .mana-pip { width: 15px; height: 15px; }
.color-stat-row-name {
  flex: 1;
  font-size: var(--sz-body);
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* margin-left:auto right-aligns the count after the flexible name column
   (both main and profile render rank · pips · name · count). */
.color-stat-row-count { font-size: var(--sz-label); color: var(--muted); flex-shrink: 0; margin-left: auto; }
.color-stat-empty { color: var(--muted); font-style: italic; font-size: var(--sz-sm); }

/* Plays / Wins donut pair (above the colour-stats grid) */
.cs-donuts {
  display: flex;
  justify-content: center;
  gap: var(--sp-7);
  padding: var(--sp-2) 0 var(--sp-5);
  flex-wrap: wrap;
}
.cs-donut-fig {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2);
  margin: 0;
}
.cs-donut {
  width: 170px;
  height: 170px;
  display: block;
  filter: drop-shadow(0 4px 16px rgba(0,0,0,0.5));
}
.cs-donut path,
.cs-donut circle {
  cursor: pointer;
  transition: opacity var(--motion-fast) var(--ease-out);
}
.cs-donut:hover path,
.cs-donut:hover circle { opacity: 0.45; }
.cs-donut path:hover,
.cs-donut circle:hover { opacity: 1; }
.cs-donut-fig figcaption {
  font-size: var(--sz-label);
  font-weight: 700;
  letter-spacing: var(--ls-caps);
  color: var(--muted);
}
.cs-donut-empty {
  width: 170px;
  height: 170px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--sz-sm);
  color: var(--muted);
  font-style: italic;
  border: 1px dashed var(--border);
  border-radius: 50%;
  box-sizing: border-box;
}

/* ── Form Timeline ── */
#timeline-rows {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.tl-row {
  background: var(--surface);
  border-radius: var(--radius);
  padding: var(--sp-3) var(--sp-4);
  display: grid;
  grid-template-columns: 140px 1fr 90px;
  align-items: center;
  gap: var(--sp-4);
}
.tl-name {
  font-size: var(--sz-body);
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.tl-track {
  position: relative;
  height: 54px;
}
.tl-track-line {
  position: absolute;
  left: 0; right: 0; top: 50%;
  height: 1px;
  background: var(--divider);
}
/* One vertical bar per session (play date); each game that session is a
   stacked segment inside it. Segments flex-shrink so a huge session still
   fits the track height — they read as a striped bar instead of overflowing. */
.tl-bar {
  position: absolute;
  top: 3px;
  bottom: 3px;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 1px;
}
.tl-seg {
  width: 7px;
  flex: 0 1 10px;
  min-height: 0;
  border-radius: 2px;
  cursor: default;
}
.tl-seg--win {
  background: var(--gold);
  box-shadow: 0 0 5px var(--gold-glow);
}
.tl-seg--loss {
  background: var(--muted);
  opacity: 0.55;
}
.tl-seg:hover {
  opacity: 1;
  transform: scaleX(1.4);
}

/* Session pager — lives in the Form section head */
.tl-pager {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}
/* Icon-only arrows and the text "All" button must compute the same height:
   pin the line box and size the svg to it — a bare 12px svg box is shorter
   than the text line, which made the arrows shorter than "All". (line-height,
   not a fixed height, so the mobile .btn-xs touch-target padding bump still
   grows these.) */
.tl-pager .btn-xs { display: inline-flex; align-items: center; line-height: 1.2; }
.tl-pager .btn-xs svg { height: 1.2em; }
.tl-pager-label {
  font-size: var(--sz-sm);
  color: var(--muted);
  white-space: nowrap;
}

/* Date axis row above player rows. Same grid as .tl-row so the centre column lines up. */
.tl-axis {
  background: transparent;
  padding: 0 var(--sp-4) var(--sp-1);
}
.tl-axis-track {
  position: relative;
  height: 14px;
}
/* Every tick centres on its session column so the label lines up with the
   bars below — session columns are inset from the track edges, so the
   first/last labels no longer need edge-anchoring overrides. */
.tl-axis-tick {
  position: absolute;
  top: 0;
  font-size: var(--sz-micro);
  color: var(--muted);
  letter-spacing: var(--ls-caps);
  transform: translateX(-50%);
  white-space: nowrap;
}
.tl-track-empty {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--sz-sm);
  color: var(--muted);
  font-style: italic;
}
.tl-summary {
  font-size: var(--sz-sm);
  color: var(--muted);
  text-align: right;
  white-space: nowrap;
}
.tl-summary-wins {
  color: var(--gold);
  font-weight: 700;
}
.tl-tip-cmd {
  color: var(--muted);
}

@media (max-width: 720px) {
  .tl-row { grid-template-columns: 80px 1fr 60px; gap: var(--sp-2); padding: var(--sp-2) var(--sp-3); }
  .tl-name { font-size: var(--sz-sm); }
  .tl-summary { font-size: var(--sz-label); }
}

/* ── Game Log ── */
#games-list {
  min-height: 24rem;
}

/* ── Session header ── */
.session-header {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3) 0 var(--sp-1);
  margin-top: var(--sp-1);
}
.session-header.session-first { margin-top: 0; padding-top: 0; }
/* Eyebrow on the session rule line, above game CARDS (not text rows) — stays
   --sz-label so the divider reads as a quiet separator, not a heading. */
.session-date {
  font-size: var(--sz-label);
  font-weight: 700;
  letter-spacing: var(--ls-caps);
  color: var(--muted);
  white-space: nowrap;
}
.session-count {
  font-size: var(--sz-micro);
  color: var(--muted);
  white-space: nowrap;
}
.session-rule {
  flex: 1;
  height: 1px;
  background: var(--divider);
}
.commanders-cell {
  min-width: 0;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--sp-2);
}
.commander-name { color: var(--text); cursor: default; }
.commander-name:hover { color: var(--text); }
.cmd-sep { color: var(--muted); font-size: var(--sz-sm); }
.result-pills { display: flex; flex-wrap: wrap; gap: var(--sp-2); align-items: center; }
.player-result {
  font-size: var(--sz-sm);
  white-space: nowrap;
}
.player-result.win {
  font-weight: 700;
  color: var(--gold);
}
.player-result.loss {
  font-weight: 400;
  color: var(--muted);
}

/* ── Popup ── */
#gf-range-popup,
#pf-range-popup,
#new-game-modal,
#pp-invite-modal,
#pp-add-player-modal,
#pp-edit-player-modal,
#player-confirm-modal {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Background scroll lock when any modal or the players panel is open */
html:has(#gf-range-popup:not([hidden])),
html:has(#pf-range-popup:not([hidden])),
html:has(#new-game-modal:not([hidden])),
html:has(#pp-invite-modal:not([hidden])),
html:has(#pp-add-player-modal:not([hidden])),
html:has(#pp-edit-player-modal:not([hidden])),
html:has(#player-confirm-modal:not([hidden])),
html:has(#players-panel:not([hidden])) {
  overflow-y: hidden;
}


/* ── Small inline buttons (used in players panel) ── */
/* Padding follows the 1x/2x rule: 1x vertical, 2x horizontal. */

/* ── Section loader ── */
.section-loader {
  text-align: center;
  color: var(--muted);
  font-size: var(--sz-sm);
  padding: var(--sp-3) var(--sp-4);
  animation: loader-pulse 1.4s ease-in-out infinite;
}
@keyframes loader-pulse {
  0%, 100% { opacity: 0.4; }
  50%       { opacity: 1;   }
}

/* ── New-game FAB (speed-dial main button) ── */
#new-game-fab {
  position: fixed;
  right: var(--edge-gap);
  bottom: var(--edge-gap);
  z-index: 100;
  will-change: transform;
  height: 3.4rem;
  min-width: 3.4rem;     /* collapsed = a perfect circle (width grows on hover) */
  border-radius: 1.7rem;
  cursor: pointer;
  display: flex;
  flex-direction: row-reverse;
  align-items: center;
  justify-content: flex-start;
  gap: 0;
  overflow: hidden;
  background: var(--accent);
  color: var(--on-accent);
  border: none;
  /* Constant padding keeps the icon a fixed distance from the (fixed) right edge,
     so it stays put while the label grows leftward — no re-centering jump.
     0.9rem (13.5px) each side + the 24px icon = 51px = the 3.4rem height, so the
     collapsed FAB is an exact circle (0.95rem overflowed it to 52.5px wide). */
  padding: 0 0.9rem;
  transition: gap var(--motion-base) var(--ease-out), background var(--motion-fast) var(--ease-out);
}
#new-game-fab .fab-icon { display: block; flex-shrink: 0; transition: transform var(--motion-base) var(--ease-out); }

.fab-label {
  font-size: var(--sz-sm);
  font-weight: 600;
  white-space: nowrap;
  max-width: 0;
  overflow: hidden;
  opacity: 0;
  transition: max-width var(--motion-base) var(--ease-out), opacity var(--motion-fast) var(--ease-out);
}
#new-game-fab:hover .fab-label {
  max-width: 6rem;
  opacity: 1;
}
#new-game-fab:hover { gap: var(--sp-2); background: var(--accent-h); }

#data-panel {
  position: fixed;
  /* The overflow menu drops just below the global-nav bar (mobile re-anchors
     it to the bottom — see the gnav media query). */
  top: calc(var(--gnav-h) + var(--sp-2));
  right: var(--edge-gap);
  min-width: 220px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  z-index: 200;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  box-shadow: var(--shadow-2);
}
a.data-panel-btn { text-decoration: none; }
#data-panel > form { display: flex; flex-direction: column; margin: 0; }
.data-panel-btn {
  background: none;
  border: none;
  color: var(--text);
  padding: var(--sp-3) var(--sp-4);
  text-align: left;
  cursor: pointer;
  font-size: var(--sz-body);
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  white-space: nowrap;
  transition: background var(--motion-fast) var(--ease-out), color var(--motion-fast) var(--ease-out);
}
.data-panel-btn:hover { background: var(--surface-hover); }
.data-panel-divider {
  height: 1px;
  background: var(--divider);
  margin: var(--sp-1) 0;
}
.nav-invite-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--accent);
  color: var(--on-accent);
  font-size: var(--sz-micro);
  font-weight: 700;
  line-height: 1;
  min-width: 1.1rem;
  height: 1.1rem;
  border-radius: 9999px;
  padding: 0 var(--sp-1);
  margin-left: var(--sp-2);
}

/* ── Speed-dial (Phase 2) ── */
.speed-dial[data-open="true"] #speed-dial-icon { transform: rotate(45deg); }
.speed-dial-actions {
  position: fixed;
  right: var(--edge-gap);
  bottom: calc(var(--edge-gap) + 3.4rem + 0.8rem);
  z-index: 100;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--sp-2);
}
.speed-dial-mini {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  height: 2.6rem;
  padding: 0 var(--sp-4);
  border-radius: 1.3rem;
  background: var(--surface);
  border: none;
  color: var(--text);
  font: inherit;
  font-size: var(--sz-sm);
  font-weight: 600;
  cursor: pointer;
  box-shadow: var(--shadow-1);
  animation: speed-dial-in var(--motion-fast) var(--ease-out) both;
  transition: background var(--motion-fast) var(--ease-out), color var(--motion-fast) var(--ease-out);
}
.speed-dial-mini-label { white-space: nowrap; }
.speed-dial-mini-icon { flex-shrink: 0; }
.speed-dial-mini:hover { background: var(--surface-hover); }
.speed-dial-mini--accent {
  background: var(--accent);
  color: var(--on-accent);
}
.speed-dial-mini--accent:hover { background: var(--accent-h); }
.speed-dial-mini:focus-visible { outline: none; box-shadow: var(--focus-ring), var(--shadow-1); }
@keyframes speed-dial-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}
/* Scrim while open (mobile primarily, lets a tap-outside close it) */
.speed-dial-scrim {
  position: fixed;
  inset: 0;
  background: transparent;
  z-index: 99;
}

/* ── Players Side Panel ── */
#players-panel {
  position: fixed;
  inset: 0;
  z-index: 300;
}
.players-panel-box {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(480px, calc(100vw - var(--edge-gap)));
  background: var(--surface);
  box-shadow: var(--shadow-2);
  display: flex;
  flex-direction: column;
  padding: var(--sp-6);
  gap: var(--sp-4);
  overflow-y: auto;
}
.pp-player {
  display: flex;
  flex-direction: column;
  padding: var(--sp-2) var(--sp-3);
  /* Nested tile on the panel's --surface → the raised --surface-2 fill
     (lighter = raised) defines the tile; borderless like .stat-card /
     .list-card. Hover/expanded lifts to --surface-hover. */
  background: var(--surface-2);
  border-radius: var(--radius);
  transition: background var(--motion-fast) var(--ease-out),
              box-shadow var(--motion-base) var(--ease-out);
}
.pp-player-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  /* Match the icon-button height so rows without action buttons (e.g. the
     owner's own row) are the same height as rows that have them. */
  min-height: 1.8rem;
}
.pp-player--expandable { cursor: pointer; }
.pp-player--expandable:hover,
.pp-player--expandable.pp-player--expanded { background: var(--surface-hover); }
/* Lift only as a hover affordance — the expanded (active) state stays grounded. */
.pp-player--expandable:hover:not(.pp-player--expanded) { box-shadow: var(--shadow-lift); }
.pp-player-name { font-weight: 700; font-size: var(--sz-body); display: flex; align-items: center; gap: var(--sp-2); flex: 1; min-width: 0; }
.pp-player-right { display: flex; align-items: center; gap: var(--sp-2); flex-shrink: 0; }
.pp-player-stats { font-size: var(--sz-label); color: var(--muted); }
.pp-verified-icon { color: var(--win); font-size: var(--sz-sm); line-height: 1; cursor: default; }
.pp-player-expand {
  display: none;
  flex-direction: row;
  align-items: center;
  gap: var(--sp-2);
  flex-wrap: wrap;
  padding-top: var(--sp-2);
  margin-top: var(--sp-1);
  border-top: 1px solid var(--divider);
}
.pp-player--expanded .pp-player-expand { display: flex; }
.pp-player--removed { opacity: 0.55; }
/* Guest checkbox in add-player form */
.players-add-guest {
  display: flex; align-items: center; gap: var(--sp-2);
  font-size: var(--sz-body); cursor: pointer;
}
.players-guest-hint { color: var(--muted); font-size: var(--sz-sm); }


/* Player panel search */
.pp-search-wrap { position: relative; }
.pp-search {
  width: 100%;
  background: var(--bg);
  border: 1px solid transparent;
  /* Inputs use --radius-lg (matches .form-input / .gf-cmd-search /
     .pp-manage-input); --radius (16px) is the card radius and read as too round. */
  border-radius: var(--radius-lg);
  color: var(--text);
  font-size: var(--sz-sm);
  font-family: inherit;
  /* Symmetric padding already centres the line box; this 1px top/bottom skew is
     an optical nudge to lift Inter's glyphs, which sit a hair below the
     geometric centre at this size, so the text reads vertically centred. */
  padding: calc(var(--sp-2) - 1px) var(--sp-3) calc(var(--sp-2) + 1px);
  box-sizing: border-box;
  transition: box-shadow var(--motion-fast) var(--ease-out);
}
.pp-search:focus { outline: none; box-shadow: var(--focus-ring); }
.players-add-field {
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
  width: 100%;
}
.players-add-field input {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--sp-2) var(--sp-3);
  color: var(--text);
  font-size: var(--sz-sm);
  width: 100%;
  transition: box-shadow var(--motion-fast) var(--ease-out);
}
.players-add-field input:focus { outline: none; box-shadow: var(--focus-ring); }
.new-player-error { color: var(--loss-text); font-size: var(--sz-sm); }

/* Suppress browser autofill/contacts icon that makes inputs appear unequal */
.players-panel-box input::-webkit-contacts-auto-fill-button,
.players-panel-box input::-webkit-credentials-auto-fill-button { visibility: hidden; }


.pp-section-hint {
  font-size: var(--sz-sm);
  color: var(--muted);
  margin: 0 0 var(--sp-3);
  line-height: 1.45;
}
.pp-invite-form-row {
  display: flex;
  width: 100%;
}
.pp-invite-form-row > * + * {
  margin-left: var(--sp-2);
}
/* Modal action row variant: anchor children to opposite ends */
.pp-invite-form-row--split { justify-content: space-between; }
.pp-invite-form-row--split > * + * { margin-left: 0; }

.pp-manage-input {
  flex: 1;
  width: 100%;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--sp-2) var(--sp-3);
  color: var(--text);
  font-family: inherit;
  font-size: var(--sz-sm);
  outline: none;
  transition: border-color var(--motion-fast) var(--ease-out), box-shadow var(--motion-fast) var(--ease-out);
  min-width: 0;
}
.pp-manage-input:focus { box-shadow: var(--focus-ring); }
.pp-manage-input::placeholder { color: var(--muted); }

/* ── Card art skeleton (shimmer while Scryfall background image loads) ── */
@keyframes skeleton-shimmer {
  0%   { transform: rotate(20deg) translateX(-120%); }
  100% { transform: rotate(20deg) translateX(120%); }
}
.player-card--art-loading {
  background-color: rgba(255, 255, 255, 0.12) !important;
  min-height: 220px;
  overflow: hidden;
}
.player-card--art-loading::before {
  content: '';
  position: absolute;
  inset: -50%;
  background-image: linear-gradient(
    90deg,
    rgba(255, 255, 255, 0) 0%,
    rgba(255, 255, 255, 0.09) 50%,
    rgba(255, 255, 255, 0) 100%
  );
  animation: skeleton-shimmer 1.8s infinite linear;
  pointer-events: none;
  will-change: transform;
}
@media (prefers-reduced-motion: reduce) {
  .player-card--art-loading::before { animation: none; }
  .section-skel-row::before         { animation: none; }
}

/* ── Generic section skeleton row (timeline/commanders/games/h2h placeholders) ── */
.section-skel-row {
  position: relative;
  overflow: hidden;
  height: 2.4rem;
  margin-bottom: var(--sp-2);
  border-radius: var(--radius-md);
  background: rgba(255, 255, 255, 0.055);
}
.section-skel-row::before {
  content: '';
  position: absolute;
  inset: -50%;
  background-image: linear-gradient(
    90deg,
    rgba(255, 255, 255, 0) 0%,
    rgba(255, 255, 255, 0.08) 50%,
    rgba(255, 255, 255, 0) 100%
  );
  animation: skeleton-shimmer 1.8s infinite linear;
  pointer-events: none;
  will-change: transform;
}

/* ── Modals ── */
#pp-invite-modal,
#pp-add-player-modal,
#pp-edit-player-modal { z-index: 400; }
#new-game-modal .popup-overlay,
#pp-invite-modal .popup-overlay,
#pp-add-player-modal .popup-overlay,
#pp-edit-player-modal .popup-overlay { position: fixed; }

/* Add/Edit-player modals: stack children with consistent spacing */
#pp-add-player-modal .popup-box,
#pp-edit-player-modal .popup-box {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}
#pp-add-player-modal .popup-header,
#pp-edit-player-modal .popup-header { margin-bottom: 0; }
#pp-edit-player-modal #pp-edit-player-name-fields[hidden] { display: none; }
#pp-edit-player-modal #pp-edit-player-name-fields {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
#pp-add-player-modal .pp-section-hint { margin: 0; }
#pp-add-player-modal .new-player-error,
#pp-edit-player-modal .new-player-error { margin: 0; }

/* Fields */
.ng-field { margin-bottom: var(--sp-4); }

/* The two new-game autocompletes (player picker + commander search) are both
   portaled to <body> and positioned via JS, so they share one panel shape.
   Visual matches .custom-multiselect-panel. */
.ng-player-dropdown,
.ng-cmd-dropdown {
  position: fixed;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  list-style: none;
  padding: var(--sp-1) 0;
  margin: 0;
  z-index: 1000;
  max-height: 220px;
  overflow-y: auto;
}
/* .ng-player-option uses .picker-opt for all visual style */
/* Scrollable modal */
.popup-box--wide {
  width: fit-content;
  min-width: min(580px, calc(100vw - 2 * var(--edge-gap)));
  max-width: min(1150px, calc(100vw - 2 * var(--edge-gap)));
  max-height: 90vh;
  max-height: 90dvh;
  overflow-y: auto;
}

/* #ng-rows horizontal flex container */
#ng-rows {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  /* remove badge sits inside the cards now — no hanging-button clearance */
  padding-top: var(--sp-2);
  margin-top: var(--sp-2);
  margin-bottom: var(--sp-4);
}

/* Dotted "add player" card */
.ng-add-card {
  width: 210px;
  flex-shrink: 0;
  min-height: 290px;
  border: 2px dashed var(--border);
  border-radius: var(--radius);
  background: none;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-3);
  transition: border-color var(--motion-fast) var(--ease-out);
}
.ng-add-card:hover { border-color: var(--text); }
.ng-add-btn {
  width: 3rem;
  height: 3rem;
  border-radius: 50%;
  background: var(--accent);
  color: var(--on-accent);
  border: none;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  pointer-events: none; /* card handles the click */
  transition: background var(--motion-fast) var(--ease-out), transform var(--motion-fast) var(--ease-out);
}
.ng-add-card:hover .ng-add-btn { background: var(--accent-h); transform: scale(1.1); }

/* Player cards (new game) */
.ng-pcard {
  position: relative;
  width: 210px;
  flex-shrink: 0;
  min-height: 290px;
  border-radius: var(--radius);
  background-color: var(--bg);
  background-size: cover;
  background-position: center 20%;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  overflow: visible;
}
/* Player row: the field and the remove-seat disc share one flex row, so the ×
   stays centred against the input whatever height the input takes (it grows on
   mobile focus). Pair the disc with .icon-btn.icon-btn--delete for hover. */
.ng-pcard-player-row {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}
.ng-pcard-player-row .ng-cmd-field { min-width: 0; }
.ng-pcard-remove {
  flex-shrink: 0;
  width: 1.9rem;
  height: 1.9rem;
  border-radius: 50%;
  background: var(--surface-2);
}
.ng-pcard-content { padding: var(--sp-2) var(--sp-3) var(--sp-3); }
.ng-pcard-body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
/* Player selector — sticky at top of solid card */
.ng-pcard-player {
  padding: var(--sp-2) var(--sp-3) var(--sp-1);
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}

/* Trophy mid zone — grows between name bar and content, centers trophy */
.ng-pcard-mid {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding-top: 1.8rem; /* clear the absolute name bar */
}

/* Trophy button */
.ng-trophy-btn {
  width: 3.2rem;
  height: 3.2rem;
  border-radius: 50%;
  border: none;
  background: rgba(0,0,0,0.35);
  color: rgba(255,255,255,0.55);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background var(--motion-fast) var(--ease-out), color var(--motion-fast) var(--ease-out), transform var(--motion-fast) var(--ease-out);
}
.ng-trophy-btn svg { width: 1.6rem; height: 1.6rem; }
.ng-trophy-btn:hover { background: rgba(0,0,0,0.55); color: rgba(255,255,255,0.85); transform: scale(1.08); }
.ng-trophy-btn.active {
  background: rgba(240,192,64,0.25);
  color: var(--gold);
  box-shadow: 0 0 0 2px rgba(240,192,64,0.6);
}
.ng-pcard--has-art .ng-trophy-btn {
  background: rgba(0,0,0,0.62);
  color: rgba(255,255,255,0.8);
}
.ng-pcard--has-art .ng-trophy-btn.active {
  background: var(--gold-glow);
}

/* Winner state */
.ng-pcard--winner {
  box-shadow: inset 0 0 0 2px rgba(240,192,64,0.8), 0 0 12px rgba(240,192,64,0.2);
}
.ng-pcard-winner-tag { display: none; }
.ng-pcard--winner .ng-pcard-winner-tag { display: inline-flex; }

/* Commander input row */
.ng-cmd-row {
  display: flex;
  gap: var(--sp-2);
  align-items: center;
}
.ng-cmd-field { position: relative; flex: 1; }
.ng-cmd-input {
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--sp-1) var(--sp-6) var(--sp-1) var(--sp-2);
  color: var(--text);
  font-size: var(--sz-sm);
  width: 100%;
  transition: box-shadow var(--motion-fast) var(--ease-out);
}
.ng-cmd-input:focus { outline: none; box-shadow: var(--focus-ring); }
.ng-cmd-input--invalid { border-color: var(--loss) !important; }
.ng-cmd-clear {
  position: absolute;
  right: var(--sp-2);
  top: 50%;
  transform: translateY(-50%);
  background: none;
  border: none;
  color: var(--muted);
  font-size: 1.1rem;
  cursor: pointer;
  padding: 0 var(--sp-1);
  line-height: 1;
  display: none;
}
.ng-cmd-row:hover .ng-cmd-input:not(:placeholder-shown) + .ng-cmd-clear { display: block; }
.ng-cmd-row--partner:hover .ng-cmd-clear { display: block; }
.ng-pcard-player:hover .ng-cmd-input:not(:placeholder-shown) + .ng-cmd-clear { display: block; }
.ng-pcard[data-unknown="true"] .ng-cmd-clear { display: none !important; }
.ng-cmd-clear:hover { color: var(--text); }

/* Autocomplete dropdown panel: shape shared with .ng-player-dropdown above. */
.ng-cmd-option {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-2);
  cursor: pointer;
  font-size: var(--sz-body);
}
.ng-cmd-option:hover,
.ng-cmd-option:focus { background: var(--surface-hover); outline: none; }
.ng-cmd-option--info {
  color: var(--muted);
  cursor: default;
  font-size: var(--sz-sm);
}
.ng-cmd-option--info:hover,
.ng-cmd-option--info:focus { background: none; }
.ng-opt-art {
  width: 36px;
  height: 27px;
  border-radius: var(--radius-xs);
  background: var(--bg);
  background-size: cover;
  background-position: center top;
  flex-shrink: 0;
}

/* Partner button */
.ng-partner-btn {
  background: none;
  border: 1px dashed var(--border);
  border-radius: var(--radius-lg);
  color: var(--muted);
  font-size: var(--sz-sm);
  padding: var(--sp-1) var(--sp-3);
  cursor: pointer;
  width: 100%;
  text-align: left;
  transition: border-color var(--motion-fast) var(--ease-out);
}
.ng-partner-btn:hover { border-color: var(--text); color: var(--text); }

/* Unknown commander checkbox */
.ng-unknown-label {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--sz-sm);
  color: var(--muted);
  cursor: pointer;
  user-select: none;
}
.ng-unknown-label:hover { color: var(--text); }
.ng-unknown-cb { accent-color: var(--text); cursor: pointer; }


/* Error */
.ng-error { color: var(--loss-text); font-size: var(--sz-body); margin-top: var(--sp-3); margin-bottom: var(--sp-3); }


/* ── Rich tooltips (structured HTML — see script.js for placement logic).
   The generic single-line #tooltip is a universal primitive (@layer components). */
#att-tooltip,
#streak-tooltip,
#skill-tooltip,
#bayesian-tooltip {
  position: fixed;
  pointer-events: none;
  z-index: 1000;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: var(--sp-3) var(--sp-3);
  font-size: var(--sz-sm);
}
#streak-tooltip    { min-width: 140px; }
#skill-tooltip     { min-width: 180px; }
#bayesian-tooltip  { min-width: 220px; max-width: 260px; }
.skill-tip-row {
  display: flex;
  justify-content: space-between;
  gap: var(--sp-6);
  line-height: 1.8;
}
.skill-tip-label { color: var(--muted); }
.skill-tip-val   { font-weight: 600; color: var(--text); }
.skill-tip--pos  { color: var(--win); }
.skill-tip--neg  { color: var(--loss-text); }
.skill-tip-divider {
  border-top: 1px solid var(--divider);
  margin: var(--sp-1) 0;
}
.skill-tip-note {
  color: var(--text);
  font-size: var(--sz-sm);
  font-style: italic;
  border-top: 1px solid var(--divider);
  margin-top: var(--sp-2);
  padding-top: var(--sp-2);
}


@media (max-width: 900px) {
  /* H2H: stack card above opponents list */
  .h2h-layout { flex-direction: column; align-items: stretch; }
  .h2h-nav { position: static; justify-content: center; }
  .h2h-nav .player-card { flex: 1; width: auto; max-width: 360px; }
  .h2h-opponents { width: 100%; }

  /* Player cards: 3-up on tablet */
  #player-cards { grid-template-columns: repeat(3, minmax(0, 1fr)); min-width: 0; }

  /* Colour stats: 2 columns */
  .color-stats-grid { grid-template-columns: repeat(2, 1fr); }
}

/* Section nav + filter sheet flip together with the app-shell (the gnav
   720px breakpoint) — with the bottom tab bar active, the inline filter
   cluster wrapping under the nav read as a half-desktop state on 601–720px
   screens. One threshold, one mode. */
@media (max-width: 720px) {
  /* Section nav: single horizontally scrolling row with a right-edge fade hint */
  #section-nav-wrap {
    flex-wrap: nowrap;
    row-gap: 0;
    position: sticky;
    top: 0;
    align-items: center;
  }
  #section-nav {
    flex: 1 1 auto;
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    justify-content: flex-start;
    row-gap: 0;
    padding-top: var(--sp-1);
    padding-bottom: var(--sp-1);
    scroll-padding-right: 2.5rem;
    /* Fade the links as they scroll off the right edge — applied to the scroll
       area itself (via mask), so the hint never bleeds onto the Filters button
       sitting to its right. */
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 2.5rem), transparent 100%);
    mask-image: linear-gradient(to right, #000 calc(100% - 2.5rem), transparent 100%);
  }
  /* Section-nav pills: the primary in-page navigation on touch — the desktop
     --sp-1 vertical padding lands the hit area near 30px, well under the
     ~44px HIG/Material floor. --sp-3 brings it to ~38px inside the same row. */
  .snav-link { padding: var(--sp-3) var(--sp-3); }
  /* Show the bottom-sheet trigger; hide the inline cluster until opened */
  .snav-filter-sheet-btn { display: inline-flex; }
  .snav-filters {
    display: none;
  }
  .snav-filters.is-open {
    display: flex;
    position: fixed;
    inset: auto 0 0 0;
    z-index: 401;            /* above the global-nav bottom bar (300) */
    flex-direction: column;
    align-items: stretch;
    gap: var(--sp-2);
    padding: var(--sp-5) var(--sp-4) calc(var(--sp-4) + env(safe-area-inset-bottom, 0px));
    background: var(--surface);
    border-top: 1px solid var(--border);
    border-top-left-radius: var(--radius);
    border-top-right-radius: var(--radius);
    box-shadow: 0 -8px 24px rgba(0,0,0,0.5);
    animation: filter-sheet-in var(--motion-base) var(--ease-out);
  }
  /* Field labels show inside the open sheet only */
  .snav-filters.is-open .snav-sheet-label {
    display: block;
    font-size: var(--sz-label);
    font-weight: 700;
    letter-spacing: var(--ls-caps);
    color: var(--muted);
    margin-top: var(--sp-2);
    margin-bottom: var(--sp-1);
  }
  .snav-filters.is-open .snav-sheet-label:first-child { margin-top: 0; }
  /* Uniform control sizing — match the rest of the form-style inputs */
  .snav-filters.is-open .custom-multiselect,
  .snav-filters.is-open .custom-color-filter { width: 100%; }
  .snav-filters.is-open .custom-multiselect-btn,
  .snav-filters.is-open .btn-range-open,
  .snav-filters.is-open .gf-cmd-search {
    width: 100%;
    max-width: none;
    min-width: 0;
    height: 2.6rem;
    padding: 0 var(--sp-3);
    display: inline-flex;
    align-items: center;
    box-sizing: border-box;
  }
  .snav-filters.is-open .gf-cmd-search { font-size: var(--sz-body); }
  /* The sheet is --surface; its controls default to --surface too, so they
     vanish into the background. Inset the field-like triggers to --bg with a
     hairline border (form-field treatment). */
  .snav-filters.is-open .gf-cmd-search,
  .snav-filters.is-open .custom-multiselect-btn,
  .snav-filters.is-open .btn-range-open {
    background: var(--bg);
    border-color: var(--border);
  }
  /* The footer Clear button steps up a tier instead (button treatment). */
  .snav-filters.is-open .btn-sm--ghost { background: var(--surface-2); }
  /* Footer with Clear / Done */
  .snav-filters.is-open .snav-sheet-footer {
    display: flex;
    justify-content: space-between;
    gap: var(--sp-2);
    margin-top: var(--sp-4);
    padding-top: var(--sp-3);
    border-top: 1px solid var(--divider);
  }
  .snav-filters-sheet-backdrop {
    position: fixed;
    inset: 0;
    background: var(--scrim);
    z-index: 400;            /* above the global-nav bottom bar (300), below the sheet */
    animation: filter-sheet-fade var(--motion-base) var(--ease-out);
  }
  @keyframes filter-sheet-in {
    from { transform: translateY(100%); }
    to   { transform: translateY(0); }
  }
  @keyframes filter-sheet-fade {
    from { opacity: 0; } to { opacity: 1; }
  }
}

@media (max-width: 600px) {
  :root { --edge-gap: var(--sp-4); }

  .section-head { flex-direction: column; }
  /* Title was nowrap + baseline-nudged for desktop side-by-side with controls.
     In column layout the nudge creates a stray gap and nowrap can clip long titles. */
  .section-head h2 { white-space: normal; padding-top: 0; }
  .controls { flex-wrap: wrap; }
  header { min-height: 110px; padding: var(--sp-5) var(--edge-gap) var(--sp-4); }
  header h1 { font-size: 1.8rem; }

  /* Player cards: 2-up grid; allow pip row to wrap so it doesn't force card width */
  #player-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp-3); min-width: 0; }
  .form-pips { flex-wrap: wrap; }

  /* Game row: prevent commander cell overflow */
  .commanders-cell { flex-wrap: wrap; word-break: break-word; }
  .game-num { font-size: var(--sz-micro); }

  /* Milestone badges: the desktop right-hand column ("🏆 Name · 100th win")
     can be wider than what's left for the commander + result cells at phone
     width. Drop the badges to their own row under the pills instead. */
  .game-row {
    grid-template-columns: auto 1fr auto;
    grid-template-areas:
      "num cmds       chev"
      "num pills      chev"
      "num milestones chev";
  }
  .game-row > .game-milestones { flex-direction: row; flex-wrap: wrap; align-items: center; }

  /* Seat tiles: two-up on phones. The desktop 11rem floor only fits one
     column inside a game row here, so a 4-player game expanded into one
     very tall single-file column of art tiles. */
  .seat-grid { grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr)); }

  /* Commander pilot-share rows (expanded Top-Commanders detail). On desktop the
     whole panel is ONE shared grid (rows are display:contents) so every bar sits
     in the same 1fr track. On a phone the trailing "N of X's Y wins · Z%" value
     starves that 1fr bar down to a sliver, so each pilot row becomes its own
     two-row grid — avatar + name + value on top, the share bar spanning full
     width below (mirrors the column-stacked H2H matchup detail). */
  .cmd-details.open { grid-template-columns: 1fr; }
  .cprow {
    display: grid;
    grid-template-columns: auto auto 1fr;
    grid-template-areas:
      "avatar name pct"
      "bar    bar  bar";
    align-items: center;
    column-gap: var(--sp-2);
    row-gap: var(--sp-2);
  }
  .cprow > .avatar { grid-area: avatar; }
  .cprow-name      { grid-area: name; min-width: 0; }
  .cprow .prop-bar { grid-area: bar; }
  .cprow-pct       { grid-area: pct; }

  /* H2H opponent rows: the desktop two-column layout starves the name column
     (rank + avatar + fixed stat slots leave ~100px on a 375px screen, and the
     nemesis pill wraps it further). Restack like .cprow — name across the full
     width, the win/loss pair side by side below it, the date line last. The
     fixed stat-slot min-widths exist only for cross-row column alignment,
     which the stack removes, so they collapse to content width here. */
  .h2h-opp-row--with-art {
    grid-template-columns: auto auto auto 1fr auto;
    grid-template-areas:
      "num art name name chev"
      "num art top  bot  chev"
      "num art last last chev";
  }
  .h2h-opp-row .h2h-stat-win,
  .h2h-opp-row .h2h-stat-loss,
  .h2h-opp-row .h2h-stat-sub { min-width: 0; }
  /* Date-only on phones — "Last played:" is padding at this width */
  .last-played-prefix { display: none; }

  /* Multiselect buttons: narrower */
  .custom-multiselect-btn { width: 130px; }
  /* Panel defaults to min-width 220px; that overhangs the narrower mobile button. */
  .custom-multiselect-panel { min-width: 0; max-width: calc(100vw - 2 * var(--edge-gap)); }

  /* Colour stats: single column on small phones */
  .color-stats-grid { grid-template-columns: 1fr; }

  /* FAB: compact icon-only circle on mobile */
  #new-game-fab {
    width: 3.4rem; justify-content: center;
    box-shadow: var(--shadow-1);
  }

  /* Disable hover/expand entirely on mobile — icon-only, no animation */
  .fab-label { max-width: 0 !important; opacity: 0 !important; }
  #new-game-fab:hover { gap: 0; }
  /* First-visit hint: keep the label visible on the speed-dial main FAB
     until the user opens it (`data-open="true"` on the wrap clears it) */
  body[data-fab-onboard="1"] #new-game-fab .fab-label {
    max-width: 6rem !important;
    opacity: 1 !important;
  }
  body[data-fab-onboard="1"] #new-game-fab { gap: var(--sp-2); width: auto; }

  /* Bottom padding clears the FAB stack; section+section spacing scales down */
  main { padding-bottom: 6rem; }
  /* Sticky nav is a single row on mobile (Phase 4) — much smaller anchor offset */
  main > section { scroll-margin-top: 4rem; }
  main > section + section {
    margin-top: var(--sp-6);
    padding-top: var(--sp-6);
    scroll-margin-top: calc(4rem - var(--sp-6));
  }
  #leaderboard {
    margin-top: 0;
    padding-top: var(--sp-5);
    border-top: none;
  }

  /* New game modal: 2-up player cards on mobile, smaller min-height */
  .ng-pcard {
    width: calc(50% - (var(--sp-3) / 2));
    min-height: 200px;
  }
  .ng-add-card {
    width: calc(50% - (var(--sp-3) / 2));
    min-height: 200px;
  }
  .ng-pcard-mid { padding-top: var(--sp-6); }
  .ng-trophy-btn { width: 2.6rem; height: 2.6rem; }
  .ng-trophy-btn svg { width: 1.3rem; height: 1.3rem; }
  /* Badge lives inside the cards — normal gaps, no clearance hacks */
  #ng-rows { row-gap: var(--sp-4); column-gap: var(--sp-3); padding-top: var(--sp-1); }
  #new-game-modal .popup-box { padding: var(--sp-4); }

  /* Leaderboard card: trim the win-rate hero so 2-up cards don't feel oversized */
  .player-card .win-pct { font-size: 1.8rem; }
  .pc-stats { gap: var(--sp-5); }

  /* ── iOS Safari input-zoom guard ──
     Mobile Safari auto-zooms when a FOCUSED field's effective font-size is
     under 16px — and it checks the size at focus time, so promoting only the
     :focus state prevents the zoom while resting fields keep their design
     size (a blanket 16px made every search box read oversized). */
  input[type="text"]:focus,
  input[type="email"]:focus,
  input[type="password"]:focus,
  input[type="search"]:focus,
  input[type="tel"]:focus,
  input[type="url"]:focus,
  input[type="number"]:focus,
  input[type="date"]:focus,
  textarea:focus {
    font-size: 16px;
  }

  /* ── Tap targets ──
     Apple HIG / Material both target ≥44px. Several controls fall just
     short on the desktop sheet; nudge them up on phones only. */
  .speed-dial-mini { height: 2.9rem; padding: 0 var(--sp-4); }
  .snav-filter-sheet-btn { padding: var(--sp-2) var(--sp-3); }
  .popup-close-btn { width: 2.75rem; height: 2.75rem; }
  /* Sort/range/multiselect triggers a touch beefier so they're easy to hit */
  .sort-btn,
  .btn-range-open,
  .custom-multiselect-btn { min-height: 2.6rem; }
  /* Touch-action: prevent the iOS double-tap zoom delay on primary CTAs */
  .btn-apply,
  .btn-sm,
  .btn-xs,
  .icon-btn,
  .speed-dial-mini,
  #new-game-fab { touch-action: manipulation; }
}


/* ─── Admin page (body.page-admin) ─────────────────────────────────────────
   Moved from inline <style> in admin.html during Phase 2.                  */
body.page-admin .admin-controls {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 1.25rem;
  flex-wrap: wrap;
}
body.page-admin .admin-search       { width: 260px; }
body.page-admin .admin-stat         { font-size: var(--sz-label); color: var(--muted); margin-left: auto; }
body.page-admin .admin-filter-label { font-size: var(--sz-label); color: var(--muted); }

body.page-admin .pods-cell { display: flex; flex-wrap: wrap; gap: 0.3rem; }
body.page-admin .pod-pill {
  display: inline-block;
  font-size: var(--sz-micro);
  font-weight: 600;
  padding: 0.15rem 0.55rem;
  border-radius: var(--radius-sm);
  background: var(--bg);
  border: 1px solid transparent;
  color: var(--muted);
  cursor: pointer;
  transition: border-color var(--motion-fast) var(--ease-out), color var(--motion-fast) var(--ease-out), background var(--motion-fast) var(--ease-out);
  white-space: nowrap;
}
body.page-admin .pod-pill:hover  { color: var(--text); background: var(--surface-hover); }
body.page-admin .pod-pill.active { color: var(--text); background: var(--muted-soft); }
body.page-admin .pods-empty { color: var(--muted); font-size: var(--sz-label); }

/* Suppress generic row hover for pod rows — hover handled by JS across both rows */
body.page-admin .data-table tbody tr.pod-main-row:not(.pod-group-hovered):hover td,
body.page-admin .data-table tbody tr.pod-detail-row:not(.pod-group-hovered):hover td { background: transparent; }

body.page-admin .inline-email-form { display: flex; margin-top: 0.45rem; gap: 0.35rem; flex-wrap: wrap; align-items: center; }
body.page-admin .inline-email-form[hidden] { display: none; }
body.page-admin .inline-email-form input {
  flex: 1; min-width: 170px;
  background: var(--bg); border: 1px solid var(--border); border-radius: var(--radius-md);
  padding: 0.28rem 0.55rem; color: var(--text); font-family: inherit; font-size: var(--sz-label);
  outline: none; transition: box-shadow var(--motion-fast) var(--ease-out);
}
body.page-admin .inline-email-form input:focus { box-shadow: var(--focus-ring); }

body.page-admin .row-msg { font-size: var(--sz-label); margin-top: 0.3rem; display: none; }
body.page-admin .row-msg.ok  { color: var(--win);  display: block; }
body.page-admin .row-msg.err { color: var(--loss-text); display: block; }

body.page-admin tr.pod-row-expanded td { border-bottom: none; }
body.page-admin .pod-main-row { cursor: pointer; }
body.page-admin .pod-group-hovered td { background: rgba(255,255,255,0.04); }
body.page-admin .pod-main-row.deleted { opacity: 0.55; }
body.page-admin .td-date  { color: var(--muted); white-space: nowrap; }
body.page-admin .pod-empty { color: var(--muted); }
body.page-admin .pod-members-empty { font-size: var(--sz-sm); color: var(--muted); }
body.page-admin .confirm-danger { color: var(--loss-text); }
body.page-admin .pod-detail-row { display: table-row; }
body.page-admin .pod-detail-row[hidden] { display: none; }
body.page-admin .pod-detail-row td { padding: 0.75rem 1rem; border-top: none; }
body.page-admin .pod-member-row { display: flex; align-items: center; gap: 0.6rem; font-size: var(--sz-sm); margin-bottom: 0.3rem; }
body.page-admin .pod-member-name  { color: var(--text); font-weight: 500; }
body.page-admin .pod-member-email { color: var(--muted); font-size: var(--sz-label); }

body.page-admin .active-filter-tag { font-size: var(--sz-label); font-weight: 600; color: var(--text); }

@media (max-width: 600px) {
  body.page-admin .admin-controls { flex-direction: column; align-items: stretch; }
  body.page-admin .admin-controls .form-input { width: 100% !important; }
  body.page-admin .admin-stat { margin-left: 0 !important; }
}
}

/* ╔═══════════════════════════════════════════════════════════════════════╗
   ║ UTILITIES — bounded set of one-off nudges, named with .u- prefix      ║
   ║                                                                       ║
   ║ Use sparingly: only when no existing class covers the case AND the    ║
   ║ nudge is a single-property tweak. Two or more elements wanting the    ║
   ║ same combo of utilities → write a real class instead.                 ║
   ║                                                                       ║
   ║ Declared in @layer utilities so they win over component rules         ║
   ║ without needing !important.                                           ║
   ╚═══════════════════════════════════════════════════════════════════════╝ */
@layer utilities {
  /* Margin */
  .u-mb-4    { margin-bottom: var(--sp-4); }
  .u-ml-auto { margin-left: auto; }

  /* Flex */
  .u-shrink-0 { flex-shrink: 0; }

  /* Alignment */
  .u-va-middle { vertical-align: middle; }
}

/* ╔═══════════════════════════════════════════════════════════════════════╗
   ║ Global navigation (.gnav) + mobile app-shell                          ║
   ║ Persistent app-level nav on every authenticated page. Desktop: slim   ║
   ║ sticky top app bar (document scrolls normally). Mobile (≤720px): the   ║
   ║ page becomes an app-shell — body is a fixed-height flex column, all    ║
   ║ content scrolls inside .app-scroll, and the bar is a STATIC flex item  ║
   ║ at the bottom. Because the document itself never scrolls, the mobile   ║
   ║ browser toolbar never collapses, so the viewport is constant and the   ║
   ║ bar, FAB and fixed modals never jump. The in-page #section-nav and     ║
   ║ per-page .tab-bar are a SEPARATE axis and unchanged.                   ║
   ╚═══════════════════════════════════════════════════════════════════════╝ */
@layer page {
  /* DESKTOP — slim sticky top app bar */
  .gnav {
    position: sticky;
    top: 0;
    z-index: 300;
    view-transition-name: gnav;      /* held still across page navigations */
    height: var(--gnav-h);           /* fixed so the section-nav can stick below it */
    display: flex;
    align-items: center;
    gap: var(--sp-4);
    padding: var(--sp-2) var(--edge-gap);
    background: var(--surface);
    border-bottom: 1px solid var(--border);
  }
  /* index.html stacks the in-page section nav below the global bar so the two
     sticky rows don't collide. */
  body.page-main #section-nav-wrap { top: var(--gnav-h); }
  .gnav-brand {
    font-family: var(--font-ui);
    font-weight: 800;
    font-size: var(--sz-h3);
    letter-spacing: -0.02em;
    color: var(--accent);            /* wordmark = the one sanctioned ember */
    text-decoration: none;
    line-height: 1;
    flex-shrink: 0;
  }
  .gnav-items {
    display: flex;
    align-items: center;
    gap: var(--sp-1);
    margin-left: auto;               /* push the cluster right on desktop */
  }
  .gnav-link {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: var(--sp-2);
    padding: var(--sp-2) var(--sp-3);
    border-radius: var(--radius-lg);
    color: var(--muted);
    font-size: var(--sz-label);
    font-weight: 600;
    white-space: nowrap;
    text-decoration: none;
    background: none;
    border: none;
    cursor: pointer;
    transition: color var(--motion-fast) var(--ease-out),
                background var(--motion-fast) var(--ease-out);
  }
  .gnav-link svg { width: 18px; height: 18px; flex-shrink: 0; }
  .gnav-link:hover { color: var(--text); }
  .gnav-link:focus-visible { outline: none; box-shadow: var(--focus-ring); }
  /* Active = neutral high-contrast (mirrors .snav-link.active). Never ember. */
  .gnav-link.active { color: var(--text); background: var(--muted-soft); }
  .gnav-link .nav-invite-badge { margin-left: var(--sp-1); }
  /* Overflow item that only belongs on the mobile bottom bar (e.g. Manage,
     which lives in the bar on desktop but folds into More on mobile). */
  .gnav-more-mobile-only { display: none; }

  /* Scroll container. Desktop: a no-op (display:contents) so the document
     scrolls exactly as before. Mobile: the app-shell scroller (below). */
  .app-scroll { display: contents; }

  /* MOBILE — app-shell: static bottom tab bar, content scrolls inside .app-scroll */
  @media (max-width: 720px) {
    /* Lock the document so it never scrolls (only .app-scroll does) — this is
       what keeps the browser toolbar from collapsing, so the viewport is
       constant and the bar/FAB/modals stay completely still. */
    html { height: 100%; }
    body.has-gnav {
      height: 100vh;       /* fallback for browsers without dvh */
      height: 100dvh;      /* tracks the toolbar; with overflow:hidden it never collapses */
      /* The base body rule sets min-height:100vh (the LARGE viewport). Left in
         place it forces the shell taller than the visible screen and pushes the
         bottom bar below the fold — cancel it so height:100dvh defines the box. */
      min-height: 0;
      overflow: hidden;
      display: flex;
      flex-direction: column;
    }
    .app-scroll {
      display: block;                /* override the desktop display:contents */
      order: 1;                      /* content above … */
      flex: 1 1 auto;
      min-height: 0;                 /* allow the flex child to shrink & scroll */
      overflow-y: auto;
      -webkit-overflow-scrolling: touch;
    }
    /* page-wrap (standalone pages) centres its card against the viewport via
       min-height:100vh on desktop; inside the shell it should fit the scroller. */
    body.has-gnav .page-wrap { min-height: 100%; }

    .gnav {
      order: 2;                      /* … bar pinned at the bottom, in normal flow */
      position: static;              /* the shell keeps the viewport still — no fixed needed */
      height: var(--gnav-h-mobile);
      gap: 0;
      padding: 0 var(--sp-2);
      border-bottom: none;
      border-top: 1px solid var(--border);
      justify-content: space-around;
    }
    .gnav-brand { display: none; }
    .gnav-items {
      margin-left: 0;
      flex: 1;
      align-items: center;   /* links sized to content + centred, so the active
                                pill has breathing room and doesn't touch edges */
      justify-content: space-around;
      gap: 0;
    }
    .gnav-link {
      flex: 1;
      flex-direction: column;          /* icon over label */
      justify-content: center;
      gap: 2px;
      padding: var(--sp-1) 0;          /* symmetric top/bottom */
      /* Guarantees the active pill never touches the bar's top/bottom edges,
         even if the link were to stretch — a definite gap, not just slack. */
      margin: var(--sp-1) 0;
      border-radius: var(--radius-md);
      font-size: var(--sz-micro);
    }
    .gnav-link svg { width: 22px; height: 22px; }
    .gnav-link--manage { display: none; }   /* folds into More on mobile */
    .gnav-link .nav-invite-badge {
      position: absolute;
      top: 2px;
      right: 50%;
      margin-left: 0;
      transform: translateX(1.1rem);
    }
    .gnav-more-mobile-only { display: flex; }
    /* section nav sticks to the top of the scroller (no global bar above it now) */
    body.page-main #section-nav-wrap { top: 0; }
    /* New-Game FAB + speed-dial sit above the static bottom bar (index only) */
    body.page-main #new-game-fab {
      bottom: calc(var(--gnav-h-mobile) + var(--edge-gap));
    }
    body.page-main .speed-dial-actions {
      bottom: calc(var(--gnav-h-mobile) + var(--edge-gap) + 3.4rem + 0.8rem);
    }
    /* the overflow menu opens upward from the bar, with the SAME side insets as
       the page content (var(--edge-gap)) so its edges line up with the page. */
    #data-panel {
      top: auto;
      bottom: calc(var(--gnav-h-mobile) + var(--sp-2));
      right: var(--edge-gap);
      left: var(--edge-gap);
      min-width: 0;
    }
  }
}

/* ╔═══════════════════════════════════════════════════════════════════════╗
   ║ Mobile keyboard reveal — scroll anchor for focused fields.            ║
   ║ lib/keyboard-reveal.js scrollIntoView({block:'start'})s the focused   ║
   ║ text field on touch devices (the locked app-shell defeats the native  ║
   ║ reveal). scroll-margin-top clears the sticky section-nav / popup      ║
   ║ header and keeps a line of context above the field.                   ║
   ╚═══════════════════════════════════════════════════════════════════════╝ */
@media (max-width: 720px) {
  input, textarea { scroll-margin-top: 4.5rem; }
}

/* ╔═══════════════════════════════════════════════════════════════════════╗
   ║ Legal pages (privacy-policy.html, terms.html) — long-form prose.      ║
   ║ Utilitarian noindex documents rendered inside an .auth-box--lg card,  ║
   ║ so they inherit the auth-family shell (hero + card + footer). The     ║
   ║ universal reset zeroes heading/list margins and there is no global    ║
   ║ prose styling, so every element is restyled here, scoped under        ║
   ║ .legal. Left-aligned (auth cards are centered by default). Nothing    ║
   ║ here is ember except link hover — legal copy is plain by design. ────  ║
   ╚═══════════════════════════════════════════════════════════════════════╝ */
@layer page {

.legal { text-align: left; }

.legal-meta {
  font-size: var(--sz-sm);
  color: var(--muted);
  margin-bottom: var(--sp-5);
}

/* Opening summary paragraph — slightly brighter than body prose. */
.legal-lead {
  font-size: var(--sz-body);
  color: var(--text);
  line-height: 1.6;
  margin-bottom: var(--sp-5);
}

.legal h3 {
  font-size: var(--sz-body);
  font-weight: 700;
  color: var(--text);
  line-height: 1.3;
  margin: var(--sp-6) 0 var(--sp-2);
}

.legal p {
  font-size: var(--sz-body);
  color: var(--muted);
  line-height: 1.6;
  margin-bottom: var(--sp-3);
}

.legal ul {
  list-style: disc;          /* the global `ol` reset kills bullets — ul opts back in */
  display: block;            /* … and the global list flex layout; prose wants normal flow */
  padding-left: 1.25rem;
  margin: 0 0 var(--sp-3);
}
.legal li {
  font-size: var(--sz-body);
  color: var(--muted);
  line-height: 1.55;
  margin-bottom: var(--sp-1);
}

.legal strong { color: var(--text); font-weight: 600; }

.legal a {
  color: var(--text);
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.legal a:hover { color: var(--accent); }

/* Boxed callout — the Wizards Fan Content notice + any set-apart clause. */
.legal-notice {
  margin-top: var(--sp-6);
  padding: var(--sp-4);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  font-size: var(--sz-sm);
  color: var(--muted);
  line-height: 1.55;
}
.legal-notice strong { color: var(--text); }

}

/* ╔═══════════════════════════════════════════════════════════════════════╗
   ║ Landing page (landing.html) — public marketing page.                  ║
   ║ Body carries no page-class (default block layout, like pod-manage /   ║
   ║ profile / styleguide) — every rule below is scoped by its own .lp-*    ║
   ║ class. Reuses existing primitives: .btn-sm(--primary/--ghost) for      ║
   ║ every CTA, .tag--accent for the hero eyebrow, and .section-head/        ║
   ║ .section-title/.section-subtitle for section headers (centered +       ║
   ║ upsized + tick-less here via .lp-main overrides). The hero shows a     ║
   ║ REAL screenshot (img/dashboard-*.webp|jpg) in a full-width rounded    ║
   ║ ember→dark gradient panel — constructed component mockups were tried  ║
   ║ twice and retired (they drift from the real product; don't             ║
   ║ reintroduce them). The page opens with the red→near-black hero on      ║
   ║ flat --bg; below it nothing is a grey full-width band (those were      ║
   ║ tried and rejected — "too much grey"; grey lives only in small cards:  ║
   ║ the step boxes and the CTA box). New primitives:                       ║
   ║ transparent blending header, hero band/panel + breakout shot + ember   ║
   ║ breathe, the ember-chip feature grid, the showcase spotlight rows,     ║
   ║ ember-numeral step boxes, the free note, the CTA band, the warm        ║
   ║ footer plate, and the staggered scroll-reveal utility. ───────────────  ║
   ╚═══════════════════════════════════════════════════════════════════════╝ */
@layer page {

/* ── Header (sticky marketing nav — distinct from .gnav, which is for   ── */
/* authenticated app pages only) ──────────────────────────────────────── */
/* `<header>` (bare element) already carries the main-app hero-band rules
   (ember gradient + film-grain ::before) from the PAGE layer above — those
   aren't class-scoped. Explicit overrides here neutralize both so this stays
   its own flat, slim bar rather than accidentally inheriting the main app's
   one sanctioned grain exception. */
/* The bar is TRANSPARENT at rest — it blends into the flat --bg body (no
   surface fill, no hairline) so the hero panel owns the top of the page.
   Once scrolled it needs to stay legible over content, so it gains a
   translucent --bg wash + blur + resting shadow (raw rgba sanctioned for
   these ambient washes — it's --bg at 86%). */
.lp-header {
  position: sticky;
  top: 0;
  z-index: 300;
  display: flex;
  align-items: center;
  justify-content: center;
  /* Vertical padding matches the hero band's zero top padding: the bar
     content sits sp-4 from the viewport top AND sp-4 from the hero panel,
     so the header reads centered between the two. */
  padding: var(--sp-4) var(--edge-gap);
  background: transparent;
  border-bottom: none;   /* the bare `header` rule leaks a --divider hairline; none = fully blended */
  transition: background var(--motion-base) var(--ease-out), box-shadow var(--motion-base) var(--ease-out);
}
.lp-header::before { content: none; }
.lp-header.is-scrolled {
  background: rgba(13,15,17,0.86);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  box-shadow: var(--shadow-1);
}
/* `<main>` (bare element, id="main") likewise inherits the main-app layout
   rules — flex column + max-width:1350 + its own edge-gap padding, plus
   `main > section + section` adds a divider/margin-top between every pair
   of sections. Both are unscoped by body.page-main. Neutralized here so
   each .lp-section manages its own width/padding/rhythm without doubling up
   or colliding with .lp-cta-band's margin:auto (auto margins + flex stretch
   don't mix — the leaked flex context was silently shrinking it to content
   width, the actual cause of the "sections aren't the same width" bug).
   NOTE: only margin-top/border-top need cancelling here — padding-top must
   NOT be touched: every .lp-section/.lp-hero/.lp-cta-band already sets its
   own padding-top via a class selector, which legitimately outranks the
   leaked bare `main > section + section` rule on specificity alone. An
   earlier version of this override added `padding-top: 0` "for symmetry",
   but that selector's own extra type-selector weight (.lp-main + 2x section)
   made IT win instead and silently zeroed every section's top padding —
   the actual cause of the "inconsistent, sometimes no gap" spacing bug. */
.lp-main {
  display: block;
  max-width: none;
  margin: 0;
  padding: 0;
  min-width: 0;
}
.lp-main > section + section {
  margin-top: 0;
  border-top: none;
}
.lp-header-inner {
  display: flex;
  align-items: center;
  gap: var(--sp-6);
  width: 100%;
  max-width: 1200px;
}
.lp-brand {
  font-family: var(--font-ui);
  font-weight: 800;
  font-size: var(--sz-h3);
  letter-spacing: -0.02em;
  color: var(--accent);   /* wordmark = the one sanctioned recurring ember use */
  text-decoration: none;
  line-height: 1;
  flex-shrink: 0;
}
.lp-nav-links {
  display: flex;
  align-items: center;
  gap: var(--sp-5);
  margin-right: auto;
}
.lp-nav-link {
  font-size: var(--sz-sm);
  font-weight: 600;
  color: var(--muted);
  text-decoration: none;
  transition: color var(--motion-fast) var(--ease-out);
}
.lp-nav-link:hover { color: var(--text); }
.lp-header-actions { display: flex; align-items: center; gap: var(--sp-3); }
/* .btn-sm is written for <button>; adapt it for the <a> CTAs used throughout
   this page (mirrors how .auth-btn--link adapts .auth-btn for anchor use).
   Height is pinned EXPLICITLY (not via padding + line-height) because Gecko
   and Blink compute the content box of <a> vs <button> differently — padding
   -derived heights drifted a pixel or two between variants in Firefox. A
   fixed height + centered flex content is engine-proof. */
.lp-header-actions .btn-sm,
.lp-hero-ctas .btn-sm,
.lp-cta-actions .btn-sm {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 2.5rem;
  padding-top: 0;
  padding-bottom: 0;
  line-height: 1;
  text-decoration: none;
}
/* Header "Log in" (ghost) gets a visible glass box so it reads as a pill the
   SAME height as the solid "Sign up" primary beside it — a fill-less ghost
   looks shorter/lighter next to a filled pill even at identical box height —
   and lifts its contrast against the --surface header bar. */
.lp-header-actions .btn-sm--ghost {
  background: rgba(255,255,255,0.06);
  border: 1px solid rgba(255,255,255,0.24);
  color: var(--text);
}
.lp-header-actions .btn-sm--ghost:hover:not(:disabled) {
  background: rgba(255,255,255,0.12);
  border-color: rgba(255,255,255,0.36);
  color: var(--text);
}

/* ── Hero — a full-width rounded panel (margin on every side so the flat
   --bg body shows around it) carrying the page's ONE ember-glow gradient
   (the ambient body glow is suppressed on landing — body.page-landing —
   and lives in here instead), with centered copy above a REAL product
   screenshot (img/dashboard-1100.webp / -2200.webp / -2200.jpg, served by
   landing.html's <picture> — an actual capture of the live dashboard; the
   constructed component mockups were retired, they drifted from the real
   thing). ──────────────────────────────────────────────────────────────── */
body.page-landing { background: var(--bg); }  /* flat — the glow moved into .lp-hero */
/* Marketing-scale vertical rhythm — landing sections breathe more than the
   app's --sp-section (4rem). Scoped custom property, dropped to 4rem on
   mobile in the media block below. */
.lp-main { --lp-rhythm: 9.5rem; --lp-header-h: 72px; --lp-breakout: 2.75rem; }
/* The hero fills ~one viewport: header (--lp-header-h) + this band + the shot's
   breakout (--lp-breakout) sum to 97svh — 97 not 100 leaves a small buffer so
   header-height variance across browsers can't push the hero past the fold. */
.lp-hero-band {
  padding: 0 var(--sp-6);
  /* min-height (not height) so the panel can still grow if the copy wraps
     taller at zoom / large fonts, rather than clipping. */
  min-height: calc(97svh - var(--lp-header-h) - var(--lp-breakout));
  display: flex;
}
.lp-hero {
  /* TRUE full width — no max-width cap; the band's padding is the margin
     around the panel. Inner content self-caps (ch limits, 1100px shot). */
  /* Ember→dark: the top-centered ember radial (raw rgba/hex sanctioned for
     these ambient washes) sits on a vertical wash that ends DARKER than
     --bg, so the panel melts into the page bottom-first and the screenshot
     floats on near-black — not on a grey slab (the flat --surface base was
     tried and rejected). */
  position: relative;
  /* The whole panel sits LIGHTER than the near-black --bg (#0d0f11) so its
     edge reads as an elevated surface against the darker page — every stop
     of the base linear gradient stays above --bg (bottom lands on --surface
     #1e242b). A warm ember-tinted top melts down into the neutral --surface
     base; the top radial adds the ambient glow. (An earlier version ended
     DARKER than --bg at #05060a, which made the bottom edge dissolve into
     the page instead of reading as a panel — the exact "blends into the
     background" bug.) */
  background:
    radial-gradient(120% 90% at 50% -10%, rgba(242,98,46,0.28) 0%, rgba(242,98,46,0.09) 45%, transparent 72%),
    linear-gradient(180deg, #33201a 0%, #26232a 52%, #1e242b 100%);
  border-radius: 1.75rem;   /* larger, softer hero corner (bigger than the 16px --radius used elsewhere) */
  /* Full-height hero: the band is ~one viewport tall and this panel fills it
     (flex:1). Two stacked zones: the copy block (grows, centers its text) and
     the shot (natural height, image capped in svh) pinned to the bottom, which
     breaks out var(--lp-breakout) past the panel's edge onto the flat --bg. */
  flex: 1;
  min-height: 0;
  padding: var(--sp-6) var(--edge-gap) 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--sp-4);   /* gap between the centered copy block and the shot */
}
/* Ambient ember breathe — one slow atmospheric moment layered over the hero
   glow (below the in-flow content). The global reduced-motion block freezes
   it; with animation off it simply never adds opacity. */
.lp-hero::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: radial-gradient(110% 80% at 50% -10%, rgba(242,98,46,0.12) 0%, transparent 58%);
  opacity: 0;
  animation: lp-ember-breathe 9s ease-in-out infinite alternate;
}
@keyframes lp-ember-breathe {
  from { opacity: 0; }
  to { opacity: 1; }
}
/* Sol Ring (Mike Bierek's From the Vault: Relics art, fetched from Scryfall into /img) as a
   whisper-faint background motif — the page's one MTG-native texture. Blended
   to LUMINOSITY and held at very low opacity so it reads as tone, not a colour
   photo, staying true to the near-monochrome palette. Sits above the gradient
   but below the copy/shot (which take z-index:1). cover keeps it responsive at
   every panel size; the ::after's inherited radius clips it to the panel. */
.lp-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: url('/img/sol-ring-art.jpg') center / cover no-repeat;
  opacity: 0.10;
  mix-blend-mode: luminosity;
}
/* Copy block: grows to fill the space above the shot and centers its text
   inside it, so the headline sits mid-way between the panel top and the image
   instead of clinging to the top. */
.lp-hero-copy {
  position: relative;
  z-index: 1;
  flex: 1 1 auto;
  min-height: 0;
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--sp-6);
}
.lp-hero-title {
  font-weight: 800;
  /* Marketing display scale — same raw-size exception as the .win-pct hero number */
  font-size: clamp(2.9rem, 2rem + 3.4vw, 4.75rem);
  line-height: 1.05;
  letter-spacing: var(--ls-display);
  color: var(--text);
  max-width: 22ch;
}
/* Ember gradient on the payoff word ("Remembered.") — the one sanctioned
   ember text-fill (marketing hero only). Falls back to a solid --accent
   where background-clip:text is unsupported, so the word is never invisible. */
.lp-hero-em { color: var(--accent); }
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .lp-hero-em {
    background: linear-gradient(96deg, #ff9166 0%, #f2622e 52%, #ff7a45 100%);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    color: transparent;
  }
}
.lp-hero-sub {
  /* Marketing display scale (same hero raw-size exception as the title) —
     stepped up from --sz-h3 so the sub reads as hero copy, not body text. */
  font-size: clamp(1.2rem, 1.05rem + 0.6vw, 1.5rem);
  font-weight: 400;
  color: var(--muted);
  line-height: 1.5;
  max-width: 52ch;
}
.lp-hero-ctas { display: flex; flex-wrap: wrap; justify-content: center; gap: var(--sp-3); margin-top: var(--sp-2); }
/* Hero CTAs are the primary conversion point — sized up from the shared
   2.5rem/--sz-sm bar to a taller, larger-type button (both variants share
   the fixed height so they stay equal, per the earlier cross-engine fix). */
.lp-hero-ctas .btn-sm {
  height: 3.25rem;
  padding-left: var(--sp-7);
  padding-right: var(--sp-7);
  font-size: var(--sz-body);
}
/* Secondary hero CTA = a glass/outline button. The default ghost fill
   (--surface-2) blended into the dark ember gradient; a translucent light
   fill + hairline rim reads cleanly on any part of the gradient and keeps
   the primary (solid near-white) as the obvious first action. Ad-hoc white
   overlays are sanctioned for this kind of decorative surface. */
.lp-hero-ctas .btn-sm--ghost {
  background: rgba(255,255,255,0.05);
  border: 1px solid rgba(255,255,255,0.22);
  color: var(--text);
}
.lp-hero-ctas .btn-sm--ghost:hover {
  background: rgba(255,255,255,0.12);
  border-color: rgba(255,255,255,0.32);
}
/* Objection-killing fine print directly under the primary CTA (pulled up
   tight against the buttons, out of the hero's sp-4 flex gap). */
.lp-hero-fineprint {
  font-size: var(--sz-sm);
  color: var(--muted);
  margin-top: calc(-1 * var(--sp-2));
}

/* Shot sits at the bottom of the panel (the copy above grows and pushes it
   down) and the image scales with the VIEWPORT — max-height in svh plus
   max-width — so it's dynamic at every window size AND engine-safe: a
   percentage height off the flex box doesn't resolve in every engine (it broke
   in Firefox), a viewport unit always does. margin-bottom is the breakout. */
.lp-hero-shot {
  position: relative;
  z-index: 1;
  flex: 0 0 auto;
  width: 100%;
  display: flex;
  justify-content: center;
  margin-bottom: calc(-1 * var(--lp-breakout));
}
.lp-hero-shot picture { display: contents; }   /* let the <img> be the flex item */
.lp-hero-shot img {
  display: block;
  width: auto;
  height: auto;
  /* Width cap holds at 1100px through HD/2K (43vw only reaches 1100px at a
     2560px-wide viewport) then grows with the viewport so the shot doesn't
     shrink to a small island on 4K+ panels, ceilinged at 1700px so it stays
     the 2200px source downscaled (crisp) and never dominates ultra-wide. */
  max-width: min(100%, clamp(1100px, 43vw, 1700px));
  /* Cap = the viewport minus the fixed header + copy + breakout allowance
     (~34rem), so the whole hero fits the fold at any window HEIGHT (a plain
     svh fraction can't — the copy is a fixed pixel height, a bigger share of a
     short screen). Width binds on wide screens, this height cap on short ones. */
  max-height: calc(97svh - 34rem);
  border-radius: var(--radius);
  border: 1px solid var(--border);   /* art-backed rim — on the sanctioned border list */
  box-shadow: var(--shadow-3);
}

/* ── Scroll-reveal (below-the-fold sections only — the hero itself must   */
/* appear immediately, same "no load-time entrance animation" spirit the   */
/* app follows elsewhere). noscript in <head> forces this visible with no  */
/* JS, so content is never permanently hidden. ────────────────────────── */
.lp-reveal {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity var(--motion-base) var(--ease-out), transform var(--motion-base) var(--ease-out);
}
.lp-reveal.is-visible { opacity: 1; transform: none; }
/* Staggered children — feature cells and step boxes cascade in behind their
   section (per-child transition-delay; the noscript fallback in landing.html
   force-shows these too, keep the two selectors in sync). */
.lp-reveal .lp-feature-item,
.lp-reveal .lp-step {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity var(--motion-base) var(--ease-out), transform var(--motion-base) var(--ease-out);
}
.lp-reveal.is-visible .lp-feature-item,
.lp-reveal.is-visible .lp-step { opacity: 1; transform: none; }
.lp-reveal .lp-feature-item:nth-child(2), .lp-reveal .lp-step:nth-child(2) { transition-delay: 70ms; }
.lp-reveal .lp-feature-item:nth-child(3), .lp-reveal .lp-step:nth-child(3) { transition-delay: 140ms; }
.lp-reveal .lp-feature-item:nth-child(4) { transition-delay: 210ms; }
.lp-reveal .lp-feature-item:nth-child(5) { transition-delay: 280ms; }
.lp-reveal .lp-feature-item:nth-child(6) { transition-delay: 350ms; }

/* ── Section shell — top-padding-only rhythm. Every section is transparent
   on the flat --bg body (the grey full-width band was rejected), so
   top+bottom padding would double up between neighbours; each section owns
   ONE --lp-rhythm gap above itself and the CTA band closes the page with
   its own bottom padding. ─────────────────────────────────────────────── */
.lp-section { padding: var(--lp-rhythm) var(--edge-gap) 0; }
.lp-section-inner { max-width: 1200px; margin: 0 auto; }
/* Anchor targets clear the sticky marketing bar */
.lp-main > section { scroll-margin-top: 4.5rem; }
/* Smooth anchor scrolling — marketing page only (:has keeps it off the app
   pages, where programmatic jumps must stay instant); the global
   reduced-motion block forces scroll-behavior back to auto. */
html:has(body.page-landing) { scroll-behavior: smooth; }
/* Landing section heads: centered, marketing-scale titles, and no ember
   heading tick — the 3px tick is an app-dashboard cue; here hierarchy comes
   from size. Title uses the same raw-size display exception as the hero. */
/* align-items:center centers the head block on the cross axis. It matters on
   mobile: the main-app `.section-head { flex-direction: column }` (≤600 block)
   leaks in and flips the axis, so justify-content:center then governs the
   VERTICAL axis and the base align-items:flex-start would left-pin a
   shrink-wrapped head (short titles like "How it works" / "Questions,
   answered"). Centering the cross axis keeps the block centered in both modes. */
.lp-main .section-head { justify-content: center; align-items: center; margin-bottom: var(--sp-5); }
.lp-main .section-head-main { align-items: center; text-align: center; }
/* The app's 3px ember tick (sanctioned) is restyled here as a short centered
   bar ABOVE the title — a section start marker, so sections don't blend into
   each other on the flat --bg page. */
.lp-main .section-head .section-title { flex-direction: column; align-items: center; gap: var(--sp-4); }
.lp-main .section-head .section-title::before { width: 2.5rem; height: 3px; }
.lp-main .section-head h2 { white-space: normal; padding-top: 0; }
.lp-main .section-title { font-size: clamp(1.7rem, 1.2rem + 1.6vw, 2.3rem); }
.lp-main .section-subtitle { font-size: var(--sz-body); }

/* ── Feature grid — open three-column: ember icon chip, title, one line of
   copy. No filled boxes, no hairlines (the ruled cells were rejected) —
   generous gaps do the separating. ───────────────────────────────────────── */
.lp-feature-grid {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-8) var(--sp-7);
  margin-top: var(--sp-8);
}
.lp-feature-item { display: flex; }
/* The tiles are NOT links — a signup click here tested as unintuitive. The
   hover is purely decorative delight: rest state is open/transparent (keeps
   the airy grid), and pointer hover reveals a --surface card + lift shadow
   with the icon chip warming to solid ember. The negative margin cancels
   the padding so the resting layout is unchanged and the card only grows on
   hover. Gated on (hover: hover) so a tap can't leave the state stuck on
   touch devices. */
.lp-feature-link {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  flex: 1;
  padding: var(--sp-4);
  margin: calc(-1 * var(--sp-4));
  border-radius: var(--radius);
  background: transparent;
  transition: background var(--motion-fast) var(--ease-out), box-shadow var(--motion-base) var(--ease-out);
}
@media (hover: hover) {
  .lp-feature-link:hover { background: var(--surface); box-shadow: var(--shadow-lift); }
  .lp-feature-link:hover .lp-feature-icon { background: var(--accent); color: var(--on-accent); }
}
/* Ember chip — on the sanctioned spark list (landing feature icon chips);
   the marketing page leans on the brand ember where the app would stay
   neutral. */
.lp-feature-icon {
  width: 3rem;
  height: 3rem;
  border-radius: var(--radius-lg);
  background: var(--accent-soft);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--accent);
  flex-shrink: 0;
  margin-bottom: var(--sp-2);
}
.lp-feature-icon { transition: background var(--motion-fast) var(--ease-out), color var(--motion-fast) var(--ease-out); }
.lp-feature-icon svg { width: 22px; height: 22px; }
.lp-feature-title { font-size: var(--sz-h3); font-weight: 700; color: var(--text); }
.lp-feature-desc { font-size: var(--sz-sm); color: var(--muted); line-height: 1.5; }

/* ── Showcase — a copy-beside-screenshot spotlight row (two of them, one
   after features and one after how-it-works; --flip puts the media first).
   The media slot is a PLACEHOLDER for now (dashed affordance — on the
   sanctioned border list): swap the inner text for a real <img> when the
   captures exist, keeping the class. ─────────────────────────────────────── */
.lp-showcase {
  display: grid;
  grid-template-columns: 1fr 1.2fr;
  gap: var(--sp-8);
  align-items: center;
}
.lp-showcase--flip { grid-template-columns: 1.2fr 1fr; }
.lp-showcase-copy { display: flex; flex-direction: column; gap: var(--sp-3); }
/* Marketing display scale — same raw-size exception as the hero/section titles */
.lp-showcase-title { font-size: clamp(1.4rem, 1.1rem + 1vw, 1.8rem); font-weight: 800; letter-spacing: var(--ls-display); color: var(--text); }
.lp-showcase-desc { font-size: var(--sz-body); color: var(--muted); line-height: 1.6; max-width: 44ch; }
.lp-showcase-media {
  aspect-ratio: 16 / 10;
  border-radius: var(--radius);
  background: var(--surface);
  border: 1px dashed var(--border);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--muted);
  font-size: var(--sz-sm);
}

/* ── How-it-works steps — three --surface boxes (standard borderless card
   model, --surface on --bg) with large ember numerals. The previous
   full-width grey band behind bare columns was rejected ("gigantic grey
   row") — the grey lives in the small boxes now, not the section. ────────── */
.lp-steps {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--sp-5);
  margin-top: var(--sp-7);
}
.lp-step {
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  background: var(--surface);
  border-radius: var(--radius);
  padding: var(--sp-6);
}
.lp-step-num {
  font-size: 3.5rem;
  font-weight: 800;
  line-height: 1;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--accent);   /* landing step numerals — on the sanctioned spark list */
}
.lp-step-title { font-size: var(--sz-h3); font-weight: 700; color: var(--text); }
.lp-step-desc { font-size: var(--sz-sm); color: var(--muted); line-height: 1.5; }

/* ── Free note — a compact strip with a bold headline, sitting between the
   last content section and the closing CTA. Deliberately a note, not a
   feature-weight section: the app sells itself by what it does; this just
   removes the price objection on the way to the CTA. ─────────────────────── */
/* Neutral --surface card with an EMBER icon (on the sanctioned spark list
   with the feature chips) — a --win-soft green tint was tried and rejected
   as out of place on this page's ember-on-neutral palette. */
.lp-note-band { padding: var(--lp-rhythm) var(--edge-gap) 0; }
.lp-note {
  max-width: 1200px;
  margin: 0 auto;
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  background: var(--surface);
  border-radius: var(--radius);
  padding: var(--sp-5) var(--sp-6);
}
.lp-note > svg { width: 22px; height: 22px; flex-shrink: 0; color: var(--accent); }
.lp-note-title { font-size: var(--sz-h3); font-weight: 800; color: var(--text); margin-bottom: var(--sp-1); }
.lp-note-body { font-size: var(--sz-sm); color: var(--muted); line-height: 1.5; }
.lp-note-link { color: var(--text); font-weight: 600; text-decoration: none; white-space: nowrap; transition: color var(--motion-fast) var(--ease-out); }
.lp-note-link:hover { color: var(--muted); }

/* ── Founder story — a personal note near the close: open centered column,
   no ember section-tick (the monogram + first-person voice carry it). The
   heading/body/signature copy is a PLACEHOLDER draft for the owner to
   rewrite in their own voice; the monogram swaps for a real photo later. ── */
.lp-story-band { padding: var(--lp-rhythm) var(--edge-gap) 0; }
.lp-story {
  max-width: 640px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: var(--sp-4);
}
/* One-off marketing monogram — NOT the app .avatar primitive (that's the
   player avatar system); this is a founder mark on the ember-soft tint. */
.lp-story-avatar {
  width: 4.5rem;
  height: 4.5rem;
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--sz-h2);
  font-weight: 800;
  letter-spacing: -0.02em;
  flex-shrink: 0;
}
.lp-story-title { font-size: clamp(1.4rem, 1.1rem + 1vw, 1.8rem); font-weight: 800; letter-spacing: var(--ls-display); color: var(--text); }
.lp-story-body { display: flex; flex-direction: column; gap: var(--sp-3); font-size: var(--sz-body); color: var(--muted); line-height: 1.7; }
.lp-story-sign { font-size: var(--sz-sm); color: var(--text); font-weight: 600; margin-top: var(--sp-1); }
.lp-story-sign span { color: var(--muted); font-weight: 400; }

/* ── FAQ — native <details>/<summary> accordions (no JS): a narrow reading
   column of --surface cards, each with a chevron that rotates open. Sits
   right before the closing CTA to clear last-minute objections. ─────────── */
.lp-faq {
  max-width: 760px;
  margin: var(--sp-7) auto 0;
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
}
.lp-faq-item {
  background: var(--surface);
  border-radius: var(--radius);
}
.lp-faq-q {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  padding: var(--sp-4) var(--sp-5);
  font-size: var(--sz-body);
  font-weight: 600;
  color: var(--text);
  cursor: pointer;
  list-style: none;   /* Firefox default triangle */
}
.lp-faq-q::-webkit-details-marker { display: none; }   /* Safari/Chrome default triangle */
.lp-faq-q:focus-visible { outline: none; box-shadow: var(--focus-ring); border-radius: var(--radius); }
.lp-faq-q::after {
  content: "";
  width: 0.55rem;
  height: 0.55rem;
  border-right: 2px solid var(--muted);
  border-bottom: 2px solid var(--muted);
  transform: rotate(45deg);
  transition: transform var(--motion-base) var(--ease-out);
  flex-shrink: 0;
}
.lp-faq-item[open] .lp-faq-q::after { transform: rotate(-135deg); }
.lp-faq-item[open] .lp-faq-q { color: var(--accent); }
.lp-faq-a {
  padding: 0 var(--sp-5) var(--sp-5);
  font-size: var(--sz-sm);
  color: var(--muted);
  line-height: 1.6;
}
.lp-faq-a a { color: var(--text); font-weight: 600; text-decoration: none; }
.lp-faq-a a:hover { color: var(--muted); }

/* ── CTA band — same shell geometry as .lp-section/.lp-section-inner (the
   band pads full-bleed, the box carries the 1200px cap) so the box's edges
   line up exactly with every other section's content. Putting the cap on
   the band itself made the cap swallow the edge padding (1152px box). ──── */
.lp-cta-band { padding: var(--lp-rhythm) var(--edge-gap); }
.lp-cta-box {
  max-width: 1200px;
  margin: 0 auto;
  background: var(--surface);   /* flat card — a mirrored ember gradient was tried and rejected */
  border-radius: var(--radius);
  box-shadow: var(--shadow-1);
  padding: var(--sp-8) var(--sp-6);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-4);
  text-align: center;
}
.lp-cta-title { font-size: clamp(1.7rem, 1.2rem + 1.6vw, 2.3rem); font-weight: 800; letter-spacing: var(--ls-display); color: var(--text); }
.lp-cta-sub { font-size: var(--sz-body); color: var(--muted); max-width: 50ch; }
.lp-cta-actions { display: flex; flex-wrap: wrap; gap: var(--sp-3); justify-content: center; margin-top: var(--sp-2); }

/* ── Footer — a FLAT warm-dark closing plate. No hairlines, and no gradient
   (an ember-wash gradient was tried and rejected): one colour a shade
   warmer than --bg does the separating (raw hex sanctioned for these
   ambient plates). ────────────────────────────────────────────────────── */
.lp-footer {
  background: var(--surface);
  border-top: 1px solid var(--divider);
  padding: var(--sp-8) var(--edge-gap) var(--sp-5);
}
.lp-footer-inner { max-width: 1200px; margin: 0 auto; display: flex; flex-wrap: wrap; gap: var(--sp-7); justify-content: space-between; }
.lp-footer-brand { display: flex; flex-direction: column; gap: var(--sp-2); max-width: 280px; }
.lp-footer-tagline { font-size: var(--sz-sm); color: var(--muted); }
.lp-footer-cols { display: flex; gap: var(--sp-7); flex-wrap: wrap; }
.lp-footer-col { display: flex; flex-direction: column; gap: var(--sp-2); min-width: 120px; }
/* Group heading over the column's links — group-heading size (--sz-body). */
.lp-footer-col-title { font-size: var(--sz-body); font-weight: 700; letter-spacing: var(--ls-caps); color: var(--muted); margin-bottom: var(--sp-1); }
.lp-footer-link { font-size: var(--sz-sm); color: var(--muted); text-decoration: none; transition: color var(--motion-fast) var(--ease-out); }
.lp-footer-link:hover { color: var(--text); }
.lp-footer-bottom {
  max-width: 1200px;
  margin: var(--sp-7) auto 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  justify-content: space-between;
  font-size: var(--sz-micro);
  color: var(--muted);
}

/* ── Mobile ─────────────────────────────────────────────────────────────── */
@media (max-width: 720px) {
  .lp-main { --lp-rhythm: 5.5rem; }
  .lp-nav-links { display: none; }
  /* Header on mobile: the main-app `header { min-height:110px }` (≤600 block)
     leaks into the landing <header> and inflates it — reset it and tighten the
     vertical padding so the bar is a compact mobile height. With the nav links
     (which carried margin-right:auto) hidden, the brand + actions would clump
     to the left; space-between puts the wordmark left and the buttons right. */
  .lp-header { min-height: 0; padding-top: var(--sp-3); padding-bottom: var(--sp-3); }
  .lp-header-inner { justify-content: space-between; }
  /* Mobile: drop the full-height behaviour — the stacked hero is naturally
     tall, so let it size to content instead of one viewport. */
  .lp-hero-band { min-height: 0; display: block; padding: 0 var(--sp-4); }
  .lp-hero { flex: none; padding: var(--sp-8) var(--sp-4) 0; }
  .lp-hero-copy { flex: none; gap: var(--sp-5); }
  .lp-hero-shot { flex: none; margin-top: var(--sp-6); margin-bottom: calc(-1 * var(--sp-7)); }
  .lp-hero-shot img { width: 100%; height: auto; max-width: 100%; max-height: none; }
  .lp-feature-grid { grid-template-columns: 1fr; gap: var(--sp-6); }
  .lp-showcase, .lp-showcase--flip { grid-template-columns: 1fr; gap: var(--sp-5); }
  .lp-showcase-copy { order: 1; }   /* copy reads first when stacked, incl. the --flip variant */
  .lp-showcase-media { order: 2; }
  .lp-steps { grid-template-columns: 1fr; }
  .lp-step { padding: var(--sp-5); }
  /* Keep the free-note as a row — the check reads left of the copy, vertically
     centered against it (matching desktop), not stranded above it. */
  .lp-note { gap: var(--sp-3); }
  .lp-footer-inner { flex-direction: column; gap: var(--sp-6); }
  .lp-cta-box { padding: var(--sp-6) var(--sp-4); }
}
@media (max-width: 480px) {
  .lp-header-actions .btn-sm { padding: var(--sp-2) var(--sp-3); }
}

}

/* ╔═══════════════════════════════════════════════════════════════════════╗
   ║ Reduced motion — honour the OS "reduce motion" preference.            ║
   ║ Unlayered + !important so it overrides every transition/animation     ║
   ║ (hover shadows, spinner, skeleton shimmer, toasts).                   ║
   ╚═══════════════════════════════════════════════════════════════════════╝ */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}
