/* ═══════════════════════════════════════════════════════════════════════
   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;
}
/* The compact draw-tool buttons carry the widest labels in the narrowest
   boxes, so they take the uppercase treatment above at a tighter tracking and
   with less side padding. Without this the tracking alone costs "Select"
   several pixels it does not have. */
.toolBtn--compact{
  padding:7px 8px !important;
  letter-spacing:.02em;
}

.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` is NOT in this list: it is the full-screen overlay (styles.css
   :4708 — fixed, 100%x100%), not a card. Papering it made the whole
   viewport opaque cream and drew a 3px ink border around the entire
   page, so the share and upgrade dialogs read as a navigation rather
   than as something layered over the board. The card inside every one
   of them is .modalCard, which is covered here. */
.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;
}
.modal,#modalOverlay,.modalOverlay,.fieldExpandBackdrop,.deleteModalOverlay,
.mobilePanelBackdrop{
  background:rgba(11,59,30,.42) !important;
  border:0 !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;
}

/* The profile dropdown got the typography above and nothing else, so its
   COLOURS were still the old dark theme's: #e5e7eb text meant for a dark
   sheet, and a #1f2937 hover. On cream, hovering an item dropped a near-black
   bar under near-white text — you could no longer read the thing you were
   pointing at.
   Hover matches the export menu's settled convention (canary ground, ink
   text) rather than inverting to dark, so the highlight reads as a highlight. */
.profileMenuItem{color:var(--ink) !important}
.profileMenuItem:hover{
  background:var(--canary) !important;
  color:var(--ink) !important;
}
.profileMenuItem.danger{color:#b3261e !important}
.profileMenuItem.danger:hover{
  background:#b3261e !important;
  color:var(--panel) !important;
}
.profileDivider{background:var(--rule-hard) !important;opacity:.28}
.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{
  background:var(--panel-2) !important;
  border:2px dashed var(--rule) !important;
  color:var(--muted);
}
/* .analysisLoader is NOT in that group — see ROUND 8. It is a fixed,
   full-screen overlay, so a dashed "empty state" border was being drawn
   around the entire viewport. */
.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}

/* The expanded "Tactical Notes" dialog has its own toolbar — .notesModalToolbar,
   a different class from the sidebar's .notesToolbar above — and the repaint
   never covered it. It kept styles.css's `background:#020617`, so opening the
   dialog on a cream page showed a near-black strip left over from the old dark
   theme. Same omission as #notesEditor: a selector the theme did not know to
   look for. */
.notesModalToolbar{
  background:var(--panel-3) !important;
  border-bottom:2px solid var(--rule-hard) !important;
}
.notesModalToolbar *{color:var(--ink) !important}

/* The notes editor on the board is #notesEditor — an ID, not a class. Every
   `.notesEditor` rule in this file (the input styling above, the placeholder
   colour below) therefore missed it, and it kept styles.css's dark-theme
   `color:white`. On this cream panel that is white on near-white: the notes
   were being typed into an invisible box. */
#notesEditor{color:var(--ink) !important}
#notesEditor:empty::before{color:var(--muted-2) !important;opacity:1 !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;
}

/* ═══════════════════════════════════════════════════════════════════════
   ROUND 6 — the components the first five rounds never reached
   Every one of these was invisible or near-invisible on paper, because
   styles.css tuned them against the old near-black ground and nothing
   here had claimed them yet.

   The shared root cause: `.modalCard{color:white}` (styles.css:4731).
   Round 2 repainted the card's background to paper but left the
   inherited white text, so every descendant that never set its own
   colour — the upgrade title, the price, the feature list — went to
   1.11:1. Fixing the inheritance at the card is what actually closes
   this class of bug; the rules below then only handle the descendants
   that DO set a colour of their own.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── the inheritance fix, first ────────────────────────────────────── */
.modalCard,.upgradeCard,.deleteModalCard,.loginModal{color:var(--text) !important}

/* .notesModal / .upgradeModal / .shareModal in the round-2 list are ids
   in the markup, not classes, so those three selectors never matched
   anything. Share and upgrade were carried by .modalCard on their inner
   card; the notes dialog's card is .notesModalCard and had nothing but
   `background:var(--panel,#161b22)` holding it up — paper by token, with
   a 1px white border that is invisible on paper. Now that .modal paints
   a scrim instead of a sheet, it needs the real rule. */
.notesModalCard{
  background:var(--panel) !important;
  border:3px solid var(--ink) !important;
  border-radius:0 !important;
  color:var(--text) !important;
}

