/* ═══════════════════════════════════════════════════════════════════════
   BILL APP THEME — preview override for app.html
   Loaded AFTER styles.css. Pure restyle: no markup, no IDs, no classes
   and no behaviour are touched, so every script binding survives.

   THESIS: The board is a coach's printed pad, not a dark cockpit. The
   pitch is the only saturated thing on screen; everything around it is
   paper, rules and small condensed caps.
   OWN-WORLD: Paper ground, bottle-green ink, canary reserved for the
   single active thing, Brazil blue for informational state. Square
   corners, 2px rules, zero shadows — the bill's grammar at UI scale.
   MODE: Operate. Restrained palette, fixed rem scale, familiar
   affordances. Brand lives in the rules and the caps, never in the way.
   ═══════════════════════════════════════════════════════════════════════ */

:root{
  /* ground & panels */
  --bg-top:#e6dfcb;
  --bg-bottom:#ded6c0;
  --panel:#f7f3e6;
  --panel-2:#f2ecdb;
  --panel-3:#eae3ce;
  --panel-4:#e2dac2;

  /* ink — tints raised until every label clears 4.5:1 on paper.
     Measured: .66 gave 4.29 (fail), .76 gives 5.6, .70 gives 4.7. */
  --text:#0b3b1e;
  --muted:rgba(11,59,30,.78);
  --muted-2:rgba(11,59,30,.70);

  /* state */
  --blue:#0057b8;
  --blue-dark:#00448f;
  --green:#2e7d32;

  /* bill tokens */
  --canary:#ffd100;
  --ink:#0b3b1e;
  --rule:rgba(11,59,30,.26);
  --rule-hard:#0b3b1e;
  --grass:#2e7d32;

  --radius:0px;
  --shadow-soft:none;
  --shadow-strong:none;

  --left-w:252px;
  --right-w:344px;
}

/* ── the desk: paper, with the same low screen the bill uses ───────── */
body{
  background:var(--bg-top) !important;
  color:var(--text);
  font-family:"Archivo","Archivo Variable",system-ui,-apple-system,"Segoe UI",sans-serif;
  font-variation-settings:"wdth" 92;
}
body::before{
  content:"";position:fixed;inset:0;z-index:0;pointer-events:none;
  background-image:radial-gradient(circle at 50% 50%, rgba(11,59,30,.055) .6px, transparent .7px);
  background-size:4px 4px;
  mix-blend-mode:multiply;
}
/* position only — NO z-index. `z-index:1` here makes .app a stacking
   context, which traps the mobile panels: .controls and .aiPanel are
   z-index 400 but #mobilePanelBackdrop (z 398) lives outside .app, so at
   root level it compared against .app's 1 and painted over both panels.
   The Analysis and Notes tabs opened correctly and were then covered by
   the green scrim, which swallowed every tap. Without a z-index .app
   creates no stacking context, so the panels' 400 beats the backdrop's
   398 as intended. The paper grain still sits above static content,
   which is the point of it, and it is pointer-events:none. */
.app{position:relative}

/* Square everything. The bill has no rounded corners and no soft light. */
.controls,.aiPanel,.fieldCard,.board,.modal,.modalCard,.overviewCard,
.resultChip,.toolBtn,.editorInput,.loginInput,.exportMenu,.saveMenu,
.profileMenu,.playerToken,.colorSwatch,.moveToolBtn,.frameBar,.playBar,
.notesEditor,.emptyState,.analysisLoader,.challengeBanner,.forkBanner,
.upgradeModal,.shareModal,.notesModal,.expandedModal,.formationModal,
.playerEditor,.mobileTabBar,.mobileBench,.btn,.analyzeButton{
  border-radius:0 !important;
  box-shadow:none !important;
}

/* ── panels are ruled sheets, not floating cards ───────────────────── */
.controls{
  background:var(--panel) !important;
  border-right:2px solid var(--rule-hard) !important;
  border-left:0;
}
.aiPanel{
  background:var(--panel) !important;
  border-left:2px solid var(--rule-hard) !important;
}
.fieldCard,.board{
  background:transparent !important;
  border:0 !important;
}
/* Fullscreen needs an OPAQUE ground. styles.css gives the expanded card
   #0d1117, but the transparent rule above outranked it, so expanding went
   fixed/inset:0 with an invisible overlay — the page showed straight
   through and the button looked dead. Restate it in the theme's paper. */
.fieldCard.fieldCard--expanded{
  background:var(--bg-top) !important;
}
.fieldCard.fieldCard--expanded .board{background:transparent !important}

/* ── section headings: the bill's condensed caps, at UI scale ──────── */
.sidebarHeader,.aiTitle,.ai-header,.modalSectionTitle{
  font-variation-settings:"wdth" 66,"wght" 900;
  text-transform:uppercase;
  letter-spacing:.04em;
  font-size:.8125rem;
  color:var(--ink);
  border-bottom:2px solid var(--rule-hard);
  padding-bottom:.5rem;
  background:none !important;
  -webkit-text-fill-color:currentColor;
}
.sectionTitle{
  font-variation-settings:"wdth" 70,"wght" 800;
  text-transform:uppercase;
  letter-spacing:.085em;
  font-size:.6875rem;
  color:var(--muted);
}
.toolSection{
  border-top:1px solid var(--rule);
  padding-top:.875rem;
}
.toolSection:first-of-type{border-top:0;padding-top:0}

