/* Ported from ForceHQ.html's approved design tokens/PIN-gate styling (docs/force-hq-token-auth-design.md
   §18) -- same variable names/values, not a new design. Shell (sidebar/topbar/dashboard) styling below
   is a first-pass structural port, not a pixel-for-pixel copy of PortalHeader.html/Page_Dashboard.html --
   see firebase/README.md for what's faithfully ported vs. still a placeholder. */
:root{
  --hq-bg:#F5F7FA; --hq-surface:#fff; --hq-surface-hover:#fafbfc;
  --hq-border:#E2E6ED; --hq-border-strong:#CBD2DE;
  --hq-text:#101828; --hq-text-secondary:#475467; --hq-text-tertiary:#667085; --hq-text-disabled:#98a2b3;
  --hq-primary:#002CA1; --hq-primary-hover:#0080E0; --hq-primary-dark:#002CA1;
  --hq-primary-tint:#E7ECF9; --hq-accent-gold:#FFE500; --hq-accent-gold-tint:#FFF9DB;
  --hq-success:#12805c; --hq-success-tint:#e7f6ef;
  --hq-warning:#b25e09; --hq-warning-tint:#fef1e2;
  --hq-danger:#b42318; --hq-danger-tint:#fdeceb;
  --hq-neutral-tint:#EFF1F5;
  --hq-sp-1:4px; --hq-sp-2:8px; --hq-sp-3:12px; --hq-sp-4:16px; --hq-sp-5:20px; --hq-sp-6:24px; --hq-sp-8:32px; --hq-sp-10:40px;
  --hq-r-sm:6px; --hq-r-md:10px; --hq-r-lg:14px; --hq-r-pill:999px;
  --hq-shadow-sm:0 1px 2px rgba(16,24,40,.04);
  --hq-shadow-md:0 4px 12px rgba(16,24,40,.06);
  --hq-font:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
  --hq-sidebar-w:240px; --hq-topbar-h:60px;
}
*{box-sizing:border-box}
body{margin:0;font-family:var(--hq-font);background:var(--hq-bg);color:var(--hq-text)}

/* ----- PIN gate (ported verbatim from ForceHQ.html) ----- */
#hqPinGate{position:fixed;inset:0;background:var(--hq-bg);display:flex;align-items:center;justify-content:center;z-index:20;padding:var(--hq-sp-6);
  padding-top:max(var(--hq-sp-6),env(safe-area-inset-top));padding-bottom:max(var(--hq-sp-6),env(safe-area-inset-bottom));
  padding-left:max(var(--hq-sp-6),env(safe-area-inset-left));padding-right:max(var(--hq-sp-6),env(safe-area-inset-right));
  overflow-y:auto;min-height:100dvh}