/* ── 1. upgrade / paywall sheet ────────────────────────────────────── */
.upgradeTitle{
  color:var(--text) !important;
  font-variation-settings:"wdth" 66,"wght" 900;
  text-transform:uppercase;letter-spacing:-.01em;
}
.upgradeSubtitle{color:var(--muted) !important}
/* The price is the one number on the sheet, so it gets the billing
   treatment rather than a plain bold — same move as the pricing row on
   the homepage bill. */
.upgradePrice{
  color:var(--ink) !important;
  font-variation-settings:"wdth" 62,"wght" 900;
  letter-spacing:-.02em;
}
.upgradePrice span{color:var(--muted-2) !important}
.upgradeFeatures li{color:var(--text) !important}
/* ── the plan picker ───────────────────────────────────────────────
   styles.css gives these rounded corners, a blue accent and a grey
   note line. The bill has no radii and one accent — ink on canary —
   so the rows butt together into a single ruled block the way the
   ticket columns do on the homepage, and the chosen plan is the one
   printed on card stock. */
.planPicker{gap:0 !important}
.planOption{
  border:2px solid var(--rule) !important;border-radius:0 !important;
  background:transparent !important;color:var(--text) !important;
}
.planOption + .planOption{border-top:0 !important}
.planOption:hover{border-color:var(--ink) !important;background:transparent !important}
.planOption.selected{
  border-color:var(--ink) !important;
  background:var(--canary) !important;
  color:var(--ink) !important;
}
.planRadio{accent-color:var(--ink) !important}
.planRadio:focus-visible{outline:2px solid var(--ink) !important;outline-offset:3px}
.planName{
  font-variation-settings:"wdth" 70,"wght" 900;
  text-transform:uppercase;letter-spacing:.02em;
}
.planNote{color:var(--muted) !important}
.planOption.selected .planNote{color:var(--muted-2) !important}
.planAmount{
  font-variation-settings:"wdth" 66,"wght" 900;
  letter-spacing:-.02em;
}
.planPer{color:var(--muted) !important}
.planOption.selected .planPer{color:var(--muted-2) !important}
/* Ink on canary would vanish once the row it sits on is also canary,
   so the badge keeps the reversed pairing in both states. */
.planBadge{
  background:var(--ink) !important;color:var(--canary) !important;
  border-radius:0 !important;
  font-variation-settings:"wdth" 80,"wght" 900;
  letter-spacing:.06em;
}
/* styles.css ships a blue-violet gradient here; the bill has no
   gradients, and the CTA is the one thing wearing solid ink. */
.upgradeBtn{
  background:var(--ink) !important;background-image:none !important;
  color:var(--canary) !important;
  border:2px solid var(--ink) !important;border-radius:0 !important;
  font-variation-settings:"wdth" 70,"wght" 900;
  text-transform:uppercase;letter-spacing:.05em;
}
.upgradeBtn:hover{background:var(--bg-bottom) !important;color:var(--ink) !important;transform:none}
.cancelBtn{
  background:transparent !important;
  color:var(--muted) !important;
  border:2px solid var(--rule) !important;border-radius:0 !important;
}
.cancelBtn:hover{color:var(--ink) !important;border-color:var(--rule-hard) !important}

/* ── 2. save menu ──────────────────────────────────────────────────── */
/* The items are bare <div>s with no class of their own (app.html:406),
   so #saveMenu div is the only handle — and an id beats any class here,
   which is why round 4's .saveMenu background fix never reached them. */
#saveMenu{
  background:var(--panel) !important;
  border:2px solid var(--ink) !important;
  border-radius:0 !important;
  box-shadow:none !important;
}
#saveMenu div{
  color:var(--text) !important;
  font-variation-settings:"wdth" 78,"wght" 700;
  text-transform:uppercase;letter-spacing:.05em;font-size:.6875rem !important;
}
#saveMenu div:hover{background:var(--canary) !important;color:var(--ink) !important}
#saveMenu div:first-child{border-bottom:2px solid var(--rule) !important}
.exportItem{color:var(--text) !important}
.exportItem:hover{background:var(--canary) !important;color:var(--ink) !important}