/* ── controls: one vocabulary, square, ruled ───────────────────────── */
.toolBtn,.editorBtn,.btn,.fieldIconBtn,.frameBarArrow,.frameBarAdd,
.playBarBtn,.notesIconBtn,.moveToolBtn,.exportItem,.cardMenuItem{
  border:2px solid var(--rule-hard) !important;
  background:transparent !important;
  color:var(--ink) !important;
  font-variation-settings:"wdth" 78,"wght" 700;
  text-transform:uppercase;
  letter-spacing:.05em;
  transition:background .15s ease,color .15s ease;
}
.toolBtn:hover,.editorBtn:hover,.btn:hover,.fieldIconBtn:hover,
.frameBarArrow:hover,.frameBarAdd:hover,.playBarBtn:hover,
.notesIconBtn:hover,.moveToolBtn:hover,.exportItem:hover,.cardMenuItem:hover{
  background:var(--ink) !important;
  color:var(--panel) !important;
}

/* the single active thing on screen wears the canary */
.toolBtn.isActive,.toolBtn.active,.moveToolBtn.isActive,.moveToolBtn.active,
.mobileTab.isActive,.mobileTab.active{
  background:var(--canary) !important;
  color:var(--ink) !important;
  border-color:var(--ink) !important;
}

/* primary action: solid ink, canary lettering */
.toolPrimary,.btn--primary,.analyzeButton,.editorBtnPrimary,.loginPrimary,
.challengeSubmitBtn,.emptyStateCTA{
  background:var(--ink) !important;
  color:var(--canary) !important;
  border:2px solid var(--ink) !important;
  font-variation-settings:"wdth" 70,"wght" 900;
  text-transform:uppercase;
  letter-spacing:.05em;
}
.toolPrimary:hover,.btn--primary:hover,.analyzeButton:hover,
.editorBtnPrimary:hover,.loginPrimary:hover,.challengeSubmitBtn:hover{
  background:var(--canary) !important;
  color:var(--ink) !important;
}

.toolGhost,.editorBtnGhost,.playBarBtn--ghost{
  border-color:var(--rule) !important;
  color:var(--muted) !important;
}

/* ── the pitch is the only saturated surface ───────────────────────── */
/* The pitch edge must NOT be a border. field.js sets border:none on purpose
   and measureBoard() reads clientWidth, which excludes the border — with
   box-sizing:border-box a 3px border shrank the content box by 6px in each
   axis, so the aspect ratio stopped matching the pitch and the expand/collapse
   rescale factors (to.w/from.w vs to.h/from.h) came out uneven, scattering the
   formation. `outline` draws outside the box and costs no layout.
   border-radius is left alone too, so the "rounded" field shape in settings
   still works. */
#soccerField{
  box-shadow:none !important;
  outline:3px solid var(--ink);
  outline-offset:0;
}
.frameGhost{opacity:.24;filter:saturate(.5)}

/* ── frame + play bars: ruled strips, tabular time ─────────────────── */
.frameBar,.playBar{
  background:var(--panel-2) !important;
  border:2px solid var(--rule-hard) !important;
}
.frameBarInner{gap:.375rem}
.frameDots .frameDot,.frameDot{border-radius:0 !important}
.frameDot.isActive,.frameDot.active{background:var(--canary) !important;outline:2px solid var(--ink)}
.playTime,.fieldFrameLabel{
  font-variant-numeric:tabular-nums;
  font-variation-settings:"wdth" 74,"wght" 800;
  text-transform:uppercase;letter-spacing:.06em;
  color:var(--ink);
}
.playScrub{accent-color:var(--ink)}
.playBarEmpty,.playQuotaHint{
  color:var(--muted);
  font-variation-settings:"wdth" 88,"wght" 500;
  border:1px dashed var(--rule);
  background:transparent !important;
}

/* ── field header: a ruled caption bar ─────────────────────────────── */
.fieldHeader{
  border-bottom:2px solid var(--rule-hard);
  background:transparent !important;
}
.fieldHeaderTitle{
  font-variation-settings:"wdth" 66,"wght" 900;
  text-transform:uppercase;letter-spacing:.075em;font-size:.6875rem;
  color:var(--ink);
}

/* ── bench tokens keep their team colours; only the chrome changes ─── */
.playerToken{
  border:2px solid var(--ink) !important;
  border-radius:50% !important;
  box-shadow:none !important;
  font-variation-settings:"wdth" 80,"wght" 900;
}
.playerToken.ballBenchToken{border-radius:50% !important}
/* A colour swatch's background is its content — never repaint it. Selection
   is shown with a ring instead, so the colour stays visible. */
.colorSwatch{border:2px solid var(--ink) !important;border-radius:0 !important}
.colorSwatch.isActive,.colorSwatch.active{
  outline:3px solid var(--ink) !important;outline-offset:2px !important;
}

/* ── analysis: figures read as a printed record ────────────────────── */
.overviewCard,.resultChip{
  background:var(--panel-2) !important;
  border:2px solid var(--rule-hard) !important;
}
.overviewValue,.resultChipValue,#overallScore,.challengeScoreNumber{
  font-variation-settings:"wdth" 66,"wght" 900;
  font-variant-numeric:tabular-nums;
  letter-spacing:-.02em;
  color:var(--ink) !important;
  -webkit-text-fill-color:var(--ink);
  background:none !important;
}
.overviewLabel,.resultChipLabel{
  font-variation-settings:"wdth" 74,"wght" 700;
  text-transform:uppercase;letter-spacing:.09em;font-size:.625rem;
  color:var(--muted) !important;
}
.modalList li,.modalCard{border-color:var(--rule) !important}

/* ── modals: sheets pinned to the board, not floating glass ────────── */
.modal,.modalCard,.upgradeModal,.shareModal,.notesModal,.expandedModal,
.formationModal,.playerEditor,.challengePickerModal,.challengeScoreModal,
.deleteModalCard,.loginModal{
  background:var(--panel) !important;
  border:3px solid var(--ink) !important;
  backdrop-filter:none !important;
}
#modalOverlay,.modalOverlay,.fieldExpandBackdrop,.deleteModalOverlay,
.mobilePanelBackdrop{
  background:rgba(11,59,30,.42) !important;
  backdrop-filter:none !important;
}