#hqPinGate[hidden]{display:none}
.hqPinCard{background:var(--hq-surface);border:1px solid var(--hq-border);border-radius:var(--hq-r-lg);box-shadow:var(--hq-shadow-md);padding:var(--hq-sp-8);max-width:360px;width:100%;text-align:center;margin:auto}
@media (max-width:380px){ #hqPinGate{padding:var(--hq-sp-4)} .hqPinCard{padding:var(--hq-sp-6)} }
.hqPinCard h1{font-size:18px;margin:0 0 var(--hq-sp-2);color:var(--hq-text)}
.hqPinCard p{font-size:13px;line-height:1.6;color:var(--hq-text-secondary);margin:0 0 var(--hq-sp-5)}
.hqPinScreen[hidden]{display:none}
.hqPinInput{width:100%;box-sizing:border-box;font-size:22px;letter-spacing:6px;text-align:center;padding:10px 8px;border:1px solid var(--hq-border-strong);border-radius:var(--hq-r-sm);font-family:var(--hq-font);margin-bottom:var(--hq-sp-4)}
.hqPinInput:focus{outline:2px solid var(--hq-primary-hover);outline-offset:1px}
.hqPinLabel{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
.hqPinSubmit{width:100%;min-height:44px;background:var(--hq-primary);color:#fff;border:0;border-radius:var(--hq-r-sm);padding:10px;font-weight:600;font-size:14px;cursor:pointer;font-family:var(--hq-font)}
.hqPinSubmit:hover{background:var(--hq-primary-hover)}
.hqPinSubmit:disabled{background:var(--hq-neutral-tint);color:var(--hq-text-disabled);cursor:default}
.hqPinSubmit:focus-visible{outline:2px solid var(--hq-primary-hover);outline-offset:2px}
.hqPinError{color:var(--hq-danger);font-size:12.5px;min-height:16px;margin:var(--hq-sp-3) 0 0}
.hqPinForgot{font-size:12.5px;margin:var(--hq-sp-4) 0 0}
.hqPinForgot a{color:var(--hq-primary);text-decoration:none;padding:8px 4px;display:inline-block}

/* Passkeys (approved 2026-07-31, docs/master-launch-inventory.md §0) */
.hqPasskeyPrompt{margin-bottom:var(--hq-sp-4);padding-bottom:var(--hq-sp-4);border-bottom:1px solid var(--hq-border)}
.hqPasskeyPromptTitle{font-size:14.5px;font-weight:600;color:var(--hq-text);margin:0 0 4px}
.hqPasskeyPromptDesc{font-size:12.5px;color:var(--hq-text-secondary);margin:0 0 var(--hq-sp-3)}
.hqPasskeyNotNow{width:100%;min-height:44px;background:none;border:0;color:var(--hq-text-secondary);font-size:13px;font-family:var(--hq-font);cursor:pointer;margin-top:6px}
.hqPasskeyNotNow:hover,.hqPasskeyNotNow:focus-visible{color:var(--hq-text)}
.hqPasskeyPromptStatus{font-size:12px;color:var(--hq-text-secondary);min-height:14px;margin:6px 0 0}
.hqPasskeySigninBtn{width:100%;min-height:44px;background:var(--hq-surface);color:var(--hq-primary);border:1px solid var(--hq-primary);border-radius:var(--hq-r-sm);padding:10px;font-weight:600;font-size:14px;cursor:pointer;font-family:var(--hq-font);margin-bottom:var(--hq-sp-4)}
.hqPasskeySigninBtn:hover{background:var(--hq-neutral-tint)}
.hqPasskeyDivider{font-size:12px;color:var(--hq-text-tertiary,var(--hq-text-secondary));text-align:center;margin:var(--hq-sp-3) 0}
.hqPinForgot a:hover,.hqPinForgot a:focus-visible{text-decoration:underline}
.hqPinSuccessIcon{font-size:32px;margin-bottom:var(--hq-sp-2)}

/* ----- App shell: sidebar + topbar (structural port of ForceHQ.html; visual fidelity to
   PortalHeader.html's full styling is a follow-up -- see firebase/README.md) ----- */
#hqApp{display:flex;min-height:100vh;min-height:100dvh}
#hqApp[hidden]{display:none}

/* BUG-007 (round 6 -- the real root cause, found from Holly's own live DOM inspection, not a
   network/timeout issue at all): #hqLoading sets `display:flex` via an INLINE style attribute in
   index.html (min-height:100vh flex centering for the branded loading view). An inline style
   attribute always outranks a plain CSS selector rule, including an ID or attribute selector --
   *only* `!important` can override it. hideLoadingView() correctly sets the `hidden` DOM
   property/attribute (confirmed: `hqLoading.hidden === true`), but without this rule the browser's
   own default `[hidden]{display:none}` UA rule is too weak to beat that inline `display:flex`, so the
   element kept rendering at full viewport height -- covering (and pushing below the fold) whatever
   real, correctly-shown screen (recovery/PIN/app) was underneath it the entire time. This is the
   exact same bug class already fixed once before in this codebase for #hqPinGate -- see
   tests/force-hq-pin-gate.test.js's own history -- just missed here because #hqLoading was built with
   an inline style instead of going through this stylesheet like #hqPinGate/#hqApp did. `!important`
   here is NOT optional the way it wasn't needed for #hqPinGate above: that element's own `display:flex`
   was itself just a stylesheet rule (beatable by attribute-selector specificity alone); this one is
   inline, which only `!important` can beat. */
#hqLoading[hidden]{display:none!important}

/* #hqPasskeyManager[hidden]{display:none!important} -- the BUG-014 CSS-specificity fix for the old
   Dashboard-only "Manage Sign-In Devices" modal -- was removed 2026-08-01 along with the element
   itself, which moved to the permanent Sign-In & Security section under Settings (settings.js). The
   underlying lesson (an inline `display:` style needs `!important` to be beaten by `[hidden]`) is
   recorded in git history and in docs/beta-readiness-tracker.md's BUG-014 entry, not repeated here for
   an element that no longer exists. */
/* 2026-08-14, real-iPhone bug (Holly's own device testing): on mobile, the drawer becomes
   position:fixed with top:0;bottom:0 (below), but this base rule's height:100vh still won -- CSS
   over-constrains bottom when top/height/bottom are all set, so bottom is recomputed FROM height,
   never the other way around. 100vh in mobile Safari is measured against the "large viewport" (chrome
   collapsed), taller than what's actually visible whenever the address bar is showing -- so the fixed
   drawer's own box extended below the real visible fold. Its overflow-y:auto never helped, because
   nothing overflowed the box's OWN (incorrectly oversized) height; a position:fixed element can't be
   scrolled into view by the page either, since it isn't part of normal document flow. Net effect:
   Switch Portal/Log out/Manage Sign-In Devices could render entirely below the real screen with no way
   to reach them. Same root cause this codebase already found and fixed once for #hqPinGate (min-height:
   100dvh there) -- just never applied here. 100dvh tracks the REAL current visible viewport (shrinks
   as browser chrome appears), so content correctly overflows this now-accurate box and the existing
   overflow-y:auto can actually do its job. */
#hqSidebar{width:var(--hq-sidebar-w);flex:0 0 auto;background:var(--hq-primary-dark);color:#fff;display:flex;flex-direction:column;position:sticky;top:0;height:100vh;height:100dvh;overflow-y:auto}

/* 2026-08-01, mobile nav polish (Holly's live iPhone finding): a close control, a backdrop, and
   reduced-motion handling for the mobile sidebar drawer -- none of which existed before (only an open
   toggle, no way to close except selecting a nav item). Close button and backdrop are both mobile-only
   (display:none outside the existing #hqSidebar mobile breakpoint, same pattern #hqNavToggle already
   uses), so nothing changes on desktop, where the sidebar is always visible and never a drawer. */
#hqSidebarCloseBtn{display:none;position:absolute;top:10px;right:10px;width:44px;height:44px;background:none;border:0;color:#fff;font-size:22px;line-height:1;cursor:pointer;border-radius:50%;align-items:center;justify-content:center}
#hqSidebarCloseBtn:hover{background:rgba(255,255,255,.1)}
#hqSidebarCloseBtn:focus-visible{outline:2px solid #fff;outline-offset:2px}
#hqSidebarBackdrop{display:none;position:fixed;inset:0;background:rgba(16,24,40,.45);z-index:9}
#hqSidebarBackdrop[hidden]{display:none!important}
@media (max-width:820px){
  #hqSidebar{padding-top:4px}
  #hqSidebarCloseBtn{display:flex}
  #hqSidebarBackdrop:not([hidden]){display:block}
}
@media (prefers-reduced-motion: reduce){
  #hqSidebar{transition:none}
}
/* 2026-08-10, sidebar/sign-in polish pass (Holly's explicit direction, ~8-12px increase requested):
   gap 10px -> 20px. Mark size (96px) and all other spacing/colors/typography unchanged -- polish only. */
.hqBrand{display:flex;flex-direction:column;align-items:center;gap:20px;padding:26px 18px 16px;text-align:center;text-decoration:none;color:inherit}
/* 2026-08-10, sign-in consistency pass real-live finding: same specificity bug already documented for
   #hqLoading/#hqPinScreen ([hidden] and a same-specificity class-based `display` rule are equal-
   specificity, so the LAST one wins by cascade order, not [hidden] automatically) -- #hqPinEnterBrand
   is the first place `hidden` gets applied directly to a `.hqBrand` element (existing uses always nest
   .hqBrand inside a separately-hidden plain div instead), so this never surfaced before. Same fix
   pattern as .hqPinScreen[hidden] below. */
.hqBrand[hidden]{display:none}
.hqBrandMark{display:block;width:96px;height:96px;flex:0 0 auto;object-fit:contain}
/* Single-line "OKLAHOMA FORCE HQ" lockup (docs/decision-log.md, 2026-07-22 "Sidebar lockup: Version
   A selected" -- 96px mark, one line, 15.5px, tightened mark-to-name spacing). Corrected 2026-08-02:
   the port had kept an older two-span "Oklahoma Force"/"HQ" markup with no gap between the spans,
   which rendered as the two words colliding ("Oklahoma ForceHQ") instead of Version A's approved
   single line -- this is the fix, not a new design. */
.hqBrandName{display:block;margin-top:2px;font-size:15.5px;font-weight:700;letter-spacing:.2px;white-space:nowrap}

/* 2026-08-01, passkey-first sign-in polish (Holly's request): the same .hqBrand lockup used in the
   sidebar, reused on the PIN-gate card -- .hqBrandLight overrides only what depends on background
   color (the sidebar is dark Primary Blue; this card is on --hq-surface/white), same mark/wordmark,
   same asset, no new lockup design. .hqBrandMarkSm is a smaller size for this less-dominant placement
   (see docs/design-system.md §9.2 -- 96px is reserved for the sidebar's own "primary brand moment").
   .hqBrandName font-size scaled proportionally to the smaller 64px mark (96:15.5 :: 64:~10.3, rounded
   to 11px) -- same single-line correction as the main lockup above, not a separate design. */
/* 2026-08-10, sign-in polish pass (Holly's explicit direction, ~6-10px increase requested):
   gap 6px -> 14px. Mark size (64px) and all other spacing/colors/typography unchanged -- polish only. */
.hqBrand.hqBrandLight{padding:0;gap:14px}
.hqBrand.hqBrandLight .hqBrandName{font-size:11px;color:var(--hq-text,#101828)}
.hqBrandMark.hqBrandMarkSm{width:64px;height:64px}

/* Approved by Holly, 2026-08-01 (docs/design-system.md §9.9 requires explicit approval before
   animating the F app-mark -- this is that approval, for exactly this effect): a subtle breathing
   scale plus a soft diagonal light sweep while a passkey/PIN sign-in attempt is genuinely in flight.
   transform/opacity only (no layout thrash); off entirely under prefers-reduced-motion. The mark
   artwork itself is never recolored, redrawn, or distorted -- only scaled uniformly and overlaid with
   a masked light gradient. */
.hqBrandMarkWrap{position:relative;display:inline-block;border-radius:50%;overflow:hidden}
@keyframes hqLogoBreathe{0%,100%{transform:scale(1)}50%{transform:scale(1.04)}}
@keyframes hqLogoShine{0%{transform:translateX(-120%) rotate(20deg)}100%{transform:translateX(220%) rotate(20deg)}}
.hqBrandMarkWrap.hqBrandMarkPulsing .hqBrandMark{animation:hqLogoBreathe 2s ease-in-out infinite}
.hqBrandMarkWrap.hqBrandMarkPulsing::after{content:'';position:absolute;top:-60%;left:-60%;width:50%;height:220%;background:linear-gradient(120deg,transparent 0%,rgba(255,255,255,.6) 50%,transparent 100%);animation:hqLogoShine 2s ease-in-out infinite;pointer-events:none}
@media (prefers-reduced-motion: reduce){
  .hqBrandMarkWrap.hqBrandMarkPulsing .hqBrandMark{animation:none}
  .hqBrandMarkWrap.hqBrandMarkPulsing::after{display:none}
}

/* Subtle crossfade from the PIN gate into the app shell once it's genuinely ready (module mount
   actually complete, not a timer) -- added per Holly's review of this same polish pass. */
#hqPinGate{transition:opacity .22s ease}
#hqPinGate.hqFadingOut{opacity:0}
#hqApp.hqFadingIn{opacity:0;transition:opacity .22s ease}
#hqApp.hqFadingIn.hqFadedIn{opacity:1}
@media (prefers-reduced-motion: reduce){
  #hqPinGate{transition:none}
  #hqApp.hqFadingIn{transition:none;opacity:1}
}
.hqNavPlaceholder{padding:var(--hq-sp-4) 18px;font-size:13px;color:#9fb2d6}
.hqNavEmpty{padding:var(--hq-sp-4) 18px;font-size:12.5px;color:#9fb2d6;line-height:1.5}
.hqNavLink{display:flex;align-items:center;gap:10px;padding:11px 18px;color:#dbe3f5;text-decoration:none;font-size:14px;font-weight:500;min-height:44px}
.hqNavLink:hover{background:rgba(255,255,255,.08)}
.hqNavLink.active{background:rgba(255,255,255,.14);color:#fff;font-weight:700;box-shadow:inset 3px 0 0 var(--hq-accent-gold)}
.hqNavIcon{font-size:16px;flex:0 0 auto}
.hqSidebarFooter{margin-top:auto;padding:14px 18px;font-size:11px;color:#7f93b8;border-top:1px solid rgba(255,255,255,.1);word-break:break-all}
.hqLogoutBtn{display:flex;align-items:center;gap:8px;width:100%;background:none;border:1px solid rgba(255,255,255,.25);color:#dbe3f5;font-family:var(--hq-font);font-size:13px;font-weight:600;padding:9px 12px;border-radius:var(--hq-r-sm);cursor:pointer;min-height:40px;margin-top:10px}
.hqLogoutBtn:hover{background:rgba(255,255,255,.08)}

/* 2026-08-13, sidebar utility-nav polish (Holly's explicit direction): Switch Portal/Log out/Manage
   Sign-In Devices previously rendered as tiny 11px .hqSidebarFooter text -- indistinguishable from
   footer/legal copy, not "readable navigation size." Reworked as real nav-style rows: normal readable
   text size (not just a bigger tap target), an icon, and min-height:44px. Switch Portal
   (.hqUtilityPrimary) is the most prominent of the three, since it's an actual navigation action, not
   an account utility -- larger/bolder/brighter than Log out/Manage Sign-In Devices
   (.hqUtilitySecondary), which stay secondary but are still comfortably readable on both desktop and
   mobile (13.5px/600, not 11px). Icons are single emoji glyphs at 16px, matching this sidebar's own
   existing NAV_ITEMS icon convention (shell.js) exactly, not a new SVG icon system. */
/* padding-bottom accounts for the iPhone home-indicator safe area -- env() resolves to 0 on devices
   without one, so this is safe unconditionally, same pattern #hqPinGate already uses. This is the
   LAST element in the sidebar's scrollable column, so it's the one that needs the extra clearance. */
.hqUtilityNav{border-top:1px solid rgba(255,255,255,.12);padding:8px 10px max(12px,env(safe-area-inset-bottom));display:flex;flex-direction:column}
.hqUtilityLink{display:flex;align-items:center;gap:10px;width:100%;box-sizing:border-box;background:none;border:0;text-decoration:none;font-family:var(--hq-font);cursor:pointer;padding:11px 10px;border-radius:var(--hq-r-sm);min-height:44px}
.hqUtilityLink:hover{background:rgba(255,255,255,.08)}
.hqUtilityLink:focus-visible{outline:2px solid #fff;outline-offset:-2px}
.hqUtilityIcon{font-size:16px;flex:0 0 auto;width:18px;text-align:center}
.hqUtilityLink.hqUtilityPrimary{font-size:14.5px;font-weight:700;color:#fff}
.hqUtilityLink.hqUtilitySecondary{font-size:13.5px;font-weight:600;color:#dbe3f5}
/* 2026-08-14, Switch Portal loading state (Holly's explicit direction, real-device finding): tapping
   Switch Portal could take long enough on this project's own documented Apps Script tail-latency
   pattern that it looked like nothing happened -- no visible feedback, no way to tell a slow response
   from a dead tap. .hqUtilityLabel is a dedicated text node (was a bare text node before) so its
   content can change between idle/"Opening portals…"/error text without touching the icon.
   .hqSpinner is a plain CSS-only spinner (no new asset), 14px, hidden by default. Error state reuses
   the same white/bold styling (just swaps the icon and label text) rather than a new error color --
   a light-red tint would read poorly against this dark navy sidebar and duplicate what the icon swap
   already communicates on its own.
   2026-08-15 fix: "hidden by default" above wasn't actually true in the browser -- see the
   .hqSpinner[hidden]{display:none} override further down (near the pre-existing passkey .hqSpinner
   rule, which unconditionally set display:inline-block and silently defeated [hidden] for this one). */
.hqUtilityLabel{flex:1 1 auto}
.hqSpinner{width:14px;height:14px;border:2px solid currentColor;border-top-color:transparent;border-radius:50%;flex:0 0 auto;animation:hqSpin .7s linear infinite}
@keyframes hqSpin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion: reduce){.hqSpinner{animation:none;opacity:.6}}
#hqMain{flex:1 1 auto;min-width:0}
#hqTopbar{height:var(--hq-topbar-h);background:var(--hq-surface);border-bottom:1px solid var(--hq-border);display:flex;align-items:center;gap:14px;padding:0 var(--hq-sp-6);position:sticky;top:0;z-index:5}
#hqTopbar h1{font-size:16px;margin:0}
#hqTopbarTitleWrap{display:flex;flex-direction:column;min-width:0;line-height:1.2}
#hqTopbarContext{display:none}
#hqContent{padding:var(--hq-sp-6)}
.hqShellCard{background:var(--hq-surface);border:1px solid var(--hq-border);border-radius:var(--hq-r-lg);padding:var(--hq-sp-6);max-width:640px}
@media (max-width:820px){
  #hqSidebar{position:fixed;left:0;top:0;bottom:0;transform:translateX(-100%);transition:transform .2s;z-index:10;box-shadow:2px 0 16px rgba(16,24,40,.2);width:min(var(--hq-sidebar-w),80vw)}
  #hqSidebar.open{transform:translateX(0)}
}
#hqNavToggle{display:none;background:none;border:0;font-size:22px;cursor:pointer;min-width:44px;min-height:44px}
@media (max-width:820px){ #hqNavToggle{display:block} }

/* 2026-08-13, mobile polish pass (Holly's explicit direction): the mobile top bar was plain white --
   "doesn't visually belong to the rest of the product" -- and showed only the bare page name (e.g.
   "Dashboard"), giving no indication of which product/context a user is in once the sidebar (the
   only place brand/context showed) is off-canvas. Mobile-only (this breakpoint already governs
   #hqSidebar/#hqNavToggle above, same pattern reused, not a new one) -- desktop's white top bar and
   full always-visible sidebar are untouched. #hqTopbarContext is the static "FORCE HQ" label; every
   one of the 8 page shells sets it identically since Force HQ has one name regardless of page (unlike
   Family Portal's own per-player context, see family/index.html). Text-transform:uppercase here
   rather than typed-uppercase markup, so a screen reader doesn't read it as an acronym-spelled-out
   shout. Size/weight/letter-spacing matches this design system's own existing sidebar nav-section
   label convention (docs/design-system.md §2 type scale: "10.5px/600, uppercase, letter-spaced"),
   not an invented new scale. */
@media (max-width:820px){
  #hqTopbar{background:var(--hq-primary-dark);border-bottom:1px solid rgba(255,255,255,.14)}
  #hqNavToggle{color:#fff}
  #hqTopbarContext{display:block;font-size:10.5px;font-weight:600;letter-spacing:.6px;text-transform:uppercase;color:rgba(255,255,255,.72)}
  #hqTopbar h1{color:#fff}
}

/* ----- Dashboard (ported verbatim from ForceHQ.html's shared shell CSS -- same classes, same
   values, not a new design; Page_Dashboard.html's own markup/render() logic is what's ported into
   app.js) ----- */
.pageIntro{display:flex;align-items:center;justify-content:flex-end;gap:var(--hq-sp-3);margin:-4px 0 var(--hq-sp-6)}
.generatedAt{font-size:12px;color:var(--hq-text-tertiary)}
button.refresh{background:var(--hq-primary);color:#fff;border:0;border-radius:var(--hq-r-sm);padding:8px 15px;font-weight:600;font-size:12.5px;cursor:pointer;font-family:var(--hq-font);transition:background .12s ease}
button.refresh:hover{background:var(--hq-primary-hover)}
button.refresh:disabled{background:var(--hq-neutral-tint);color:var(--hq-text-disabled);cursor:default}
h2.sectionTitle{display:flex;align-items:center;gap:8px;font-size:14.5px;font-weight:650;color:var(--hq-text);margin:var(--hq-sp-8) 0 var(--hq-sp-4);letter-spacing:-.1px}
h2.sectionTitle:first-child{margin-top:0}
h2.sectionTitle svg{color:var(--hq-text-tertiary);flex:0 0 auto}
.panelGrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:var(--hq-sp-4)}
.panel{background:var(--hq-surface);border:1px solid var(--hq-border);border-radius:var(--hq-r-lg);padding:var(--hq-sp-5);box-shadow:var(--hq-shadow-sm);display:flex;flex-direction:column;min-width:0;transition:box-shadow .15s ease,border-color .15s ease}
.panel:hover{box-shadow:var(--hq-shadow-md);border-color:var(--hq-border-strong)}
.panelHead{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:var(--hq-sp-2)}
.panelHead h3{margin:0;font-size:13.5px;font-weight:650;color:var(--hq-text)}
.count{background:var(--hq-primary-tint);color:var(--hq-primary);font-weight:650;font-size:11.5px;border-radius:var(--hq-r-pill);padding:2px 9px;line-height:1.5}
.count.zero{background:var(--hq-neutral-tint);color:var(--hq-text-disabled)}
.panelTotal{font-size:12px;color:var(--hq-text-tertiary);margin-bottom:var(--hq-sp-3)}
.empty{color:var(--hq-text-disabled);font-style:italic;font-size:12.5px;padding:8px 2px}
table.dataTable{width:100%;border-collapse:collapse;font-size:12.5px}
table.dataTable th{text-align:left;color:var(--hq-text-tertiary);font-weight:600;padding:6px 8px;border-bottom:1px solid var(--hq-border);white-space:nowrap}
table.dataTable td{padding:7px 8px;border-bottom:1px solid var(--hq-neutral-tint);vertical-align:top;color:var(--hq-text-secondary)}
table.dataTable tr:last-child td{border-bottom:0}
/* Field Report F6/C4/Finding 4/7/10 (2026-08-14): every .dataTable is now rendered inside this
   wrapper so a wide table scrolls horizontally within its own box instead of overflowing or clipping
   the page at mobile/narrow-desktop widths -- the same fix already proven for Launch Readiness's own
   .lrArchiveTableWrap, applied consistently to every other page's shared renderTable() helper. */
.dataTableWrap{overflow-x:auto}
.actionBtn{background:var(--hq-surface);color:var(--hq-primary);border:1px solid var(--hq-border-strong);border-radius:var(--hq-r-sm);padding:5px 11px;font-size:11.5px;font-weight:600;cursor:pointer;white-space:nowrap;font-family:var(--hq-font);transition:background .12s ease,border-color .12s ease}
.actionBtn:hover{background:var(--hq-primary-tint);border-color:var(--hq-primary)}
.actionBtn:disabled{background:var(--hq-neutral-tint);color:var(--hq-text-disabled);border-color:var(--hq-border);cursor:default}
.helpIcon{display:inline-flex;align-items:center;justify-content:center;margin-left:6px;width:15px;height:15px;border-radius:50%;background:var(--hq-neutral-tint);color:var(--hq-text-tertiary);font-size:10px;font-weight:700;cursor:help;vertical-align:middle}
.panelLink{font-size:11.5px;color:var(--hq-primary);text-decoration:none;font-weight:600;white-space:nowrap;margin-top:var(--hq-sp-3);display:inline-block}
.panelLink:hover{text-decoration:underline}
.panelLink.hidden{display:none!important}
.batchPanel{margin-bottom:var(--hq-sp-6)}
.batchExplain{margin:0 0 var(--hq-sp-4);font-size:12.5px;line-height:1.6;color:var(--hq-text-secondary)}
.batchStats{display:flex;gap:var(--hq-sp-6);flex-wrap:wrap;margin-bottom:var(--hq-sp-3)}
.batchStat{display:flex;flex-direction:column;gap:2px}
.batchStatValue{font-size:24px;font-weight:700;color:var(--hq-text);font-variant-numeric:tabular-nums}
.batchStat.good .batchStatValue{color:var(--hq-success)}
.batchStat.warn .batchStatValue{color:var(--hq-warning)}
.batchStatLabel{font-size:11.5px;color:var(--hq-text-tertiary);font-weight:600;text-transform:uppercase;letter-spacing:.3px}
.batchDeadline{margin:0 0 var(--hq-sp-4);font-size:12px;color:var(--hq-text-tertiary)}
.batchBlockersHead{margin:0 0 var(--hq-sp-2);font-size:12.5px;font-weight:650;color:var(--hq-text)}
#batch-blockersWrap.hidden{display:none!important}
.statGrid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:var(--hq-sp-4)}
.statPanel{background:var(--hq-surface);border:1px solid var(--hq-border);border-radius:var(--hq-r-lg);padding:var(--hq-sp-5);box-shadow:var(--hq-shadow-sm)}
.statPanel h3{margin:0 0 var(--hq-sp-3);font-size:13.5px;font-weight:650;color:var(--hq-text)}
.statRow{display:flex;justify-content:space-between;padding:7px 0;border-bottom:1px solid var(--hq-neutral-tint);font-size:13px}
.statRow:last-child{border-bottom:0}
.statRow .label{color:var(--hq-text-secondary)}
.statRow .value{font-weight:650;color:var(--hq-text)}
.teamWideCard{background:var(--hq-primary-tint);border:1px solid #c9dcfb;border-radius:var(--hq-r-lg);padding:var(--hq-sp-5);margin-bottom:var(--hq-sp-4)}
.teamWideCard h3{margin:0 0 3px;font-size:13.5px;font-weight:650;color:var(--hq-primary-hover)}
.teamWideCard .hint{font-size:11.5px;color:var(--hq-text-secondary);margin:0 0 var(--hq-sp-3)}
.teamWideStats{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:var(--hq-sp-3)}
.teamWideStats .item{background:var(--hq-surface);border-radius:var(--hq-r-sm);padding:9px 11px;border:1px solid rgba(37,99,235,.12)}
.teamWideStats .item .label{display:block;font-size:10.5px;color:var(--hq-text-tertiary);font-weight:600;text-transform:uppercase;letter-spacing:.3px}
.teamWideStats .item .value{display:block;font-size:15px;font-weight:700;color:var(--hq-text);margin-top:3px}
table.playerTable{width:100%;border-collapse:collapse;font-size:13px;background:var(--hq-surface);border:1px solid var(--hq-border);border-radius:var(--hq-r-lg);overflow:hidden;box-shadow:var(--hq-shadow-sm)}
table.playerTable th{background:var(--hq-surface-hover);color:var(--hq-text-tertiary);text-align:left;padding:10px 12px;font-size:11px;font-weight:650;text-transform:uppercase;letter-spacing:.3px;border-bottom:1px solid var(--hq-border)}
table.playerTable td{padding:10px 12px;border-bottom:1px solid var(--hq-neutral-tint);color:var(--hq-text-secondary)}
table.playerTable tr:last-child td{border-bottom:0}
table.playerTable tr:hover td{background:var(--hq-surface-hover)}
table.playerTable td.num,table.playerTable th.num{text-align:right;font-variant-numeric:tabular-nums}
a.sponsorLink{color:var(--hq-primary);text-decoration:none;font-weight:600}
a.sponsorLink:hover{text-decoration:underline}
.msg{padding:10px 0;color:var(--hq-danger);font-size:13px}
.msg:empty{display:none}

/* Passkey setup: loading/failure/success states (2026-08-01, live QA request) -- the app's first
   visible motion anywhere (checked: no @keyframes/spinner existed before this). A minimal ring spinner
   in the existing brand-primary color, not a new visual language -- reuses --hq-primary/--hq-border-
   strong exactly as already defined above, and the alert boxes reuse the existing --hq-danger/
   --hq-success tokens (already used for pill/banner states elsewhere) rather than inventing new colors. */
@keyframes hqSpin{to{transform:rotate(360deg)}}
.hqSpinner{display:inline-block;flex-shrink:0;width:16px;height:16px;border:2px solid var(--hq-border-strong);border-top-color:var(--hq-primary);border-radius:50%;animation:hqSpin .7s linear infinite}
/* 2026-08-15 fix (Switch Portal perpetual-spinner bug): this .hqSpinner's own unconditional
   display:inline-block above -- written for the passkey spinner, which is always shown/hidden via its
   *parent* .hqPasskeyWaiting[hidden] wrapper, never via [hidden] on the spinner itself -- silently
   defeated the browser's default [hidden]{display:none} for every OTHER .hqSpinner usage that toggles
   [hidden] directly on the spinner element (the sidebar Switch Portal spinner, app.js/family.js's own
   setSwitchPortalLinkState_, added 2026-08-14 and reusing this same class name without realizing the
   collision). The JS state machine was already correct -- idle/error both set spinner.hidden = true --
   this CSS override is what actually makes that take visible effect. Author-level, so it applies
   regardless of which .hqSpinner rule's specificity would otherwise "win"; harmless no-op for the
   passkey spinner, which never sets [hidden] on itself. */
.hqSpinner[hidden]{display:none}
.hqPasskeyWaiting{display:flex;align-items:center;gap:9px;font-size:13px;color:var(--hq-text-secondary);padding:10px 0}
.hqPasskeyWaiting[hidden]{display:none}
.hqPasskeyAlert{display:flex;gap:10px;align-items:flex-start;padding:12px 14px;border-radius:var(--hq-r-md);margin:0 0 var(--hq-sp-3);border:1px solid transparent}
.hqPasskeyAlert[hidden]{display:none}
.hqPasskeyAlert.error{background:var(--hq-danger-tint);border-color:var(--hq-danger)}
.hqPasskeyAlert.success{background:var(--hq-success-tint);border-color:var(--hq-success)}
.hqPasskeyAlert svg{flex-shrink:0;margin-top:1px}
.hqPasskeyAlert.error svg{color:var(--hq-danger)}
.hqPasskeyAlert.success svg{color:var(--hq-success)}
.hqPasskeyAlertTitle{font-weight:700;font-size:13.5px;margin:0 0 2px}
.hqPasskeyAlert.error .hqPasskeyAlertTitle{color:var(--hq-danger)}
.hqPasskeyAlert.success .hqPasskeyAlertTitle{color:var(--hq-success)}

/* Controlled player selector (canonical §3/§4/§21, 2026-08-11 Finance rebuild) -- replaces free-text
   player entry everywhere a Finance workflow targets a player. Reuses existing tokens/classes
   (tmSearchInput, actionBtn, pill) rather than inventing a parallel visual language. */
.playerSelector{display:flex;flex-direction:column;gap:var(--hq-sp-3);grid-column:1/-1}
.playerRosterControls{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
.playerRosterControls .tmSearchInput{flex:1 1 180px;min-width:140px}
.playerRosterList{max-height:220px;overflow-y:auto;border:1px solid var(--hq-border);border-radius:var(--hq-r-md);padding:6px;background:var(--hq-surface)}
.playerRosterRow{display:flex;align-items:center;gap:8px;padding:5px 6px;font-size:12.5px;color:var(--hq-text-secondary);border-radius:var(--hq-r-sm);cursor:pointer}
.playerRosterRow:hover{background:var(--hq-neutral-tint)}
.playerRosterRow input{margin:0;flex-shrink:0}
.playerCustomAmounts{display:flex;flex-direction:column;gap:6px;grid-column:1/-1}
.playerCustomAmountRow{display:flex;align-items:center;gap:10px}
.playerCustomAmountRow span{flex:1 1 auto;font-size:12.5px;color:var(--hq-text-secondary)}
.playerCustomAmountRow input{width:110px}
.fundingLineRow{display:grid;grid-template-columns:1fr 110px auto auto;gap:8px;align-items:center;margin-bottom:6px}
.fundingLineRow select,.fundingLineRow input{margin:0}
.hqPasskeyAlertBody{font-size:12.5px;color:var(--hq-text-secondary)}

/* Unloaded financial values (2026-08-20). A number that has not arrived yet must never be drawn as a
   number: $0, 0 and a blank cell are all legitimate financial results, so any of them shown while a
   request is still in flight is indistinguishable from the real answer. Everything asynchronous and
   financial renders one of these instead, and only becomes a figure once the figure is known. Sized in
   em so a skeleton occupies the same line box as the value it stands in for, which keeps the layout
   from jumping when the real number lands. */
.finSkeleton{display:inline-block;min-width:64px;height:1em;vertical-align:-.12em;border-radius:var(--hq-r-pill);background:linear-gradient(90deg,var(--hq-neutral-tint) 25%,var(--hq-border) 37%,var(--hq-neutral-tint) 63%);background-size:400% 100%;animation:finShimmer 1.4s ease-in-out infinite}
.finSkeleton.sm{min-width:42px}
.finSkeleton.lg{min-width:92px;height:1.1em}
.finSkeleton.text{min-width:140px;max-width:100%;height:.85em;border-radius:var(--hq-r-sm)}
@keyframes finShimmer{0%{background-position:100% 50%}100%{background-position:0 50%}}
@media (prefers-reduced-motion:reduce){.finSkeleton{animation:none}}
.finLoadingNote{display:flex;align-items:center;gap:9px;font-size:12.5px;font-weight:600;color:var(--hq-text-secondary);padding:2px 0 var(--hq-sp-3)}
.finErrorState{border:1px solid var(--hq-danger);background:var(--hq-danger-tint);border-radius:var(--hq-r-md);padding:12px 14px;margin-bottom:var(--hq-sp-4)}
.finErrorState p{margin:0 0 var(--hq-sp-3);font-size:13px;color:var(--hq-text);line-height:1.55}

/* Player Accounts list (2026-08-20). A tappable card per family rather than a ledger table: the list
   is read to answer "who owes me something, and for what," and a table gave a $0 credit balance the
   same weight as an outstanding amount. Built from the existing surface/border/shadow/radius tokens
   and the existing pill, so it reads as the same product as every other HQ panel. */
.paCardList{display:flex;flex-direction:column;gap:var(--hq-sp-3)}
.paCard{display:flex;align-items:center;gap:var(--hq-sp-4);width:100%;box-sizing:border-box;background:var(--hq-surface);border:1px solid var(--hq-border);border-radius:var(--hq-r-lg);box-shadow:var(--hq-shadow-sm);padding:var(--hq-sp-4) var(--hq-sp-5);min-height:64px;text-decoration:none;color:inherit;cursor:pointer;transition:box-shadow .15s ease,border-color .15s ease,transform .15s ease}
.paCard:hover{box-shadow:var(--hq-shadow-md);border-color:var(--hq-primary)}
.paCard:focus-visible{outline:2px solid var(--hq-primary);outline-offset:2px}
.paCard[aria-disabled="true"]{cursor:default;pointer-events:none}
.paCardMain{flex:1 1 auto;min-width:0}
/* display:block added 2026-08-20 -- these are <span>s inside .paCardMain, so without it the name and
   the status ran together as one line ("Addie MillerPaid up — nothing outstanding") and the meta
   line's own margin-top was inert, margins having no effect on an inline box. */
.paCardName{display:block;font-size:14.5px;font-weight:650;color:var(--hq-text);line-height:1.35}
.paCardMeta{display:block;font-size:12px;color:var(--hq-text-secondary);line-height:1.45;margin-top:2px;overflow-wrap:anywhere}
.paCardSide{display:flex;align-items:center;gap:var(--hq-sp-3);flex:0 0 auto}
.paCardAmount{text-align:right;font-size:17px;font-weight:700;color:var(--hq-warning);font-variant-numeric:tabular-nums;line-height:1.25}
.paCardAmount .paCardAmountLabel{display:block;font-size:10.5px;font-weight:600;color:var(--hq-text-tertiary);text-transform:uppercase;letter-spacing:.3px}
.paCardAmount.settled{font-size:13px;font-weight:600;color:var(--hq-text-tertiary)}
.paCardChevron{color:var(--hq-text-tertiary);font-size:15px;line-height:1}
@media (max-width:560px){
  .paCard{flex-wrap:wrap;gap:var(--hq-sp-2) var(--hq-sp-3);padding:var(--hq-sp-4)}
  .paCardMain{flex:1 1 100%}
  .paCardSide{flex:1 1 100%;justify-content:space-between}
  .paCardAmount{text-align:left}
}

/* Individual account: the human-readable header, then open charges as cards. Audit detail keeps every
   field it had, one disclosure lower down. */
.paAccountHead{background:var(--hq-surface);border:1px solid var(--hq-border);border-radius:var(--hq-r-lg);box-shadow:var(--hq-shadow-sm);padding:var(--hq-sp-5);margin-bottom:var(--hq-sp-5)}
.paAccountName{margin:0;font-size:20px;font-weight:700;color:var(--hq-text);line-height:1.25}
.paAccountSummary{margin:6px 0 0;font-size:14.5px;font-weight:600;color:var(--hq-warning);font-variant-numeric:tabular-nums}
.paAccountSummary.settled{color:var(--hq-success)}
.paAccountSub{margin:8px 0 0;font-size:12.5px;color:var(--hq-text-secondary);line-height:1.55}
/* Player Account -> Team Directory record (2026-08-22, approved). An inline link under the player's
   name, deliberately NOT another action card: it is a navigation aid, not an action, and the record it
   opens is where the existing View Family Portal button already lives. .panelLink is not reused
   because it carries margin-top:12px sized for panel footers, which reads as a detached orphan
   directly beneath a heading. */
.paRecordLink{display:inline-block;margin:6px 0 0;font-size:12.5px;font-weight:600;color:var(--hq-primary);text-decoration:none}
.paRecordLink:hover{text-decoration:underline}
.paChargeCard{display:flex;align-items:flex-start;gap:var(--hq-sp-4);background:var(--hq-surface);border:1px solid var(--hq-border);border-radius:var(--hq-r-lg);box-shadow:var(--hq-shadow-sm);padding:var(--hq-sp-4) var(--hq-sp-5);margin-bottom:var(--hq-sp-3)}
.paChargeMain{flex:1 1 auto;min-width:0}
.paChargeTitle{font-size:14px;font-weight:650;color:var(--hq-text);line-height:1.35;overflow-wrap:anywhere}
.paChargeMeta{font-size:11.5px;color:var(--hq-text-tertiary);margin-top:3px}
.paChargeAmount{font-size:15.5px;font-weight:700;color:var(--hq-warning);font-variant-numeric:tabular-nums;margin-top:6px}
.paChargeActions{display:flex;align-items:center;gap:var(--hq-sp-3);flex:0 0 auto;flex-wrap:wrap;justify-content:flex-end}
.primaryBtn{background:var(--hq-primary);color:#fff;border:1px solid var(--hq-primary);border-radius:var(--hq-r-sm);padding:8px 14px;font-size:12.5px;font-weight:650;cursor:pointer;white-space:nowrap;font-family:var(--hq-font);min-height:36px;transition:background .12s ease}
.primaryBtn:hover{background:var(--hq-primary-hover);border-color:var(--hq-primary-hover)}
.primaryBtn:focus-visible{outline:2px solid var(--hq-primary-hover);outline-offset:2px}
.primaryBtn:disabled{background:var(--hq-neutral-tint);color:var(--hq-text-disabled);border-color:var(--hq-border);cursor:default}
.quietBtn{background:none;border:0;color:var(--hq-text-tertiary);font-family:var(--hq-font);font-size:12px;font-weight:600;cursor:pointer;padding:8px 6px;min-height:36px;text-decoration:underline}
.quietBtn:hover{color:var(--hq-danger)}
.quietBtn:focus-visible{outline:2px solid var(--hq-primary);outline-offset:2px}
@media (max-width:560px){
  .paChargeCard{flex-direction:column;gap:var(--hq-sp-3)}
  .paChargeActions{width:100%;justify-content:flex-start}
  .primaryBtn{flex:1 1 auto}
}
.paDisclosure{margin:var(--hq-sp-5) 0 var(--hq-sp-3)}

/* Headline financial figures while their read is in flight (2026-08-20). The pill's tint and padding
   are dropped so a skeleton reads as an empty slot rather than as a filled badge that happens to be
   blank -- a badge with no number in it looks like a value of nothing, which is the exact reading this
   whole treatment exists to prevent. `isUnavailable` is deliberately a word, not a dash or a zero: it
   cannot be mistaken for an amount at a glance the way "—" can. */
.count.isPending{background:transparent;padding:2px 0;line-height:1.5}
.isUnavailable{font-style:italic;font-weight:600;color:var(--hq-text-tertiary)}
.count.isUnavailable{background:var(--hq-neutral-tint);color:var(--hq-text-tertiary);font-size:10.5px;letter-spacing:.2px}

/* Record Payment: which charge is armed (2026-08-20). Reuses the primary accent already used for a
   focused/active surface elsewhere rather than introducing a selection colour of its own. */
.paChargeCard.isSelected{border-color:var(--hq-primary);box-shadow:0 0 0 2px var(--hq-primary-tint),var(--hq-shadow-sm)}
.paRecPayContext{margin:0 0 var(--hq-sp-4)}
.paRecPayTitle{font-size:14.5px;font-weight:650;color:var(--hq-text);line-height:1.35;overflow-wrap:anywhere}
.paRecPayMeta{font-size:12.5px;color:var(--hq-text-secondary);margin-top:2px}
.paRecPayAudit{font-size:11px;color:var(--hq-text-tertiary);margin-top:4px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}

/* Record Payment stays collapsed until a charge is chosen (2026-08-20). This element carried
   class="hidden" from the day it was written, but app.css has no global `.hidden` rule -- only the two
   id/class-scoped ones above it -- so the class did nothing and the form was permanently visible
   directly beneath the charge cards. That is why clicking Record Payment appeared to do almost
   nothing: the form it "opened" was already on screen, and Cancel never closed it either. Scoped by id,
   matching the #batch-blockersWrap precedent in this file rather than introducing a global rule that
   would change every other element already carrying the class. */
#paRecPaySection.hidden{display:none!important}

/* Team Directory's Team Shared Account drilldown stays hidden when it is meant to be (2026-08-20).
   Second confirmed instance of the same defect as #paRecPaySection above: both elements have carried
   class="hidden" since they were written, but this stylesheet has no global `.hidden` rule, so the
   class did nothing. Two real consequences, not just a cosmetic one -- the transaction table never
   actually collapsed (the toggle button's label changed while its content stayed put), and the
   Admin-only "View Transactions" button was shown to Coaches, whose getTeamDashboardData response
   carries no teamSharedAccountDetail. The data itself was never at risk: getTeamSharedAccountTransactions
   is Admin-gated server-side, so a Coach clicking it received a permission error rather than any
   transaction. Scoped by id for the same reason as #paRecPaySection -- a global `.hidden` utility would
   silently change every other element in the app already carrying the class. */
#td-transactionsToggleWrap.hidden,
#td-transactionsWrap.hidden{display:none!important}

/* Funding-bucket cards carry two canonical figures (2026-08-20): what the bucket owns and what it is
   still owed / can still recover. The primary figure is the one an Admin acts on -- money they can
   actually pay out or spend -- so it leads; the obligation sits under it in a quieter weight. */
.fundFigure{display:flex;align-items:baseline;justify-content:space-between;gap:var(--hq-sp-3);padding:6px 0;border-bottom:1px solid var(--hq-neutral-tint)}
.fundFigure:last-of-type{border-bottom:0}
.fundFigureLabel{font-size:11.5px;font-weight:600;color:var(--hq-text-tertiary);text-transform:uppercase;letter-spacing:.3px;line-height:1.35}
.fundFigureValue{flex:0 0 auto;font-size:18px;font-weight:700;color:var(--hq-text);font-variant-numeric:tabular-nums;line-height:1.3}
.fundFigureValue.secondary{font-size:14px;font-weight:650;color:var(--hq-text-secondary)}
.fundingCashSource{grid-column:1/-1;display:flex;gap:var(--hq-sp-4);flex-wrap:wrap;padding:2px 0 4px}
.fundingCashSource label{color:var(--hq-text-secondary)}
.fundingCashSource input[disabled]+*,.fundingCashSource label:has(input[disabled]){color:var(--hq-text-disabled)}

/* ===== Action Center bands (2026-08-20) =====
   Twenty rows in one flat table meant the two things that actually needed Holly sat visually level
   with eighteen historical errors, and on a phone they were below the fold entirely. Items are now
   grouped by what they ask of her, as cards rather than table rows -- a table forces horizontal
   scrolling at phone widths, which is precisely where the two useful actions were hardest to find. */
.acBand{margin:0 0 var(--hq-sp-6)}
.acBandHead{display:flex;align-items:baseline;justify-content:space-between;gap:var(--hq-sp-3);margin:0 0 var(--hq-sp-2);flex-wrap:wrap}
.acBandTitle{margin:0;font-size:12px;font-weight:700;letter-spacing:.6px;text-transform:uppercase;color:var(--hq-text-secondary)}
.acBand.isPrimary .acBandTitle{color:var(--hq-danger,#b3261e)}
.acBandNote{margin:0 0 var(--hq-sp-3);font-size:12.5px;line-height:1.6;color:var(--hq-text-secondary)}
.acItems{display:flex;flex-direction:column;gap:var(--hq-sp-3)}
.acItem{background:var(--hq-surface);border:1px solid var(--hq-border);border-radius:var(--hq-r-lg);padding:var(--hq-sp-4);display:flex;flex-direction:column;gap:6px;min-width:0}
.acBand.isPrimary .acItem{border-color:var(--hq-danger,#b3261e);box-shadow:var(--hq-shadow-sm)}
.acItemTop{display:flex;align-items:baseline;justify-content:space-between;gap:var(--hq-sp-3);flex-wrap:wrap}
.acItemTitle{margin:0;font-size:14.5px;font-weight:650;color:var(--hq-text);line-height:1.35}
.acItemAmount{font-weight:700;font-size:14.5px;white-space:nowrap;font-variant-numeric:tabular-nums}
.acItemWhat{margin:0;font-size:13px;line-height:1.55;color:var(--hq-text-secondary)}
.acItemDetail{margin:0;font-size:12.5px;line-height:1.55;color:var(--hq-text-tertiary)}
.acItemActions{display:flex;flex-wrap:wrap;gap:var(--hq-sp-2);align-items:center;margin-top:2px}
/* History stays reachable but never competes: one line each, inside a closed disclosure. */
.acHistory{border:1px solid var(--hq-border);border-radius:var(--hq-r-lg);background:var(--hq-surface)}
.acHistory>summary{cursor:pointer;padding:var(--hq-sp-4);font-size:13px;font-weight:600;color:var(--hq-text-secondary);list-style:none}
.acHistory>summary::-webkit-details-marker{display:none}
.acHistory>summary:hover{color:var(--hq-text)}
.acHistory[open]>summary{border-bottom:1px solid var(--hq-border)}
.acHistoryBody{padding:var(--hq-sp-4)}
.acHistoryRow{padding:8px 0;border-bottom:1px solid var(--hq-neutral-tint);font-size:12.5px;line-height:1.5;color:var(--hq-text-tertiary)}
.acHistoryRow:last-child{border-bottom:0}
.acHistoryRow b{color:var(--hq-text-secondary);font-weight:600}
.acClear{padding:var(--hq-sp-5);text-align:center;font-size:13.5px;color:var(--hq-text-secondary);background:var(--hq-surface);border:1px solid var(--hq-border);border-radius:var(--hq-r-lg)}
@media (max-width:520px){
  .acItemTop{gap:4px}
  .acItemActions>*{flex:1 1 auto}
}

/* Whose portal this is (2026-08-22). Shown only when the server resolved the signed-in person to a
   Coaches record, so an Admin -- or an Admin previewing Coach View -- never sees a coach's name here. */
.tdViewerName{margin:0 0 4px;font-size:20px;font-weight:700;color:var(--hq-text);line-height:1.25}

/* ===== Field Ops planner load state (2026-08-26) =====
   Shared by Practice Planner and Game Planner, styled once here rather than twice in two page
   <style> blocks, because Holly's requirement was explicitly that the two planners not diverge.
   Markup comes from /shared/field-ops-loading.js; this is only how it looks.

   It is deliberately LARGE. The thing being replaced was a blank body, and the failure mode being
   designed against is a coach on a phone in a cage building deciding the page is broken -- so the
   spinner is 22px rather than the 16px utility one, the line is real reading size, and the whole
   block sits at the top of the content area where it cannot fall below the fold on a phone.

   Unused on every other HQ page; .foLoad appears nowhere else. */
.foLoad{display:flex;align-items:flex-start;gap:14px;min-height:76px;max-width:560px}
.foLoadSpinner{width:22px;height:22px;border-width:3px;margin-top:2px}
.foLoadText{display:flex;flex-direction:column;gap:5px;min-width:0}
/* Reserve the second line's height whether or not it is showing, so escalating from one line to two
   does not shove the rest of the page down -- "the status text does not jump around awkwardly." */
.foLoadLine{font-size:15px;font-weight:650;color:var(--hq-text);line-height:1.35}
.foLoadSub{font-size:13px;color:var(--hq-text-secondary);line-height:1.45;min-height:19px}
@media (max-width:600px){
  .foLoad{gap:12px;min-height:72px}
  .foLoadSpinner{width:20px;height:20px}
  .foLoadLine{font-size:14.5px}
  .foLoadSub{font-size:12.5px}
}

/* Tournament Roster card (2026-08-28) -- Force HQ -> Team -> Directory.
   Three actions and a line of metadata; deliberately a .panel like everything else on the page
   rather than a new visual language for one card. */
.tmRosterCard{margin-bottom:var(--hq-sp-6)}
.tmRosterMeta{font-size:12px;color:var(--hq-text-tertiary);margin-bottom:var(--hq-sp-4);min-height:14px}
.tmRosterActions{display:flex;flex-wrap:wrap;gap:var(--hq-sp-2)}
/* Wraps rather than overflows on a phone, and every control keeps a 44px touch target on iPad,
   which is the device this card exists for. */
.tmRosterActions button{min-height:44px}
.tmRosterSecondary{background:var(--hq-surface);color:var(--hq-primary);border:1px solid var(--hq-border-strong)}
.tmRosterSecondary:hover{background:var(--hq-primary-tint)}
.tmRosterSecondary:disabled{background:var(--hq-surface);color:var(--hq-text-disabled);border-color:var(--hq-border)}
.tmRosterStatus{font-size:12px;color:var(--hq-text-tertiary);margin-top:var(--hq-sp-2);min-height:14px}
.tmRosterStatusError{color:var(--hq-danger,#b42318)}

/* ==================================================================================================
   LIVE GAME (2026-08-28) — one mode, one state, two intentional presentations.

   LANDSCAPE is the authoritative large-screen layout, because the 13-inch iPad is hung horizontally
   in the dugout and is never turned. Three panels side by side: the field, the bench, the order. A
   coach reads all three without scrolling.

   PORTRAIT is a phone being used as a real controller, not a fallback and not "rotate your device".
   One panel at a time behind a segmented control, with the live bar pinned so Advance is always one
   tap away. The breakpoint is on WIDTH alone, so a rotated iPad lands here and stays safe rather
   than getting a third bespoke design nobody asked for.

   Every interactive element here is at least 44px on its short side, in BOTH presentations.
   ================================================================================================ */
.gdLive{display:flex;flex-direction:column;gap:var(--hq-sp-4)}

/* The bar that never scrolls away: where we are, and the action taken most. */
.gdLiveBar{position:sticky;top:0;z-index:30;display:flex;align-items:center;justify-content:space-between;
  gap:var(--hq-sp-3);flex-wrap:wrap;background:var(--hq-surface);border:1px solid var(--hq-border-strong);
  border-radius:var(--hq-r-lg);padding:var(--hq-sp-3) var(--hq-sp-4);box-shadow:var(--hq-shadow-md)}
.gdLiveWhere{display:flex;align-items:baseline;gap:var(--hq-sp-3);flex-wrap:wrap;min-width:0}
.gdLiveGame{font-size:18px;font-weight:700;color:var(--hq-text)}
.gdLiveInning{font-size:15px;font-weight:650;color:var(--hq-primary)}
.gdLiveDot{font-size:12px;color:var(--hq-text-tertiary)}
.gdLiveDot.isOn{color:#067647}
.gdLiveDot.isOn::before{content:'● '}
.gdLiveActions{display:flex;align-items:center;gap:var(--hq-sp-2);flex-wrap:wrap}
.gdLiveWhy{font-size:12px;color:var(--hq-text-tertiary);max-width:32ch}

/* 44px floor, everywhere, both presentations. A dugout is not a desk. */
.gdLiveBtn{min-height:44px;padding:0 16px;border-radius:var(--hq-r-sm);border:1px solid var(--hq-border-strong);
  background:var(--hq-surface);color:var(--hq-text);font-family:var(--hq-font);font-size:14px;font-weight:650;cursor:pointer}
.gdLiveBtn:hover{background:var(--hq-neutral-tint)}
.gdLiveAdvance{background:var(--hq-primary);color:#fff;border-color:var(--hq-primary);min-width:132px}
.gdLiveAdvance:disabled{background:var(--hq-neutral-tint);color:var(--hq-text-disabled);border-color:var(--hq-border);cursor:default}
.gdLiveLock{min-width:104px}
.gdLiveOut{border-color:var(--hq-danger);color:var(--hq-danger)}
.gdLiveReturn{border-color:#067647;color:#067647}

.gdLiveProblems{display:flex;flex-direction:column;gap:6px}

/* Three panels on a landscape iPad; one at a time on a phone. */
.gdLiveCols{display:grid;grid-template-columns:minmax(0,1.15fr) minmax(0,0.95fr) minmax(0,0.9fr);gap:var(--hq-sp-4);align-items:start}
/* Padding and heading rhythm are tighter than the planner's on purpose: three stacked sections plus
   two pickers have to fit 1024px on an iPad that is never scrolled during an inning. */
.gdLivePanel{background:var(--hq-surface);border:1px solid var(--hq-border);border-radius:var(--hq-r-lg);
  padding:var(--hq-sp-3);min-width:0}
.gdLiveHead{display:flex;align-items:center;gap:8px;margin:0 0 8px;font-size:13px;font-weight:700;
  letter-spacing:.04em;text-transform:uppercase;color:var(--hq-text-tertiary)}
.gdLiveHead+.gdLiveHead,.gdLiveChips+.gdLiveHead,.gdLivePosGrid+.gdLiveHead,
.gdLiveMore+.gdLiveHead{margin-top:var(--hq-sp-4)}
.gdLiveCount{background:var(--hq-primary-tint);color:var(--hq-primary);border-radius:var(--hq-r-pill);padding:1px 8px;font-size:11.5px}
.gdLiveCue{margin-left:auto;text-transform:none;letter-spacing:0;font-weight:600;color:var(--hq-primary)}

/* Positions: big, colour-coded, and readable at arm's length. */
.gdLivePosGrid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}
.gdLivePos{display:flex;flex-direction:column;align-items:flex-start;gap:2px;min-height:56px;padding:7px 10px;
  border:1px solid var(--hq-border);border-left:5px solid var(--gdPosColor,var(--hq-border-strong));
  border-radius:var(--hq-r-sm);background:var(--hq-surface);font-family:var(--hq-font);cursor:pointer;text-align:left}
.gdLivePos.isOpen{background:var(--hq-neutral-tint);border-style:dashed}
.gdLivePos.isPending{outline:3px solid var(--hq-primary);outline-offset:-1px}
.gdLivePos.isTarget{border-color:var(--hq-primary)}
.gdLivePosName{font-size:12px;font-weight:700;color:var(--hq-text-tertiary)}
.gdLivePosWho{font-size:13.5px;color:var(--hq-text);overflow-wrap:anywhere}

/* Player chips: one line each, never a precision target. */
.gdLiveChips{display:flex;flex-wrap:wrap;gap:8px}
.gdLiveChip{display:inline-flex;align-items:center;gap:6px;min-height:44px;padding:0 12px;border-radius:var(--hq-r-pill);
  border:1px solid var(--hq-border-strong);background:var(--hq-surface);font-family:var(--hq-font);font-size:13.5px;cursor:pointer}
.gdLiveChip.isPending{outline:3px solid var(--hq-primary);outline-offset:-1px}
.gdLiveChip.isOn{background:var(--hq-primary);color:#fff;border-color:var(--hq-primary)}
.gdLiveChip.isSit{background:var(--hq-neutral-tint);color:var(--hq-text-secondary)}
.gdLiveSitWhy{font-size:10.5px;font-weight:700;letter-spacing:.04em;color:var(--hq-text-tertiary)}
.gdLiveChip.isAway{opacity:.55;text-decoration:line-through}
.gdLiveSitBar{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin:var(--hq-sp-3) 0;
  padding:var(--hq-sp-3);background:var(--hq-primary-tint);border-radius:var(--hq-r-sm)}

/* Batting: the whole order, jersey numbers first, out state unmistakable. */
.gdLiveBat{display:flex;flex-direction:column}
.gdLiveBatRow{display:flex;align-items:center;gap:10px;min-height:48px;padding:4px 2px;border-bottom:1px solid var(--hq-neutral-tint)}
.gdLiveBatRow:last-child{border-bottom:0}
.gdLiveBatRow.isOut{opacity:.6}
.gdLiveBatNum{flex:0 0 22px;text-align:right;font-size:12px;color:var(--hq-text-tertiary)}
.gdLiveBatWho{flex:1 1 auto;min-width:0;font-size:14px;overflow-wrap:anywhere}
.gdLiveOutTag{margin-left:8px;font-size:10.5px;font-weight:700;letter-spacing:.04em;color:var(--hq-danger)}
.gdLiveBatRow .gdLiveBtn{flex:0 0 auto;padding:0 14px;font-size:13px}

.gdLiveSeg{display:none}
.gdLiveFoot{display:flex;align-items:center;gap:var(--hq-sp-3);flex-wrap:wrap}
.gdLiveFoot .gdBtn{min-height:44px}

/* ---- COMPACT: a phone held vertically, and a rotated iPad ---------------------------------------
   Not a shrunken landscape. One panel at a time, chosen by a segmented control, so the field is
   readable at 390px without horizontal scrolling and without precision tapping. */
@media (max-width: 900px){
  .gdLiveCols{grid-template-columns:minmax(0,1fr)}
  .gdLivePanel[data-gd-compact-hidden]{display:none}
  .gdLiveSeg{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px;position:sticky;top:72px;z-index:29;
    background:var(--hq-bg,var(--hq-surface));padding:2px 0}
  .gdLiveSegBtn{min-height:44px;border-radius:var(--hq-r-sm);border:1px solid var(--hq-border-strong);
    background:var(--hq-surface);font-family:var(--hq-font);font-size:14px;font-weight:650;color:var(--hq-text-secondary);cursor:pointer}
  .gdLiveSegBtn.active{background:var(--hq-primary);color:#fff;border-color:var(--hq-primary)}
  .gdLiveBar{padding:var(--hq-sp-3)}
  .gdLiveGame{font-size:16px}
  .gdLiveActions{width:100%}
  /* No room for the sentence on a phone; the refusal toast carries the same explanation. */
  .gdLiveWhy{display:none}
  .gdLiveActions .gdLiveAdvance{flex:1 1 auto}
  /* Two columns of positions at phone width keeps each one a comfortable target. */
  .gdLivePosGrid{grid-template-columns:repeat(2,minmax(0,1fr))}
  .gdLiveChip{flex:1 1 calc(50% - 8px);justify-content:flex-start}
}
/* The working-defense banner, in the live arrangement. Sticks under the live bar so a staged edit
   is never scrolled out of sight and forgotten with the dugout waiting on it. */
.gdLiveStaged{position:sticky;top:76px;z-index:28;display:flex;align-items:center;gap:var(--hq-sp-2);
  flex-wrap:wrap;padding:var(--hq-sp-3);border-radius:var(--hq-r-lg)}
.gdLiveStaged span{flex:1 1 220px;min-width:0}
@media (max-width: 900px){ .gdLiveStaged{position:static} .gdLiveStaged .gdLiveBtn{flex:1 1 auto} }
/* P and C sit side by side on a landscape iPad: both lists are short, and stacking them pushed the
   live screen past the 1024px the iPad actually has. On a phone they stack, where height is cheap
   and width is not. */
.gdLiveGroupName{font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;
  color:var(--hq-text-tertiary);margin:var(--hq-sp-3) 0 6px}
.gdLiveMore{margin-top:6px}
/* A 44px summary row, because it is a real control -- and it opens in place, so nothing is buried. */
.gdLiveMore>summary{min-height:44px;display:flex;align-items:center;cursor:pointer;font-size:12.5px;
  font-weight:650;color:var(--hq-primary);list-style:none}
.gdLiveMore>summary::-webkit-details-marker{display:none}
.gdLiveMore>summary::before{content:'▸ ';margin-right:4px}
.gdLiveMore[open]>summary::before{content:'▾ '}

/* ==================================================================================================
   GAME PLANNER RESPONSIVE PASS (2026-08-28)

   Four widths, three intentions:
     >=1500px  large desktop — the batting workspace gets a wider pool column, because there is room
     >=1024px  laptop and 13" iPad landscape — two aligned columns, neither pushing the other down
      <1024px  tablet portrait — still two columns, tighter, because the width is genuinely there
       <760px  phone — panes behind a segmented control, never a shrunken desktop layout
   ================================================================================================ */

/* ---- Batting: two aligned cards, not one stacked on the other -------------------------------- */
.gdBatWork{display:grid;grid-template-columns:minmax(0,1.55fr) minmax(0,1fr);gap:var(--hq-sp-4);
  align-items:start;margin-top:var(--hq-sp-3)}
.gdBatCol{background:var(--hq-surface);border:1px solid var(--hq-border);border-radius:var(--hq-r-lg);
  padding:var(--hq-sp-3);min-width:0}
.gdBatColHead{display:flex;align-items:center;gap:8px;margin:0 0 var(--hq-sp-2);font-size:12.5px;
  font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--hq-text-tertiary)}
.gdBatCount{background:var(--hq-primary-tint);color:var(--hq-primary);border-radius:var(--hq-r-pill);
  padding:1px 8px;font-size:11.5px}
/* The ORDER scrolls inside its own card rather than growing the page: thirteen names should not push
   the pool -- the thing you need most while building -- below the fold. */
.gdBatRows{max-height:min(58vh,620px);overflow:auto;-webkit-overflow-scrolling:touch}
.gdBatPool{display:flex;flex-wrap:wrap;gap:8px}
.gdBatPool .gdPlayerChip{min-height:44px}
.gdBatSeg{display:none}

/* ---- Defense: the validation summary --------------------------------------------------------- */
.gdProblemSummary{display:flex;flex-wrap:wrap;gap:6px;align-items:baseline}
.gdProblemList{font-variant-numeric:tabular-nums}
.gdInningStatus{display:flex;flex-wrap:wrap;gap:var(--hq-sp-4);margin:var(--hq-sp-2) 0 var(--hq-sp-3);
  font-size:12.5px;color:var(--hq-text-secondary)}
.gdInningStatus b{color:var(--hq-text)}

/* ---- Destructive actions, held apart from the ordinary ones ---------------------------------- */
.gdBtnDanger{background:var(--hq-surface);color:var(--hq-danger);border:1px solid var(--hq-danger)}
.gdBtnDanger:hover{background:var(--hq-danger-tint)}
.gdDayRowDanger{border-top:1px solid var(--hq-border);margin-top:var(--hq-sp-3);padding-top:var(--hq-sp-3)}
.gdDeleteBlocked{align-self:center}

/* ---- Large desktop: more room for the pool, so chips sit two or three to a row ---------------- */
@media (min-width: 1500px){
  .gdBatWork{grid-template-columns:minmax(0,1.35fr) minmax(0,1fr);gap:var(--hq-sp-5)}
  .gdBatRows{max-height:min(64vh,760px)}
}

/* ---- Tablet portrait: keep two columns, tighten the gutters ----------------------------------- */
@media (max-width: 1024px) and (min-width: 761px){
  .gdBatWork{grid-template-columns:minmax(0,1.4fr) minmax(0,1fr);gap:var(--hq-sp-3)}
  .gdBatRows{max-height:min(52vh,540px)}
}

/* ---- Phone: panes, not columns ---------------------------------------------------------------
   A 390px two-column batting layout would be two unusable columns. One pane at a time behind a
   segmented control, so reaching the unplaced pool is one tap rather than scrolling past thirteen
   names -- which is the whole complaint this pass exists to answer. */
@media (max-width: 760px){
  .gdBatWork{grid-template-columns:minmax(0,1fr);gap:var(--hq-sp-3)}
  .gdBatCol[data-gd-compact-hidden]{display:none}
  .gdBatSeg{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:6px;margin-top:var(--hq-sp-3)}
  .gdBatSegBtn{min-height:44px;border-radius:var(--hq-r-sm);border:1px solid var(--hq-border-strong);
    background:var(--hq-surface);font-family:var(--hq-font);font-size:14px;font-weight:650;
    color:var(--hq-text-secondary);cursor:pointer}
  .gdBatSegBtn.active{background:var(--hq-primary);color:#fff;border-color:var(--hq-primary)}
  /* No inner scroll on a phone: the page already scrolls, and a nested scroller inside a scrolling
     page is the interaction nobody wins. */
  .gdBatRows{max-height:none;overflow:visible}
  .gdInningStatus{gap:var(--hq-sp-3);font-size:12px}
  .gdProblemSummary{font-size:12.5px}
  /* Destructive controls get their own row rather than sharing one with End game. */
  .gdDayRowDanger{flex-direction:column;align-items:stretch}
  .gdBtnDanger{min-height:44px}
}