/* ── 3. share sheet ────────────────────────────────────────────────── */
.shareModalTitle{
  color:var(--text) !important;
  font-variation-settings:"wdth" 66,"wght" 900;
  text-transform:uppercase;letter-spacing:-.005em;
}
.shareModalDesc{color:var(--muted) !important}
.closeShareBtn{color:var(--muted) !important}
.closeShareBtn:hover{color:var(--ink) !important}
/* The link field stayed readable at 5.71:1 but kept the old navy chip,
   which read as a hole punched in the paper. Monospace is right here —
   it is a URL, not a costume. */
.shareLinkInput{
  background:#fffdf5 !important;
  border:2px solid var(--rule-hard) !important;border-radius:0 !important;
  color:var(--ink) !important;
}
.copyLinkBtn{
  background:var(--ink) !important;
  color:var(--canary) !important;
  border:2px solid var(--ink) !important;border-radius:0 !important;
  font-variation-settings:"wdth" 74,"wght" 900;
  text-transform:uppercase;letter-spacing:.06em;
}
.copyLinkBtn:hover{background:var(--bg-bottom) !important;color:var(--ink) !important}
.unshareBtn{color:#9b1c1c !important}

/* ── 4. achievement unlock ─────────────────────────────────────────── */
/* achievements.js sets the tier gradient inline on the glow and the
   medal, and the ring as an inline box-shadow, so these need
   !important to land. Tier identity survives in the pill's wording
   ("Club Cup", "Continental Trophy") rather than in metal — the bill
   names the act instead of gilding it. */
.achievementModal{background:rgba(11,59,30,.42) !important;backdrop-filter:none !important}
.achievementModalCard{
  background:var(--panel) !important;background-image:none !important;
  border:3px solid var(--ink) !important;border-radius:0 !important;
  color:var(--text) !important;
  box-shadow:none !important;
}
.achievementModalGlow{display:none !important}
.achievementMedal{
  background:var(--canary) !important;background-image:none !important;
  border-radius:0 !important;
  box-shadow:0 0 0 3px var(--ink) !important;
}
/* The trophy path carries fill="#fff" as a presentation attribute, which
   any CSS fill outranks — no JS change needed. */
.achievementMedal svg path{fill:var(--ink) !important}
.achievementModalEyebrow{
  color:var(--muted-2) !important;
  font-variation-settings:"wdth" 70,"wght" 700;
  text-transform:uppercase;letter-spacing:.10em;
}
.achievementModalTitle{
  color:var(--text) !important;
  font-variation-settings:"wdth" 66,"wght" 900;
  text-transform:uppercase;
}
.achievementTierPill{
  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;letter-spacing:.06em;
}
.achievementModalDesc{color:var(--muted) !important}
.achievementModalBtn{
  background:var(--ink) !important;
  color:var(--canary) !important;
  border:2px solid var(--ink) !important;border-radius:0 !important;
  font-variation-settings:"wdth" 74,"wght" 900;
  text-transform:uppercase;letter-spacing:.06em;
}
.achievementModalBtn:hover{background:var(--bg-bottom) !important;color:var(--ink) !important}

/* ── 5. my_tactics: challenge strip + card date ────────────────────── */
/* A live challenge is the one active thing on the page, so it wears the
   canary — same rule the board's own .challengeBanner already follows.
   The class differs (.challengeBannerStrip, built inline in
   my_tactics.html:208), which is why round 4 missed it. */
.challengeBannerStrip{
  background:var(--canary) !important;background-image:none !important;
  border:2px solid var(--ink) !important;border-radius:0 !important;
  color:var(--ink) !important;
}
.challengeBannerStripText,
.challengeBannerStripText strong{
  color:var(--ink) !important;
  font-variation-settings:"wdth" 74,"wght" 800;
}
.challengeBannerStripText strong{font-variation-settings:"wdth" 70,"wght" 900}
.challengeBannerStripHint{color:rgba(11,59,30,.80) !important}
/* Exhausted is a dead state on a live banner: drop it off the canary
   rather than tinting it, so the strip stops shouting. */
.challengeBannerStripExhausted{color:rgba(11,59,30,.62) !important}

/* rgba(255,255,255,.5) on paper measures 1.3:1 — the saved date was
   effectively unprinted. --muted-2 (.70) is the usual secondary tint but
   it only clears 4.5:1 on --panel; the card meta sits on the darker
   --bg-top ground, where it measures 4.41. --muted (.78) holds on both. */
.tacticMeta,.tactic-date{color:var(--muted) !important}

/* ═══════════════════════════════════════════════════════════════════════
   ROUND 7 — the Analysis panel: readable, and no longer a scroll-in-a-box

   Two problems, one of them structural.

   STRUCTURE: .aiPanel splits its height between Analysis and Notes, both
   `flex:1 1 0` — a hard 50/50 regardless of what either contains. On a
   laptop-height window that left the results area about 110px tall with
   its own scrollbar, so the three key metrics AND the Full Analysis
   button lived below the fold of a box most people never realised could
   scroll. Run Analysis had already been lifted out of that box for the
   same reason (see .analyzeFooter); Full Analysis is now out too, and
   the results area no longer scrolls at all.

   The quick list is only ever three metrics (QUICK_METRIC_KEYS in
   script.js), so the panel's height is bounded and predictable — it can
   size to its content and hand the leftover height to Notes, which is a
   text editor and actually wants it. Scrolling now happens in exactly
   one place: inside the Full Analysis modal, after you ask for it.

   COLOUR: the metric rows, the Full Analysis button and the whole modal
   metrics table were still on the old slate palette (#cbd5e1, #93c5fd,
   #fff) — 1.18:1 to 1.62:1 on paper.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── structure ─────────────────────────────────────────────────────── */
/* No inner scroll: the results area is as tall as its contents. */
.ai-chat{
  overflow:visible !important;
  flex:0 0 auto !important;
}
@media (min-width:769px){
  /* Analysis takes what it needs; Notes absorbs the remainder. Mobile is
     left alone — there .aiPanel is a slide-over showing one section at a
     time (styles.css ≤768px), so a content-height card would leave a
     hole under it. */
  .aiPanel .ai-container{flex:0 0 auto !important}
  .aiPanel .notesPanel{flex:1 1 auto !important;min-height:0}
}

/* The Full Analysis footer, ruled off from the results the same way the
   Run Analysis footer is. */
.analysisDetailsFooter{
  flex:0 0 auto;
  padding:10px 12px 0;
  background:var(--panel) !important;
}
.viewDetailsBtn{
  background:transparent !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:.06em;
}
.viewDetailsBtn:hover{background:var(--canary) !important;color:var(--ink) !important}

/* ── colour: compact panel ─────────────────────────────────────────── */
.metricLine{color:var(--muted) !important}
.metricLine span:last-child{
  color:var(--text) !important;
  font-variant-numeric:tabular-nums;
  font-variation-settings:"wdth" 70,"wght" 900;
}

/* ── colour: full analysis modal ───────────────────────────────────── */
#expandedModalTitle{
  color:var(--text) !important;
  font-variation-settings:"wdth" 66,"wght" 900;
  text-transform:uppercase;letter-spacing:-.01em;
}
.closeModalBtn{color:var(--muted) !important}
.closeModalBtn:hover{color:var(--ink) !important}
/* styles.css gives these a thick blue left tab; the shorthand below
   replaces all four edges with one even rule, which is what kills it. */