/* ── inputs ────────────────────────────────────────────────────────── */
.editorInput,.loginInput,.notesEditor,input[type="text"],input[type="email"],
input[type="password"],textarea,select{
  background:#fffdf5 !important;
  border:2px solid var(--rule-hard) !important;
  border-radius:0 !important;
  color:var(--ink) !important;
  font-family:inherit;
}
.editorLabel{
  font-variation-settings:"wdth" 74,"wght" 800;
  text-transform:uppercase;letter-spacing:.08em;font-size:.625rem;
  color:var(--muted);
}
:focus-visible{outline:3px solid var(--ink) !important;outline-offset:2px}

/* ── top nav ───────────────────────────────────────────────────────── */
.navbar,.topbar,nav{
  background:var(--panel) !important;
  border-bottom:2px solid var(--rule-hard) !important;
  backdrop-filter:none !important;
}
.navChipLabel,.profileMenuItem{
  font-variation-settings:"wdth" 78,"wght" 700;
  text-transform:uppercase;letter-spacing:.05em;
}
.profileMenu,.exportMenu,.saveMenu,.cardMenu{
  background:var(--panel) !important;
  border:2px solid var(--ink) !important;
}

/* ── mobile ────────────────────────────────────────────────────────── */
.mobileTabBar{
  background:var(--panel) !important;
  border-top:2px solid var(--rule-hard) !important;
}
.mobileTab{
  font-variation-settings:"wdth" 74,"wght" 800;
  text-transform:uppercase;letter-spacing:.06em;
  color:var(--muted) !important;
}
.mobileBench{
  background:var(--panel-2) !important;
  border-top:2px solid var(--rule-hard) !important;
}

/* ── banners ───────────────────────────────────────────────────────── */
.challengeBanner,.fieldChallengeBanner,.forkBanner{
  background:var(--canary) !important;
  border:2px solid var(--ink) !important;
  color:var(--ink) !important;
}
.challengeBannerTitle,.fieldChallengeBannerText,.forkBannerTitle{
  color:var(--ink) !important;
  font-variation-settings:"wdth" 70,"wght" 900;
  text-transform:uppercase;letter-spacing:.04em;
}

/* ── empty + loading states ────────────────────────────────────────── */
.emptyState,.analysisEmptyState,.analysisLoader{
  background:var(--panel-2) !important;
  border:2px dashed var(--rule) !important;
  color:var(--muted);
}
.emptyStateTitle{
  font-variation-settings:"wdth" 66,"wght" 900;
  text-transform:uppercase;letter-spacing:.03em;
  color:var(--ink);
}

/* Operate mode: transitions convey state, nothing choreographs.
   Scoped to controls and surfaces ON PURPOSE. A blanket
   `*{transition-duration:.15s}` here caused the field-expand bug: styles.css
   leaves #soccerField on `transition-property: all` with a 0s duration, so
   it was inert; giving it a duration made width/height ANIMATE. render()
   sets those in px and resizeBoard() measures clientWidth/clientHeight on
   the very next line, so it read the OLD size, concluded nothing had
   changed, hit its early return and skipped playerManager.relayout(). The
   pitch then grew while every player stayed at coordinates computed for the
   old box — and on collapse the stale factors fed scaleObjects, scrambling
   them for good (setNormalizedPosition clamps to 1, which is why a token
   ended up pinned to the right edge). */
a,button,input,select,textarea,summary,
.toolBtn,.editorBtn,.btn,.fieldIconBtn,.frameBarArrow,.frameBarAdd,
.playBarBtn,.notesIconBtn,.moveToolBtn,.exportItem,.cardMenuItem,
.mobileTab,.navChip,.hubCard,.colorSwatch,.swatch,.playerToken{
  transition-duration:.15s;
}

/* Never animate the pitch's box: its dimensions are measured synchronously
   right after they are set. */
#soccerField,.frameMain,.frameGhost,.frameViewport,.fieldInnerWrapper,.board{
  transition-property:none !important;
}

@media (prefers-reduced-motion:reduce){
  *{animation:none !important;transition:none !important}
}

/* ═══════════════════════════════════════════════════════════════════════
   ROUND 2 — from a real screenshot plus a scripted audit of every dark
   surface and every text/background pair below its WCAG threshold.
   The first pass guessed at class names (.navbar, .topbar) that do not
   exist in this app; these are the real ones.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── 1. LEFTOVER DARK SURFACES ──────────────────────────────────────────
   Each of these was still the old near-black #020617 and had my green
   ink painted onto it — .navTitle measured 1.59:1, the notes B/U buttons
   1.16:1. Unreadable, not just off-world. */

.topNav,.siteFooter{
  background:var(--panel) !important;
  border-bottom:2px solid var(--rule-hard) !important;
  backdrop-filter:none !important;
}
.siteFooter{border-bottom:0 !important;border-top:2px solid var(--rule-hard) !important}
.topNav *,.siteFooter *{color:var(--text)}
.navTitle{
  color:var(--ink) !important;
  font-variation-settings:"wdth" 66,"wght" 900;
  text-transform:uppercase;letter-spacing:-.01em;
  background:none !important;-webkit-text-fill-color:var(--ink);
}

/* nav chips: bordered, not tinted glows */
.navChip,.challengesBtn,.signupBtn,.loginBtn,.upgradeBtn{
  background:transparent !important;
  background-image:none !important;
  border:2px solid var(--rule-hard) !important;
  color:var(--ink) !important;
  border-radius:0 !important;
}
.navChip:hover,.challengesBtn:hover,.signupBtn:hover,.loginBtn:hover,.upgradeBtn:hover{
  background:var(--ink) !important;color:var(--panel) !important;
}
.navChip:hover .navChipLabel,.challengesBtn:hover .navChipLabel{color:var(--panel) !important}
.navChipLabel{
  color:var(--ink) !important;
  font-variation-settings:"wdth" 74,"wght" 800;
  text-transform:uppercase;letter-spacing:.06em;
}
.signupPrimary{
  background:var(--ink) !important;background-image:none !important;
  color:var(--canary) !important;border-color:var(--ink) !important;
}
.signupPrimary:hover{background:var(--canary) !important;color:var(--ink) !important}
.proBadge,.navProBadge{
  background:var(--canary) !important;color:var(--ink) !important;
  border:2px solid var(--ink) !important;border-radius:0 !important;
  font-variation-settings:"wdth" 74,"wght" 900;text-transform:uppercase;
}

/* the notes toolbar was a black strip floating in a paper panel */
.notesToolbar{
  background:var(--panel-3) !important;
  border:2px solid var(--rule-hard) !important;
  border-radius:0 !important;
}
.notesToolbar *,.notesIconBtn,.notesBtn{color:var(--ink) !important}
.notesToolDivider{background:var(--rule) !important}
.notesBtn,.notesIconBtn{
  background:transparent !important;border:1px solid transparent !important;border-radius:0 !important;
}
.notesBtn:hover,.notesIconBtn:hover{background:var(--ink) !important;color:var(--panel) !important}
.notesBtn.isActive,.notesIconBtn.isActive{background:var(--canary) !important;color:var(--ink) !important}

/* frame bar pill was still translucent black */
.frameBarInner{
  background:var(--panel-3) !important;
  border:2px solid var(--rule-hard) !important;
  border-radius:0 !important;
}

.skip-link{
  background:var(--ink) !important;color:var(--canary) !important;
  border-radius:0 !important;
}

/* runtime-injected chrome: cookie banner + first-run tour.
   Injected by JS after load, so the first pass never saw them. */
#tb-cookie-banner{
  background:var(--panel) !important;
  border-top:3px solid var(--ink) !important;
  backdrop-filter:none !important;
}
#tb-cookie-banner *{color:var(--text) !important}
.tb-btn{border-radius:0 !important;border:2px solid var(--ink) !important;background-image:none !important}
.tb-btn-accept{background:var(--ink) !important;color:var(--canary) !important}
.tb-btn-decline{background:transparent !important;color:var(--ink) !important}

#tbWelcomeCard,#tbTooltip{
  background:var(--panel) !important;
  border:3px solid var(--ink) !important;
  border-radius:0 !important;
  box-shadow:none !important;
}
#tbWelcomeCard *,#tbTooltip *{color:var(--text) !important}
#tbWelcomeCard button,#tbTooltip button{
  border:2px solid var(--ink) !important;border-radius:0 !important;
  background:transparent !important;color:var(--ink) !important;
}
#tbBtnNext,#tbStartTour{background:var(--ink) !important;color:var(--canary) !important}
#tbSpotlight{border:3px solid var(--canary) !important;border-radius:0 !important}

/* ── 2. LOW CONTRAST ────────────────────────────────────────────────────
   Small UI labels are the ones people actually need to read fast, so
   these get the strong ink, not the tint. */

.sectionTitle,.sectionTitle span,.overviewLabel,.resultChipLabel,
.editorLabel,.mobileTab,.notesWordCount,.playBarEmpty,.emptyStateText,
.toolGhost,.playQuotaHint,.forgotSubtitle{
  color:var(--muted) !important;
}
.notesWordCount,.infoTip{color:var(--muted-2) !important}

/* the info affordance was a 9px 46%-opacity "i" — it read as a stray
   slash in the screenshot. Make it an actual button. */
.infoTip{
  width:16px;height:16px;min-width:16px;
  border:1.5px solid var(--muted-2) !important;
  border-radius:50% !important;
  background:transparent !important;
  font-size:10px;line-height:1;
  font-variation-settings:"wdth" 88,"wght" 800;
  display:inline-flex;align-items:center;justify-content:center;
}
.infoTip:hover{background:var(--ink) !important;color:var(--panel) !important;border-color:var(--ink) !important}

/* empty states must teach, so they need real contrast, not whispers */
.emptyState,.analysisEmptyState{
  border:2px dashed var(--muted-2) !important;
  background:var(--panel-3) !important;
}
.emptyStateTitle,.emptyStateText{color:var(--text) !important}
.emptyStateIcon{opacity:.5}

/* placeholders: browsers default these far too light */
::placeholder{color:var(--muted-2) !important;opacity:1 !important}
.notesEditor:empty::before,[data-placeholder]::before{color:var(--muted-2) !important;opacity:1 !important}

/* ── 3. FLATTEN REMAINING GRADIENTS (the bill has no soft light) ─────────
   Team hues are functional — blue vs red must stay obvious — so only the
   gradient and glow are removed, never the hue. */