.metricRow{
  background:var(--panel-2) !important;
  border:2px solid var(--rule) !important;
  border-radius:0 !important;
}
.metricRowLabel{color:var(--muted) !important}
.metricRowValue{
  color:var(--text) !important;
  font-variant-numeric:tabular-nums;
  font-variation-settings:"wdth" 70,"wght" 900;
}
.modalList li{color:var(--text) !important}
.modalList.positive li::marker{color:#1b5e20}
.modalList.negative li::marker{color:#8a5300}

/* ═══════════════════════════════════════════════════════════════════════
   ROUND 8 — the analysis loading overlay

   Two faults, the same pair as the modals in round 6.

   1. STRUCTURE: .analysisLoader is `position:fixed; inset:0` — an overlay,
      not a panel. Round 4 grouped it with .emptyState, which papered the
      whole viewport opaque and drew a 2px dashed border around the edge of
      the screen. It also hid the board completely: the original was a
      translucent scrim you could see the pitch through, and that intent is
      restored here in the theme's own palette.

   2. COLOUR: the status line is #e6edf3 — near-white, chosen for the old
      near-black ground. On the accidental cream it was invisible, which is
      why the loader looked like a pulsing logo with nothing to say.

   Scrim density is .88 on purpose. The overlay sits over whatever happens
   to be behind it, and the worst case for light text is the cream side
   panel: at .72 the canary measured 4.0:1 against that, under the 4.5 the
   16px status line needs. At .88 it holds regardless of what is beneath.
   ═══════════════════════════════════════════════════════════════════════ */

.analysisLoader{
  background:rgba(11,59,30,.88) !important;
  border:0 !important;
  backdrop-filter:none !important;
  -webkit-backdrop-filter:none !important;
}
.analysisLoaderMsg{
  color:var(--canary) !important;
  font-variation-settings:"wdth" 70,"wght" 900;
  text-transform:uppercase;
  letter-spacing:.05em;
}
.analysisLoaderTime{
  color:rgba(255,255,255,.82) !important;
  font-variation-settings:"wdth" 76,"wght" 600;
}
/* The glow was a blue halo tuned for the dark cockpit; the bill has no
   glows, and on the green scrim it read as a smudge. */
.analysisLoaderLogo{filter:none !important}

/* ═══════════════════════════════════════════════════════════════════════
   ROUND 9 — the "i" tooltips

   Round 5 themed the .infoTip trigger but not the bubble it opens, so the
   help text was still the old near-black chip (#0b1220 / #dbe3ec) with a
   soft drop shadow — the last piece of the dark cockpit left on screen.

   A tooltip has to separate from the paper behind it, and paper-on-paper
   with a rule is too quiet at this size. Solid ink with the page's own
   cream lettering is the bill's answer: it reads as a printed label laid
   on the sheet. Body case, not caps — this is a sentence to read, not a
   control to scan.
   ═══════════════════════════════════════════════════════════════════════ */

.infoTipBubble{
  background:var(--ink) !important;
  border:2px solid var(--ink) !important;
  border-radius:0 !important;
  box-shadow:none !important;
  color:var(--panel) !important;
  font-variation-settings:"wdth" 92,"wght" 500;
}

/* ═══════════════════════════════════════════════════════════════════════
   ROUND 10 — the saved-tactic card and its pitch thumbnail

   Nothing in this card had ever been themed: .tacticCard was a 4%-white
   pane on paper (so, invisible), .tacticPreview a slate gradient, and the
   thumbnail a dark cockpit pitch with neon dots — the last surviving
   screenful of the old app.

   The thumbnail is now a true miniature of the board rather than an
   impression of one. Grass is field.js's own default (#2e7d32) and the
   stripe pair is the one the published formation diagrams already use
   (tools/lib/svg.mjs), so a saved tactic's thumbnail, the board it opens,
   and the /formations page all show the same pitch. The pitch keeps its
   white lines and its colour: it is the one saturated surface in the
   theme, and the card around it is paper.

   The dots keep blue/red/amber, because that is product meaning — team A,
   team B, keeper — not decoration. What goes is the neon: three stacked
   glows per dot, tuned to bloom against near-black, which on grass just
   read as smudges. A hairline white ring does the separating instead,
   which is how tokens are already drawn in the formation SVGs.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── the card: a paper sheet, ruled ────────────────────────────────── */
.tacticCard{
  background:var(--panel) !important;
  border:2px solid var(--rule-hard) !important;
  border-radius:0 !important;
  box-shadow:none !important;
}
/* The bill does not float. Lift is replaced by the canary edge that marks
   the one thing under the cursor. */
.tacticCard:hover{
  transform:none !important;
  box-shadow:none !important;
  background:var(--panel-2) !important;
  border-color:var(--ink) !important;
}
.tacticPreview{
  background:none !important;
  border-radius:0 !important;
}
.tacticInfo{border-top:2px solid var(--rule) !important}
.tacticTitle{
  color:var(--text) !important;
  /* Both axes: font-variation-settings drives the variable font, but
     font-weight is what styles.css set (600) and what any non-variable
     fallback would use. Leaving them disagreeing is how you get a title
     that renders one weight here and another on a cold font load. */
  font-weight:900 !important;
  font-variation-settings:"wdth" 74,"wght" 900;
  /* Not uppercased: this is a name the coach typed, and the bill's caps
     would overwrite their own capitalisation. */
}

/* ── the pitch: the app's real grass, at thumbnail scale ───────────── */
.miniPitch{
  border-radius:0 !important;
  border:2px solid var(--ink) !important;
  /* currentColor drives every marking in PITCH_SVG. */
  color:rgba(255,255,255,.92) !important;
  background:
    repeating-linear-gradient(
      180deg,
      #2f8135 0, #2f8135 12.5%,
      #28712e 12.5%, #28712e 25%
    ) !important;
}

/* ── the tokens: solid, ringed, unglowing ──────────────────────────── */
.miniPlayer{
  box-sizing:border-box;
  border:1.5px solid rgba(255,255,255,.9);
  box-shadow:none !important;
}
.miniPlayer{background:#2563eb !important}
.miniPlayer.teamB{background:#dc2626 !important}
.miniPlayer.gk{background:#f59e0b !important}

/* ── the formation chip: a struck label, not frosted glass ─────────── */
.formationBadge{
  background:var(--ink) !important;
  color:var(--canary) !important;
  border:2px solid var(--ink) !important;
  border-radius:0 !important;
  backdrop-filter:none !important;
  font-variation-settings:"wdth" 74,"wght" 900;
  text-transform:uppercase;letter-spacing:.06em;
}

/* ── the ··· menu ──────────────────────────────────────────────────── */
/* Round 3 swept .cardMenuItem into the button group, which put a 2px box
   on all four sides of every row — a dropdown rendered as a stack of
   buttons. These are list rows: one rule between them, no box. */
.cardMenuItem{
  border:0 !important;
  border-bottom:1px solid var(--rule) !important;
}
.cardMenuItem:last-child{border-bottom:0 !important}
.cardMenuBtn{
  background:var(--panel) !important;
  border:2px solid var(--ink) !important;
  border-radius:0 !important;
  color:var(--ink) !important;
  backdrop-filter:none !important;
}
.cardMenuBtn:hover{background:var(--canary) !important;color:var(--ink) !important}
.cardMenu{
  background:var(--panel) !important;
  border:2px solid var(--ink) !important;
  border-radius:0 !important;
  box-shadow:none !important;
}
.cardMenuItem{color:var(--text) !important}
.cardMenuItem:hover{background:var(--canary) !important;color:var(--ink) !important}

/* ═══════════════════════════════════════════════════════════════════════
   ROUND 11 — the "check your inbox" view was half-converted

   The signup confirmation panel had its TEXT colour fixed by an earlier
   round (.confirmEmailAddress -> var(--ink)) but not its BACKGROUND, which
   styles.css still sets to #1e293b. Bottle-green ink on dark slate is
   invisible: the user's own email address rendered as a solid dark bar,
   which is also the only place that view tells them WHERE the mail went.

   The same trap as the .modalCard note above, in reverse — overriding one
   half of a colour pair is worse than overriding neither, because the
   result reads as a deliberate blank rather than as an unstyled element.

   The address panel is now the same object as the stub in the confirmation
   EMAIL (email-templates/welcome.html): bone card, 2px dashed ink rule,
   square. The two are seen thirty seconds apart and should match.
   ═══════════════════════════════════════════════════════════════════════ */

.confirmEmailAddress{
  background:var(--panel-2) !important;
  background-image:none !important;
  border:2px dashed var(--ink) !important;
  border-radius:0 !important;
  color:var(--ink) !important;
  padding:11px 14px !important;
}

/* Square, ink-ruled, no tint fill. The blue stroke on the envelope is an
   inline presentation attribute in app.html, which any CSS rule outranks. */
.confirmEmailIcon{
  background:var(--panel-2) !important;
  border:2px solid var(--ink) !important;
  border-radius:0 !important;
  width:52px !important;
  height:52px !important;
}
.confirmEmailIcon svg{stroke:var(--ink) !important}

.resendCooldown{color:var(--muted-2) !important}

/* ═══════════════════════════════════════════════════════════════════════
   ROUND 12 — Notes could be crushed out of existence by an analysis

   ROUND 7 gave the Analysis card its content height (`flex:0 0 auto`) and
   told Notes to absorb whatever was left. That is the right split when
   there IS something left. `flex:0 0 auto` also means flex-shrink:0, so on
   a short window every pixel of the squeeze landed on Notes instead — and
   .aiPanel is `height:calc(100vh - 64px)` with `overflow:hidden`, so there
   was no way to scroll to what had been squeezed out.

   Measured at 1280x620 with a result on screen: Analysis 415px, Notes
   crushed to 99px, the editor a 28px sliver whose bottom sat 11px below
   the clipped panel edge. Not "small" — unreachable, and silently so.

   Two rules fix it, and they have to work together:

     · Notes gets a floor. 220px is its own chrome (48px header + 47px
       toolbar) plus ~125px of editor — the point below which the thing
       stops being a text editor.
     · The panel scrolls when Analysis plus that floor won't fit.

   The floor alone would just move the clipping; the scroll alone would
   still let Notes collapse to a sliver before the scrollbar appeared.

   Deliberately NOT capping the Analysis card and scrolling inside it —
   that is the "scroll-in-a-box" ROUND 7 removed on purpose, where the
   metrics and the Full Analysis button lived below the fold of a box
   nobody realised could scroll. Scrolling the panel keeps one scroll
   region in one obvious place.

   Desktop only, matching ROUND 7's guard: under 769px .aiPanel is a
   slide-over showing one section at a time (styles.css), so neither rule
   applies there.
   ═══════════════════════════════════════════════════════════════════════ */

@media (min-width:769px){
  .aiPanel{
    overflow-y:auto !important;
    overscroll-behavior:contain;
  }
  .aiPanel .notesPanel{
    min-height:220px !important;
  }
}

/* ═══════════════════════════════════════════════════════════════════════
   ROUND 13 — the paywall sheet, and the scroll fix it exposed
   (2026-08-22)

   Two jobs in one round.

   (a) THE SCROLL FIX, which is not paywall-specific and repairs every
   dialog on the site. `.modal` (styles.css:4726) is a fixed full-screen
   flex box with align-items:center, no padding and no overflow, and
   `.modalCard` has no max-height. Any card taller than the viewport
   therefore overflows BOTH ends and the overflow is unreachable — on a
   ~590px window the upgrade sheet lost its own title off the top. The
   overlay scrolls, the card is capped, and align-items goes to start so
   a too-tall card grows downward into the scroll instead of centring
   itself half off-screen.

   (b) THE SHEET. Order is status → pitch → what Pro adds → price → CTA
   → reset clock, which is a reordering of what was already there plus
   three new bands. Rationale, in bill grammar:
     · The status strip and the reset band are the SAME component —
       label left, value right, panel-3 ground, ink rule. The sheet
       opens with the allowance spent and closes with it coming back.
     · Canary stays reserved. Only two things wear it: the selected plan
       row (round 6) and the one benefit matching the wall just hit.
       A third canary element and none of them mean anything.
     · The reset clock gets the billing treatment the price gets,
       because it is the other number on the sheet.
   The card goes padding:0 because the bands are full-bleed; every inner
   section carries its own 22px instead.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── (a) every modal on the site ───────────────────────────────────── */
.modal{
  align-items:flex-start !important;
  padding:24px 16px !important;
  overflow-y:auto !important;
  overscroll-behavior:contain;
}
/* Every direct child, not just `.modalCard` — the notes dialog's card is
   `.notesModalCard` and would otherwise be left pinned to the top by the
   align-items change above. `margin:auto` centres it while it fits and
   yields to the scroll once it does not. */
.modal > *{
  margin:auto !important;
  max-height:none !important;
}

/* ── (b) the sheet ─────────────────────────────────────────────────── */
.upgradeCard{
  padding:0 !important;
  text-align:left !important;
  display:flex;flex-direction:column;
  max-width:400px !important;
}

/* 1 — allowance spent */
.upgradeStatus:not(.hidden){display:block}
.upgradeStatus{
  border-bottom:2px solid var(--ink);
  padding:12px 22px 11px;
  background:var(--panel-3);
}
.upgradeStatusRow{
  display:flex;align-items:baseline;justify-content:space-between;gap:12px;
}
.upgradeStatusLabel{
  font-variation-settings:"wdth" 78,"wght" 900;
  text-transform:uppercase;letter-spacing:.1em;font-size:.625rem;
  color:var(--ink);
}
.upgradeStatusCount{
  font-variation-settings:"wdth" 70,"wght" 900;
  font-size:.6875rem;letter-spacing:.04em;
  /* --muted-2 (.70) is tuned against --panel and only reaches 4.48 on the
     darker --panel-3 these two bands use. .74 gives 4.99. */
  color:rgba(11,59,30,.74);
  font-variant-numeric:tabular-nums;white-space:nowrap;
}
.upgradeMeter{
  margin-top:7px;height:6px;background:rgba(11,59,30,.16);
  border:1px solid var(--rule);
}
.upgradeMeterFill{height:100%;width:100%;background:var(--ink)}

/* 2 — the pitch */
.upgradeHead{padding:20px 22px 0}
.upgradeTitle{
  margin:0 !important;
  font-size:1.625rem !important;line-height:.98;
  text-wrap:balance;
}
.upgradeSubtitle{
  margin:10px 0 0 !important;
  font-size:.875rem !important;line-height:1.45;
}

/* 3 — what Pro adds */
.upgradeAdds{padding:18px 22px 0}
.upgradeAddsLabel{
  margin:0 0 7px;
  font-variation-settings:"wdth" 78,"wght" 900;
  text-transform:uppercase;letter-spacing:.11em;font-size:.5625rem;
  /* Measured on --panel: .52 gave 2.98 (fail), .70 gives 4.83. Same
     exercise the token block at the top of this file records. */
  color:rgba(11,59,30,.70) !important;
}
.upgradeFeatures{
  list-style:none;margin:0 !important;padding:0;
  border-top:2px solid var(--ink);
}
.upgradeFeatures li{
  display:flex;align-items:baseline;gap:9px;
  margin:0 !important;padding:7px 0;
  border-bottom:1px solid var(--rule);
  font-size:.8125rem !important;
}
.upgradeAddMark{
  flex:0 0 auto;width:8px;height:8px;background:var(--rule);
  transform:translateY(-1px);
}
.upgradeAddText{
  flex:1 1 auto;line-height:1.3;color:var(--text);
  font-variation-settings:"wdth" 88,"wght" 600;
}
.upgradeAddFree{
  flex:0 0 auto;font-size:.625rem;letter-spacing:.05em;
  font-variation-settings:"wdth" 78,"wght" 700;
  text-transform:uppercase;color:rgba(11,59,30,.70);   /* 4.83 on --panel */
  font-variant-numeric:tabular-nums;white-space:nowrap;
}
/* the wall they actually hit — set by openUpgradeModal(), never in markup */
.upgradeFeatures li.primary{
  background:var(--canary);
  margin:0 -6px !important;padding:9px 6px;
  border-bottom:1px solid var(--ink);
}
.upgradeFeatures li.primary .upgradeAddMark{background:var(--ink);width:9px;height:9px}
.upgradeFeatures li.primary .upgradeAddText{
  font-variation-settings:"wdth" 84,"wght" 900;
  color:var(--ink);font-size:.875rem;
}
/* .72 on canary is 4.48; .78 gives 5.20. */
.upgradeFeatures li.primary .upgradeAddFree{color:rgba(11,59,30,.78)}

/* 4 — plan picker: round 6 already dresses it, this only places it */
.upgradeCard .planPicker{margin:18px 22px 0 !important}

/* 5 — CTA with its terms welded on */
.upgradeFoot{padding:16px 22px 18px}
.upgradeCard .upgradeBtn{margin-bottom:0 !important;padding:13px 12px !important}
.upgradeCard .upgradeBtn:focus-visible{position:relative;z-index:1}
.upgradeTrust{
  display:flex;align-items:center;justify-content:center;gap:7px;
  border:2px solid var(--ink);border-top:0;
  background:var(--panel-2);
  padding:8px 10px;text-align:center;
}
.upgradeTrustMark{
  flex:0 0 auto;width:14px;height:14px;
  background:var(--ink);color:var(--panel);
  display:flex;align-items:center;justify-content:center;
  font-size:.5625rem;line-height:1;font-weight:900;
}
.upgradeTrustText{
  font-size:.6875rem;line-height:1.25;color:var(--ink);
  font-variation-settings:"wdth" 84,"wght" 700;
}
.upgradeTrustText b{
  font-variation-settings:"wdth" 80,"wght" 900;
  text-transform:uppercase;letter-spacing:.04em;
}
.upgradeCard .cancelBtn{margin-top:12px !important;padding:9px !important}

/* 6 — allowance returning */
.upgradeReset:not(.hidden){display:flex}
.upgradeReset{
  align-items:center;justify-content:space-between;gap:12px;
  border-top:2px solid var(--ink);
  background:var(--panel-3);
  padding:11px 22px 12px;
}
.upgradeResetLabel{
  font-variation-settings:"wdth" 78,"wght" 900;
  text-transform:uppercase;letter-spacing:.1em;font-size:.625rem;
  color:rgba(11,59,30,.74);line-height:1.2;   /* 4.99 on --panel-3 */
  /* the label is set with textContent and carries its own 
 break */
  white-space:pre-line;
}
.upgradeResetClock{
  font-variation-settings:"wdth" 64,"wght" 900;
  font-size:1.375rem;letter-spacing:-.01em;line-height:1;
  color:var(--ink);font-variant-numeric:tabular-nums;white-space:nowrap;
}

/* Narrow phones: the sheet is already only 400px, so all that has to give
   is the horizontal breathing room. */
@media (max-width:400px){
  .upgradeStatus,.upgradeHead,.upgradeAdds,.upgradeFoot,.upgradeReset{
    padding-left:15px;padding-right:15px;
  }
  .upgradeCard .planPicker{margin-left:15px !important;margin-right:15px !important}
  .upgradeTitle{font-size:1.375rem !important}
}