.playerToken.teamA{background:#2f6fd0 !important;background-image:none !important}
.playerToken.teamB{background:#c0392b !important;background-image:none !important}
.playerToken{color:#fff !important;text-shadow:none !important}
.ballBenchToken{background:var(--panel) !important;background-image:none !important}

/* ── 4. FORMATION SELECT: make it read as a control ─────────────────────
   It was paper on paper with no affordance. */
#formationSelector,.formationSelector,select{
  background:#fffdf5 !important;
  border:2px solid var(--rule-hard) !important;
  color:var(--ink) !important;
  font-variation-settings:"wdth" 80,"wght" 700;
}

/* ── 5. SCROLLBARS in the panels ────────────────────────────────────────*/
.controls,.aiPanel,.notesEditor{scrollbar-color:var(--muted-2) var(--panel-3);scrollbar-width:thin}

/* ── 6. ANALYSIS HEADER: kill the old accent underline ──────────────────*/
.ai-header,.aiHeader{border-top:0 !important;background-image:none !important}
.ai-status-dot,.statusPulse{background:var(--green) !important;box-shadow:none !important}

/* ── ROUND 2b — two defects the re-audit caught ─────────────────────────
   1. `#tb-cookie-banner *` was a blunt instrument: it repainted the
      Accept button's own lettering to ink over an ink fill, measuring
      exactly 1.0:1 — invisible. Scope the sweep, then restate the button.
   2. Footer Terms/Privacy links kept the old muted slate (2.29:1). */

#tb-cookie-banner .tb-btn-accept,
#tb-cookie-banner .tb-btn-accept *{color:var(--canary) !important}
#tb-cookie-banner .tb-btn-decline,
#tb-cookie-banner .tb-btn-decline *{color:var(--ink) !important}

.siteFooter a,.siteFooter a *,.footerLinks a{
  color:var(--muted) !important;
  text-decoration:underline;
  text-underline-offset:2px;
}
.siteFooter a:hover{color:var(--ink) !important}

/* ── ROUND 3 — the display face ─────────────────────────────────────────
   styles.css puts Sora on nine title selectors. Sora is a geometric
   display face; the bill wants one condensed grotesque everywhere, so
   these follow the rest of the UI onto Archivo. */
.sidebarHeader,.loginTitle,.statsPanelTitle,.challengesHeader h1,
.challengeTitle,.achievementModalTitle,.aiTitle,.notesHeaderTitle,
#currentFormation{
  font-family:"Archivo",system-ui,-apple-system,"Segoe UI",sans-serif !important;
  letter-spacing:-.01em;
}

/* ── ROUND 4 — JS-injected status text ──────────────────────────────────
   my_tactics.html builds its load-failure message with an inline
   color:#9ca3af, a gray chosen for the old dark ground. On paper it
   measures 1.91:1. Inline styles can only be beaten with !important, and
   this is scoped to status paragraphs the script drops into the grid so
   the script itself stays untouched. */
#tacticsGrid > p[style],
#tactics-grid > p[style],
#tacticsBar > p[style]{
  color:var(--muted) !important;
}

/* Semantic state colours, restated for a light ground. The originals were
   all tuned against near-black and wash out on paper. */
.errorText,.error,.formError,.loginError{color:#9b1c1c !important}
.successText,.success{color:#1b5e20 !important}
.warnText,.warning{color:#8a5300 !important}

/* ═══════════════════════════════════════════════════════════════════════
   ROUND 5 — page-specific components
   settings.html needed nothing; profile, challenges, reset-password and
   404 each carry their own inline stylesheet with colours hardcoded for
   the old near-black ground. Those live outside styles.css's token set,
   so overriding :root never reached them.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── shared: any page-specific panel becomes a ruled paper sheet ──────── */
.statsPanelCard,.statTile,.achievementsPanelCard,.avatarStage,.creatorPanel,
.creatorPanelBody,.creatorTabPanel,.styleThumb,.statSkeleton,.numberInput,
.activeChallengeCard,.leaderboardTable,.pastChallengeCard,.emptyStateBox,
.challengeSideInfo,.resetCard,.requirements,.podiumBase{
  background:var(--panel) !important;
  background-image:none !important;
  border:2px solid var(--rule-hard) !important;
  border-radius:0 !important;
  box-shadow:none !important;
  color:var(--text) !important;
}
.statTile,.pastChallengeCard{background:var(--panel-2) !important}
.styleThumb{background:var(--panel-2) !important}
.styleThumb *{background:none !important}

/* ── shared: text ─────────────────────────────────────────────────────── */
.statLabel,.optionGroupLabel,.attemptsLabel,.challengeMetaLabel,
.challengeDesc,.pastWinnerLabel,.pastChallengeSeason,.resetSubtitle,
.nf-subtitle,.lbName,.successMsg{
  color:var(--muted) !important;
}
.statValue,.statsPanelTitle,.challengeTitle,.challengesHeader h1,
.challengeMetaValue,.lbScore,.podiumName,.podiumScore,.pastChallengeTitle,
.pastWinnerName,.resetTitle,.successTitle,.nf-title,.nf-code,.lbRank{
  color:var(--text) !important;
  -webkit-text-fill-color:var(--text);
  background:none !important;
  background-image:none !important;
}
.statsPanelTitle,.challengeTitle,.resetTitle,.successTitle,.nf-title,
.challengesHeader h1{
  font-variation-settings:"wdth" 62,"wght" 900;
  text-transform:uppercase;letter-spacing:-.015em;
}
.statValue,.lbScore,.podiumScore,.pastChallengeScore,.nf-code,.lbRank{
  font-variant-numeric:tabular-nums;
  font-variation-settings:"wdth" 66,"wght" 900;
}
.statEmoji,.challengesHeaderIcon,.statsPanelIcon{filter:none;opacity:1}

/* ── shared: buttons ──────────────────────────────────────────────────── */
.saveAvatar,.submitFormationBtn,.resetBtn,.nf-btn-primary{
  background:var(--ink) !important;background-image:none !important;
  color:var(--canary) !important;
  border:2px solid var(--ink) !important;border-radius:0 !important;
  box-shadow:none !important;
  font-variation-settings:"wdth" 70,"wght" 900;
  text-transform:uppercase;letter-spacing:.05em;
}
.saveAvatar:hover,.submitFormationBtn:hover,.resetBtn:hover,.nf-btn-primary:hover{
  background:var(--canary) !important;color:var(--ink) !important;
}
.randomizeAvatar,.nf-btn-ghost,.backLink,.creatorTab,.challengeTab{
  background:transparent !important;background-image:none !important;
  color:var(--ink) !important;
  border:2px solid var(--rule-hard) !important;border-radius:0 !important;
  font-variation-settings:"wdth" 74,"wght" 800;
  text-transform:uppercase;letter-spacing:.05em;
}
.randomizeAvatar:hover,.nf-btn-ghost:hover,.backLink:hover,
.creatorTab:hover,.challengeTab:hover{
  background:var(--ink) !important;color:var(--panel) !important;
}
.creatorTab.active,.challengeTab.active,.styleThumb.selected{
  background:var(--canary) !important;color:var(--ink) !important;
  border-color:var(--ink) !important;
}

/* ── badges and dots ──────────────────────────────────────────────────── */
.challengeTypeBadge,.challengeTimeBadge,.proUnlimitedBadge,.lbYouTag{
  background:var(--canary) !important;background-image:none !important;
  color:var(--ink) !important;
  border:2px solid var(--ink) !important;border-radius:0 !important;
  font-variation-settings:"wdth" 74,"wght" 900;
  text-transform:uppercase;letter-spacing:.05em;
}
.challengeTimeDot,.attemptDot{background:var(--ink) !important}
.attemptDot.used,.attemptDot.spent{background:transparent !important;border:2px solid var(--rule)}

/* ── tables and rows ──────────────────────────────────────────────────── */
.leaderboardHeader{
  background:var(--ink) !important;color:var(--stock,var(--canary)) !important;
  font-variation-settings:"wdth" 74,"wght" 900;
  text-transform:uppercase;letter-spacing:.08em;
}
.leaderboardHeader *{color:var(--canary) !important}
.leaderboardRow{
  background:transparent !important;
  border-bottom:1px solid var(--rule) !important;
}
.leaderboardRow:hover{background:var(--panel-3) !important}

/* ── inputs ───────────────────────────────────────────────────────────── */
.resetInput,.numberInput input{
  background:#fffdf5 !important;
  border:2px solid var(--rule-hard) !important;border-radius:0 !important;
  color:var(--ink) !important;
}
/* .swatch is an avatar colour chip: its inline background IS the colour the
   user is picking. Painting it cream blanked the whole palette. Only the
   selection ring is themed. */
.swatch{border-radius:50% !important;box-shadow:none !important}
.swatch.selected{
  border-color:var(--ink) !important;
  outline:3px solid var(--ink) !important;outline-offset:2px !important;
  box-shadow:none !important;
}
.req{color:var(--muted) !important}
.req-dot{background:var(--muted-2) !important}
.req.met,.req.ok{color:#1b5e20 !important}
.req.met .req-dot,.req.ok .req-dot{background:#1b5e20 !important}
.resetError{color:#9b1c1c !important}
.resetSuccess,.successIcon{color:#1b5e20 !important}

/* ── 404: the flag keeps its own drawing, only the ground changes ─────── */
.notFoundPage{background:transparent !important}
.nf-field{background:var(--grass) !important;border:2px solid var(--ink) !important;border-radius:0 !important}

/* ── ROUND 5b — containers and loading states the sweep surfaced ────────
   .creatorTabs stayed dark, which made its (correctly re-inked) child
   tabs unreadable at 1.4:1 — the container, not the control, was wrong.
   The rest are "Loading…" and header states that live in styles.css. */
.creatorTabs,.creatorLayout,.creatorCenter,.statsGrid,.statsPanel,
.avatarActions,.styleRow,.swatchRow,.achievementsGrid,.achievementsGroup,
.attemptsTracker,.challengeMetaRow,.challengeBadgeRow,.pastChallengesGrid{
  background:transparent !important;
  background-image:none !important;
}
.achievementsHeader{border-bottom:2px solid var(--rule-hard) !important;background:transparent !important}
.achievementsHeaderTitle{
  color:var(--text) !important;-webkit-text-fill-color:var(--text);
  background:none !important;background-image:none !important;
  font-variation-settings:"wdth" 62,"wght" 900;text-transform:uppercase;
}
.achievementsHeaderCount,.achievementsGroupLabel,.achievementsEmpty,
.challengeParticipants,.resetLoading,.mobileBenchHint,.statSkeletonLbl{
  color:var(--muted) !important;
}
.achievementTile{
  background:var(--panel-2) !important;background-image:none !important;
  border:2px solid var(--rule-hard) !important;border-radius:0 !important;
  box-shadow:none !important;
}
.achievementTile--locked{opacity:.55}
.achievementTileTitle{color:var(--text) !important}
.achievementTileProgress{color:var(--muted) !important}
.achievementTileBar{background:var(--panel-4) !important;border-radius:0 !important}
.achievementTileBarFill{background:var(--ink) !important;border-radius:0 !important}
.achievementTierPill{
  background:var(--canary) !important;color:var(--ink) !important;
  border:2px solid var(--ink) !important;border-radius:0 !important;
}

/* ── ROUND 5c — settings.html's own inline styles ───────────────────────
   .settingsTitle is literally `color: white` and the two labels are
   #64748b, both chosen for the old near-black ground. On paper they
   measure 1.33:1 and 3.75:1. An earlier sweep reported this page clean —
   it was reading a cached stylesheet, which is exactly the failure mode
   the repo's ?v= discipline exists to prevent. */
.settingsTitle{
  color:var(--text) !important;-webkit-text-fill-color:var(--text);
  background:none !important;background-image:none !important;
  font-variation-settings:"wdth" 62,"wght" 900;
  text-transform:uppercase;letter-spacing:-.015em;
}
.settingsCardTitle{
  color:var(--muted) !important;
  font-variation-settings:"wdth" 70,"wght" 800;
}
.settingsLoading,.settingsSubtitle,.settingsHint,.cancelNotice{color:var(--muted) !important}
.settingsCard,.settingsPanel{
  background:var(--panel) !important;background-image:none !important;
  border:2px solid var(--rule-hard) !important;border-radius:0 !important;
  box-shadow:none !important;
}
.proStatusValue,.settingsValue{color:var(--text) !important}

/* ═══════════════════════════════════════════════════════════════════════
   ROUND 6 — the auth modal
   .loginCard and friends were never themed: the card stayed #0f172a with a
   14px radius and a drop shadow, .loginGhost stayed dark slate, and the
   primary button kept its 8px corners. Sign-in is the first thing a new
   user sees, so it cannot be the one screen still wearing the old skin.
   ═══════════════════════════════════════════════════════════════════════ */

.loginCard,.resetCard,.forgotView,.confirmationView{
  background:var(--panel) !important;
  background-image:none !important;
  border:3px solid var(--ink) !important;
  border-radius:0 !important;
  box-shadow:none !important;
  color:var(--text) !important;
}
/* the backdrop is a scrim, never a solid sheet */
#loginModal,.loginModal{
  background:rgba(11,59,30,.42) !important;
  backdrop-filter:none !important;
}
.loginTitle{
  color:var(--ink) !important;-webkit-text-fill-color:var(--ink);
  background:none !important;background-image:none !important;
  font-variation-settings:"wdth" 62,"wght" 900;
  text-transform:uppercase;letter-spacing:-.015em;
}
.loginSubtitle,.forgotSubtitle,.confirmHint,.signupSwitch{color:var(--muted) !important}
.signupSwitch span,.switchLink,#switchToSignup,#switchToForgot,#switchBackToLogin,
#backToLoginFromConfirm,#signupSwitch span{
  color:var(--ink) !important;
  font-variation-settings:"wdth" 80,"wght" 800;
  text-decoration:underline;text-underline-offset:2px;
}
.confirmEmailAddress{color:var(--ink) !important;font-variation-settings:"wdth" 80,"wght" 800}
.confirmEmailIcon{opacity:1}

.loginPrimary,.loginBtn,.signupBtn{border-radius:0 !important}
.loginGhost,.loginCancel,#loginCancel{
  background:transparent !important;background-image:none !important;
  color:var(--ink) !important;
  border:2px solid var(--rule-hard) !important;border-radius:0 !important;
  font-variation-settings:"wdth" 70,"wght" 900;
  text-transform:uppercase;letter-spacing:.05em;
}
.loginGhost:hover,.loginCancel:hover,#loginCancel:hover{
  background:var(--ink) !important;color:var(--panel) !important;
}
.loginButtons,.authButtons,.forgotLinkRow,.passwordInputWrapper,.passwordWrapper{
  background:transparent !important;
}
.passwordToggle{color:var(--muted) !important;background:transparent !important;border:0 !important}
.passwordToggle:hover{color:var(--ink) !important}
.forgotSuccess{color:#1b5e20 !important}

/* first-run tour: give the primary action the same weight as everywhere else */
#tbStartTour,#tbBtnNext{
  background:var(--ink) !important;color:var(--canary) !important;
  border:2px solid var(--ink) !important;
}
#tbSkipTour,#tbBtnPrev,#tbExitTour{color:var(--muted) !important}

/* ── ROUND 7 — the footer is unreachable on the phone app shell ─────────
   On phones app.html is a fixed full-viewport layout: .fieldCard covers
   64-736, .mobileBench 736-788 and .mobileTabBar 788-844, all position:fixed.
   .siteFooter is the last element in normal flow, so it scrolls UNDERNEATH
   that chrome and can never be tapped — Terms and Privacy hit-tested to
   .mobileTab and then to #frameBar. Padding cannot fix it; there is no
   uncovered space to scroll into.

   This is pre-existing (the fixed phone layout predates the redesign), not
   something the theme introduced. Hiding it on the pages that carry the
   fixed chrome is the honest fix: a dead link is worse than no link, and
   both pages' footers are reachable at every other width and on every other
   page. Desktop is unaffected — the links hit-test correctly there.

   If you would rather surface them inside the mobile UI (a row in the Tools
   panel, or the profile menu), that is a small change — say the word. */
/* Desktop: .board is position:relative while .siteFooter is static, so the
   board wins the paint/hit-test order even below its own box — Terms and
   Privacy were unclickable at every desktop width. Give the footer a
   position of its own so it sits above the app shell. The board's lower
   region is empty space, so nothing is covered. */
.siteFooter{position:relative;z-index:2}
.siteFooter a{position:relative;z-index:3}

@media (max-width:768px){
  body:has(#mobileTabBar) .siteFooter{display:none !important}
}

/* Everywhere the footer IS reachable, give the links a thumb-sized target. */
@media (max-width:768px){
  .siteFooter a{display:inline-block;padding:10px 8px}
}

/* ═══════════════════════════════════════════════════════════════════════
   ROUND 8 — the frame switcher
   The dots were 6px circles carrying no label; which frame you were on was
   a 1.3x scale difference between two tiny dots, and the number existed
   only in a title tooltip. script.js already renders one .frameDot per
   frame in order, so a CSS counter can number them without touching JS.
   ═══════════════════════════════════════════════════════════════════════ */

.frameDots{
  counter-reset:frameno;
  gap:4px !important;
  flex-wrap:wrap;
  max-width:min(52vw,420px);
}

.frameDots .frameDot{
  counter-increment:frameno;
  width:auto !important;
  min-width:24px;
  height:24px !important;
  padding:0 6px;
  border-radius:0 !important;
  border:2px solid var(--rule-hard) !important;
  background:var(--panel) !important;
  color:var(--muted) !important;
  display:flex;
  align-items:center;
  justify-content:center;
  font-variation-settings:"wdth" 70,"wght" 900;
  font-variant-numeric:tabular-nums;
  font-size:11px;
  line-height:1;
  transform:none !important;
  transition:background .13s ease,color .13s ease;
}
.frameDots .frameDot::before{content:counter(frameno)}

.frameDots .frameDot:hover:not(.active){
  background:var(--ink) !important;
  color:var(--panel) !important;
}

/* The current frame is unmistakable: canary fill, ink number, heavier edge. */
.frameDots .frameDot.active{
  background:var(--canary) !important;
  color:var(--ink) !important;
  border-color:var(--ink) !important;
  outline:2px solid var(--ink);
  outline-offset:1px;
  animation:frameLand .24s cubic-bezier(.16,1,.3,1);
}

/* The ink-drop landing, same gesture the landing page uses — the chip
   settles onto the bar rather than blinking. Transform and opacity only:
   nothing here can touch a measured dimension. */
@keyframes frameLand{
  from{transform:translateY(-3px) scale(.84);opacity:.35}
  to  {transform:none;scale:1;opacity:1}
}

/* Adding a frame should read as a new sheet arriving on the stack. The add
   button is the thing you pressed, so it acknowledges the press. */
.frameBarAdd:active{
  background:var(--canary) !important;
  color:var(--ink) !important;
  transform:scale(.92);
}
.frameBarAdd{transition:background .13s ease,color .13s ease,transform .1s ease}

/* The frame counter in the field header carries the same weight as the
   chips, so the two never disagree at a glance. */
.fieldFrameLabel{
  border:2px solid var(--rule-hard);
  padding:2px 7px;
  font-size:10px;
  letter-spacing:.09em;
}

@media (prefers-reduced-motion:reduce){
  .frameDots .frameDot.active{animation:none}
  .frameBarAdd:active{transform:none}
}

/* ═══════════════════════════════════════════════════════════════════════
   ROUND 9 — settings.html's signed-in subscription panel
   None of this renders logged out, so every audit so far walked straight
   past it. .statusValue is `color: white` — invisible on paper — the
   labels are #94a3b8, and the ghost button is a #1e293b slab with #94a3b8
   lettering. Rebuilt as a ruled statement: label left, value right, a
   hairline between rows, and one ink button.
   ═══════════════════════════════════════════════════════════════════════ */

/* Rows read like a printed statement rather than floating pairs. */
.statusRow{
  padding:11px 0;
  border-bottom:1px solid var(--rule);
  align-items:baseline;
}
.statusRow:last-of-type{border-bottom:2px solid var(--rule-hard)}

.statusLabel{
  color:var(--muted) !important;
  font-variation-settings:"wdth" 74,"wght" 700;
  text-transform:uppercase;
  letter-spacing:.085em;
  font-size:11px !important;
}

.statusValue{
  color:var(--text) !important;
  -webkit-text-fill-color:var(--text);
  font-variation-settings:"wdth" 70,"wght" 900;
  font-variant-numeric:tabular-nums;
  text-transform:uppercase;
  letter-spacing:.02em;
  font-size:13px !important;
}
/* Semantic values keep their meaning, restated for a light ground. */
.statusValue.green{color:#1b5e20 !important;-webkit-text-fill-color:#1b5e20}
.statusValue.red  {color:#9b1c1c !important;-webkit-text-fill-color:#9b1c1c}
.statusValue.amber{color:#8a5300 !important;-webkit-text-fill-color:#8a5300}

/* Plan badges: the bill's ticket vocabulary, square and ruled. */
.proBadge,.freeBadge{
  background-image:none !important;
  border-radius:0 !important;
  border:2px solid var(--ink) !important;
  font-variation-settings:"wdth" 70,"wght" 900;
  text-transform:uppercase;
  letter-spacing:.06em;
  padding:5px 11px !important;
}
.proBadge{background:var(--canary) !important;color:var(--ink) !important}
.freeBadge{background:var(--panel-3) !important;color:var(--ink) !important}

/* Buttons follow the same two shapes used everywhere else. */
.settingsBtn{
  border-radius:0 !important;
  font-variation-settings:"wdth" 70,"wght" 900;
  text-transform:uppercase;
  letter-spacing:.05em;
  transition:background .13s ease,color .13s ease;
}
.settingsBtn.primary{
  background:var(--ink) !important;background-image:none !important;
  color:var(--canary) !important;
  border:2px solid var(--ink) !important;
}
.settingsBtn.primary:hover{
  background:var(--canary) !important;color:var(--ink) !important;
  opacity:1 !important;transform:none !important;
}
.settingsBtn.ghost{
  background:transparent !important;background-image:none !important;
  color:var(--ink) !important;
  border:2px solid var(--rule-hard) !important;
}
.settingsBtn.ghost:hover{background:var(--ink) !important;color:var(--panel) !important}

/* Usage meter: an ink bar in a ruled trough, no gradient. */
.usageBar{
  background:var(--panel-4) !important;
  border:2px solid var(--rule-hard) !important;
  border-radius:0 !important;
  height:10px;
}
.usageFill{
  background:var(--ink) !important;background-image:none !important;
  border-radius:0 !important;
}
.usageFill.full{background:#9b1c1c !important}
.usageLabel{
  color:var(--muted) !important;
  font-variation-settings:"wdth" 74,"wght" 700;
  text-transform:uppercase;letter-spacing:.08em;font-size:11px !important;
}
.usageLabel span:last-child{
  color:var(--text) !important;
  font-variant-numeric:tabular-nums;
  font-variation-settings:"wdth" 70,"wght" 900;
}

/* A pending cancellation is a notice, so it wears the canary. */
.cancelNotice{
  background:var(--canary) !important;
  border:2px solid var(--ink) !important;
  border-radius:0 !important;
  color:var(--ink) !important;
}
