/* ── Colour theme tokens (v208, golf-caddie-color-modes-scope.md) ────────
   Custom properties driving the app's brand chrome (page bg, topbar, card
   surfaces, tab bar, primary buttons/active states, primary/secondary/
   tertiary text) plus the mini hole-schematic's terrain/route colours.
   Defaults below are the Green/Standard palette — i.e. exactly what this
   app has always shipped — so adding this block changes nothing visually
   on its own. applyColorTheme() in app.js overwrites these via
   documentElement.style.setProperty() when the user picks a different
   theme/Sunlight state on the Stats tab's Appearance section; nothing
   else needs to change. Status colours (success/danger), the gold Pro
   badge, the Platinum gradient badge, gated-tier chips, map pin colours,
   and the wind-direction indicator are deliberately NOT tokens here — see
   the Appearance section build notes for the full list of exclusions. */
:root {
  --app-bg: #0b3d20;
  --app-header: #082b16;
  --app-card: #0d2e18;
  --app-card-alt: #103d22;
  --app-text1: #f2f2f2;
  --app-text2: #cdeede;
  --app-text3: #9be7b8;
  --app-accent-fill: #2e8b57;
  --app-accent-ontext: #ffffff;
  --app-accent-text: #f2f2f2;
  /* Mini hole-schematic (getHoleSchematicSvg() in app.js) terrain/route
     colours — NOT part of the theme identity, so these stay fixed for
     Standard across all 3 themes and only change when Sunlight is on. */
  --sc-fairway: #2d7a45;
  --sc-green: #1fbf6e;
  --sc-bunker: #c8b472;
  --sc-tee-marker: #c8a83a;
  --sc-route: rgba(255,255,255,0.5);
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html, body { margin: 0; height: 100%; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: var(--app-bg); color: var(--app-text1); }
body {
  display: flex;
  flex-direction: column;
  /* 100dvh (dynamic viewport height) shrinks automatically when the iOS Safari
     toolbar is visible, so our controls are never hidden beneath it.
     100vh is the fallback for older browsers that don't support dvh. */
  height: 100vh;
  height: 100dvh;
  overflow: hidden;
}

.topbar {
  background: var(--app-header);
  padding: env(safe-area-inset-top, 6px) 12px 6px 12px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  flex-shrink: 0;
  /* 2026-08-26 (Ged: the sticky-header gap bug came back) — the v210 fix
     promoted the 5 STICKY children to their own GPU layer but never did
     the same for this static sibling sitting directly above them. iOS
     Safari doesn't strictly guarantee paint order between a promoted
     scrolling layer and an unpromoted static element with no stacking
     context of its own during fast/momentum scroll, so the scrolling
     content behind it could flash on top for a frame right at the seam —
     same symptom as before, different half of the fix. Giving .topbar its
     own stacking context (z-index above the sticky headers' z-index:5)
     and the same GPU-layer promotion closes that gap. */
  position: relative;
  z-index: 10;
  transform: translateZ(0);
  -webkit-transform: translateZ(0);
  will-change: transform;
}
.topbar h1 { font-size: 1rem; margin: 2px 0; display: flex; align-items: center; gap: 8px; }
.topbar h1 .brand-logo { height: 24px; width: 24px; display: block; flex-shrink: 0; }
.topbar h1 .brand-text { position: relative; top: 3px; }
/* Tier badge (dev scaffold) — only shown once tier is above the base/free
   tier; hidden entirely at "intro" so today's only-tier users see nothing
   new in the header. */
.tier-badge {
  font-size: 0.62rem;
  font-weight: 800;
  letter-spacing: 0.04em;
  padding: 2px 7px;
  border-radius: 20px;
  position: relative;
  top: 2px;
}
.tier-badge-pro  { background: #C9A227; color: #082b16; } /* Gold */
/* Platinum, sheen variant (2026-08-20, Ged: mocked up flat vs. sheen, picked
   sheen) — was flat sage (#8FA396) before the Silver/Gold/Platinum badge-
   colour pass. Extended to the Stats-tab gated-card chips too, see
   .gated-tier-chip-pro/.gated-tier-chip-tour below. .risk-advisory-retarget's
   badge still uses the old flat sage independently (a separate CSS value,
   not derived from this rule) — left alone, out of scope for this pass. */
.tier-badge-tour { background: linear-gradient(135deg, #A2ACB5, #7C858C); color: #082b16; }
/* Active Tour trial (build phase 9, 2026-09-15) — amber, matching the
   Upgrade tab's .upg-tier-cta.trialling and the pin-estimated/risk-prompt
   amber (#e0c069) already used elsewhere for "temporary/in-progress"
   status, rather than the Tour tier's own permanent silver. */
.tier-badge-trial { background: #e0c069; color: #082b16; }
.tabs { display: flex; gap: 6px; }
.tab-btn {
  flex: 1;
  padding: 7px 4px;
  background: var(--app-card-alt);
  border: none;
  border-radius: 8px;
  color: var(--app-text2);
  font-size: 0.9rem;
  font-weight: 600;
}
.tab-btn.active { background: var(--app-accent-fill); color: var(--app-accent-ontext); }

/* 2026-08-25 redesign: this used to show the live club/avg-distance/
   dispersion/confidence recommendation on every tab. That content now
   lives in the Range tab's own club block instead (a Leaflet map control —
   see .club-blk above — populated by updateClubBlock() in app.js), so this
   header element's only remaining job used to be the brief "✓ Logged ..."
   confirmation shown after logging a shot on the Range tab (see
   flashTopbarRec() in app.js). #topbar-rec itself moved OUT of the header
   entirely as of 2026-08-30 — it's now a Leaflet map overlay on the Range
   tab, styled alongside .range-panel below (search there for the real
   rule) — this element genuinely has nothing left to do inside the header,
   same pattern as the very first #topbar-rec-in-the-header-banner removal
   noted elsewhere in this file. */

/* Recommendation is now shown in the Range tab's own club block — hide the old readout slot */
#suggested-club { display: none; }

main { flex: 1; overflow: hidden; position: relative; }

/* overscroll-behavior-y:contain (2026-08-26, same fix as .topbar above) —
   stops iOS elastic rubber-band bounce inside this scroll container, which
   is almost certainly the specific "when you scroll" trigger that exposes
   the compositing mis-paint at the .topbar seam; containing it removes the
   condition, not just the symptom. */
.tab-panel { display: none; height: 100%; overflow-y: auto; overscroll-behavior-y: contain; }
.tab-panel.active { display: flex; flex-direction: column; }

#tab-range { padding: 0; }
#map { flex: 1; min-height: 0; }

.range-readout {
  padding: 5px 14px;
  /* Retokenized 2026-08-26 (Ged, colour-consistency pass) — was hardcoded
     green regardless of theme; now the same colour the logo/header bar
     already sits on. */
  background: var(--app-header);
  flex-shrink: 0;
}
.distance-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding: 2px 0;
}
/* Explicit accentText token (2026-08-25, colour themes) — this used to just
   inherit body's color:#f2f2f2, which is exactly --app-accent-text's Green/
   Standard default, so this adds no visual change today. Declaring it
   explicitly lets the Range tab's primary "distance to target" number pick
   up each theme's own accent-text colour instead of staying tied to the
   page's plain body-text colour like everything else. */
.distance-row.main { font-size: 2rem; font-weight: 700; color: var(--app-accent-text); }
.distance-row:not(.main) { font-size: 1.15rem; color: var(--app-text2); }
#row-to-target, #row-remaining { font-size: 1.5rem; }
.distance-row .unit { font-size: 0.8em; font-weight: 400; opacity: 0.7; margin-left: 3px; }
.distance-row.hidden { display: none; }
.distance-label { font-size: 0.7em; font-weight: 400; opacity: 0.75; text-transform: uppercase; letter-spacing: 0.04em; }
.suggestion { font-size: 0.85rem; color: var(--app-text3); text-align: left; margin-top: 2px; }
.rec-club { font-weight: 700; color: #fff; }
.rec-detail { font-size: 0.8em; opacity: 0.8; margin-left: 4px; }
.rec-conf { font-size: 0.8em; color: #ffeb3b; margin-left: 4px; font-weight: 600; white-space: nowrap; letter-spacing: -0.5px; }

/* Topbar confidence, progress-bar treatment (v135) — same visual language as
   .prog-bar/.prog-fill on the Stats tab, sized for the topbar's larger hero
   text. Ged's call after reviewing a mockup: reuse the bar in the topbar for
   Pro/Tour (fills to the confidence %) and for entry tier (fills with shot-
   count maturity, no % shown), replacing the plain-text .rec-conf badge. */
.rec-prog-wrap { display: inline-flex; align-items: center; gap: 5px; vertical-align: middle; margin-left: 4px; }
.rec-prog-bar { width: 34px; height: 7px; background: rgba(255,255,255,0.16); border-radius: 3.5px; overflow: hidden; flex-shrink: 0; }
.rec-prog-fill { display: block; height: 100%; border-radius: 3.5px; background: var(--app-accent-fill); transition: width 0.4s; }
.rec-prog-fill.full { background: #1fbf6e; }
.rec-prog-pct { font-size: 0.62em; font-weight: 700; color: #ffeb3b; white-space: nowrap; }
.rec-alt { display: block; font-size: 0.75em; opacity: 0.65; margin-top: 1px; }

.mode-controls { padding-top: 8px; }
.mode-btn {
  flex: 1;
  padding: 10px 6px;
  border: 2px solid transparent;
  border-radius: 10px;
  background: var(--app-card-alt);
  color: var(--app-text2);
  font-size: 0.85rem;
  font-weight: 600;
}
.mode-btn.active { border-color: #ffeb3b; background: #1d6b3a; color: white; }

.controls {
  display: flex;
  gap: 6px;
  padding: 6px 12px;
  /* Retokenized 2026-08-26 — see .range-readout above. */
  background: var(--app-header);
  flex-shrink: 0;
}
.controls button {
  flex: 1;
  padding: 10px 4px;
  border: none;
  border-radius: 10px;
  background: var(--app-accent-fill);
  color: var(--app-accent-ontext);
  font-size: 0.8rem;
  font-weight: 600;
}
.controls button:disabled { opacity: 0.4; }
/* Icon + label buttons (search, overlay toggle, mark, log shot) — center the
   inline SVG against the text rather than relying on default inline baseline
   alignment, and give it a small gap so the glyph doesn't touch the label. */
#btn-search-course, #btn-toggle-overlay, #btn-range-mark, #btn-log-shot {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
}
#btn-search-course svg, #btn-toggle-overlay svg, #btn-range-mark svg, #btn-log-shot svg {
  flex-shrink: 0;
}
#btn-clear { background: #5a5a5a; }
#btn-toggle-tracking.tracking { background: #b33a2a; }
/* Range tab Mark button — 2026-08-26 (Ged, colour-consistency pass): this
   was the one drawer button with its own hardcoded green instead of the
   shared button default, which is why it stayed green while Live Dist
   (plain .controls button, no override) already went blue under Logo
   Navy. Dropping the override entirely makes it match Live Dist exactly
   at rest. Marked/pressed state also moves off its own hardcoded green —
   background now matches the search field, border/glow now a neutral
   accent instead of green. */
#btn-range-mark.marked {
  background: var(--app-card-alt);
  border: 1px solid var(--app-accent-fill);
  color: var(--app-text1);
  box-shadow: 0 0 0 2px rgba(255,255,255,0.22);
}

.status-line {
  /* Add bottom safe-area inset so the status text isn't hidden behind the
     iPhone home indicator when the app is in fullscreen / standalone mode. */
  padding: 3px 12px calc(6px + env(safe-area-inset-bottom, 0px));
  font-size: 0.72rem;
  /* Retokenized 2026-08-26 — was a fixed sage (#a8c9b6); now white/near-
     white in every theme. */
  color: var(--app-text1);
  background: var(--app-header);
  flex-shrink: 0;
}

/* "Know this course? Help map it →" (v237) — appended inside #course-status
   / #search-status only for the "no mapped course found" message, via
   drawCourseFeatures() in app.js. Block-level so it reads as its own line
   under the status text, matching the approved mockup
   (https://claude.ai/code/artifact/6864b171-b28e-496e-9721-cd9a94403de9)
   rather than running on inline. */
.status-help-link { display: block; margin-top: 7px; font-size: 0.79rem; font-weight: 700; color: var(--app-text3); text-decoration: underline; text-underline-offset: 2px; }

/* ── Range tab floating Search + Controls ─────────────────────────
   Replaces the old drag-handle drawer (2026-08-26, Ged's redesign request —
   mocked up and approved at
   https://claude.ai/code/artifact/b8328a57-5005-4137-ac3e-1401b49389cd).
   Two floating icons sit bottom-right as a real Leaflet control (see
   RangeFloatingIconsControl in app.js) — same visual family the Hole/
   Weather/Club/Locate corner blocks already use, just anchored to the
   opposite corner. Each icon toggles its own panel; panels are appended
   directly to the map container (see buildRangePanel() in app.js) rather
   than living inside the Leaflet control itself, so they can span nearly
   the map's full width instead of being boxed into the bottomright
   corner's own content size.

   Icon size 52px, settled during the initial mockup review. Icon
   background is Translucent at 60% opacity, no blur (v216 — dialled in
   across a 40/60/80% comparison). Panel shells (Search/Controls) are
   Frosted glass at 70% opacity WITH blur (2026-08-26, v217 — Ged tried
   Translucent for the shells in v216, then asked for Frosted at 60%, then
   bumped to 70% after seeing it — more solid, still has the glass
   quality). Buttons and inputs *inside* the panels stay fully Opaque (see
   the panel-content rules further down) — so the shell reads as glass
   while whatever you're actually reading/tapping stays fully legible.
   Every color-mix() background below has a solid fallback declared first,
   so anything that doesn't understand color-mix() still gets a fully
   legible solid panel instead of nothing. Panels stop at right:72px
   rather than sharing the icons' right:14px inset — a deliberate
   departure from the mockup's own CSS, which had the open Controls
   panel's higher z-index fully covering the icon column; reserving that
   width instead keeps both icons reachable (to switch panels or close the
   open one) no matter which panel is open. */
.leaflet-control.range-fic-stack {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-bottom: calc(10px + env(safe-area-inset-bottom, 0px));
}
.range-fic {
  width: 52px;
  height: 52px;
  border-radius: 16px;
  border: 2px solid var(--app-text3);
  background: var(--app-header);
  background: color-mix(in srgb, var(--app-header) 60%, transparent);
  box-shadow: 0 6px 16px -6px rgba(0,0,0,0.65);
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  padding: 0;
  cursor: pointer;
}
.range-fic svg { display: block; }
/* Panel open — solid accent fill, same "this is the active one" language
   the old drawer's brass-ring handle used to signal expanded state. */
.range-fic.fic-active {
  background: var(--app-accent-fill);
  border-color: var(--app-accent-fill);
}
/* "Something worth checking" indicator on the Controls icon — a brass edge,
   swapped in during mockup review for an earlier red-dot idea. Reuses the
   app's existing gold Pro-tier colour (#C9A227, .tier-badge-pro above)
   rather than a new one-off hex. Lit by updateRangeControlsBadge() in
   app.js whenever the panel is closed and Log Shot would be enabled if you
   opened it. Static/steady — deliberately a different signal from
   .fic-busy below (something's *ready*, vs something's *in progress*). */
.range-fic.fic-pending {
  border-color: #C9A227;
  box-shadow: 0 6px 16px -6px rgba(0,0,0,0.65), 0 0 0 2px rgba(201,162,39,0.4);
}
/* "Something's happening" pulse (2026-08-26, v217) — replaces the v216
   independent top banner. Ged: "the banner... its always there which is
   annoying" — and asked that, while a panel is closed, a real in-flight
   event (search fetch, GPS burst, wind fetch) pulse that icon's edge
   instead of a banner floating over the map. Toggled by
   updateStatusPulses() in app.js, driven by isSearchBusyNow()/
   isControlsBusyNow() — never by the idle hint, only genuine "please
   wait" operations. A continuous soft green ring, deliberately a
   different colour/motion from the brass .fic-pending ring above, since
   both can be true on the Controls icon at once (GPS locating AND a
   marked distance already sitting ready to log).
   Upgraded to a brighter "fast double-ring" (2026-08-27) — Ged: the single
   soft ring wasn't noticeable enough on the Search icon specifically while
   a search was running. Mocked up 3 louder options (bigger/brighter single
   ring, ring + a small icon scale-nudge, this fast double-ring); Ged picked
   the double-ring. Brighter green (#6de39a vs the old #2e8b57-derived
   rgba) and a lit border-color to match, since both icons share this one
   class the change applies identically to Search and Controls busy. */
.range-fic.fic-busy { border-color: #6de39a; animation: fic-busy-pulse 1.3s ease-out infinite; }
@keyframes fic-busy-pulse {
  0%   { box-shadow: 0 6px 16px -6px rgba(0,0,0,0.65), 0 0 0 0 rgba(58,196,120,0.9), 0 0 0 0 rgba(58,196,120,0.5); }
  45%  { box-shadow: 0 6px 16px -6px rgba(0,0,0,0.65), 0 0 0 6px rgba(58,196,120,0), 0 0 0 3px rgba(58,196,120,0.55); }
  100% { box-shadow: 0 6px 16px -6px rgba(0,0,0,0.65), 0 0 0 6px rgba(58,196,120,0), 0 0 0 13px rgba(58,196,120,0); }
}
@media (prefers-reduced-motion: reduce) {
  .range-fic.fic-busy { animation: none; box-shadow: 0 6px 16px -6px rgba(0,0,0,0.65), 0 0 0 3px rgba(58,196,120,0.6); }
}

/* Shot-logged confirmation (2026-08-30) — moved here from the header
   (search this file for "#topbar-rec itself moved OUT of the header" for
   the pointer). Ged, on the header version shipped 2026-08-29: "I noticed
   when the logged shot infomation looks no different as the banner back
   ground is opaque" — correct: .topbar has always painted a flat solid
   colour (deliberately, for iOS compositing reasons documented elsewhere
   in this file), so backdrop-filter had nothing behind it to actually
   blur. Floating over the map instead — the same trick every other
   frosted surface in this app already relies on — gives it real satellite
   imagery to blur, same as .range-panel below. Mocked up at
   https://claude.ai/code/artifact/b3379885-da41-4241-8c8f-dc04934dd972 and
   approved (move to the map, accepting that it now only ever shows on the
   Range tab, disappears on a tab switch mid-display, and sits outside the
   double-tap-to-zoom readability boost — that feature deliberately skips
   the map, see the body.ui-zoomed comment further down). Positioned
   top-anchored (the only top-anchored panel on this map — everything else
   sits at the bottom) with a 96px left inset to clear the top-left corner
   stack (Hole/Weather/Club/Locate, each 68px + Leaflet's own ~10px
   margin) — right stays a plain 14px since nothing occupies the top-right.
   No safe-area-inset-top needed here (unlike .range-panel's bottom offset)
   since the map's own top edge already sits below the header, nowhere near
   the physical notch. Frosting/type/duration all carried over unchanged
   from the header version — only the element's home and position changed:
   still 60% color-mix, still the Option C "accent-led inline" type
   (.topbar-rec-hero below), still a 4.4s display (flashTopbarRec() in
   app.js). Built and appended to the map container in app.js exactly like
   .range-panel is (see buildRangePanel()) — index.html no longer has a
   static #topbar-rec element at all. */
#topbar-rec {
  position: absolute;
  top: 10px;
  left: 96px;
  right: 14px;
  z-index: 1000;
  padding: 9px 14px 10px;
  border-radius: 18px;
  border: 2px solid var(--app-text3);
  background: var(--app-header);
  background: color-mix(in srgb, var(--app-header) 60%, transparent);
  backdrop-filter: blur(11px) saturate(150%);
  -webkit-backdrop-filter: blur(11px) saturate(150%);
  box-shadow: 0 6px 16px -6px rgba(0,0,0,0.65);
  text-align: center;
  font-size: 0.86rem;
  font-weight: 500;
  color: var(--app-text2);
  text-shadow: 0 1px 3px rgba(0,0,0,0.75);
  line-height: 1.45;
}
#topbar-rec:empty { display: none; }
/* The one number worth reading mid-swing (the logged distance itself) —
   set apart from the rest of the confirmation the same way Option C's
   mockup did: larger, bolder, brightest text token. Built as a real <span>
   by flashTopbarRec() rather than string-concatenated markup, so club
   names (user-entered text) never pass through innerHTML. */
.topbar-rec-hero {
  font-size: 1.22rem;
  font-weight: 700;
  color: var(--app-text1);
}

.range-panel {
  position: absolute;
  bottom: calc(10px + env(safe-area-inset-bottom, 0px));
  left: 14px;
  right: 72px;
  z-index: 1000;
  border-radius: 18px;
  border: 2px solid var(--app-text3);
  background: var(--app-header);
  background: color-mix(in srgb, var(--app-header) 70%, transparent);
  backdrop-filter: blur(11px) saturate(150%);
  -webkit-backdrop-filter: blur(11px) saturate(150%);
  box-shadow: 0 6px 16px -6px rgba(0,0,0,0.65);
  max-height: calc(100% - 20px - env(safe-area-inset-bottom, 0px));
  overflow-y: auto;
  padding: 6px 0;
}

/* Rows nested inside a floating panel go transparent — the panel itself
   already supplies the frosted surface, so each row's old solid
   var(--app-header) strip (the previous drawer's look) would just paint
   over it rather than showing it through. Buttons/inputs/results are
   deliberately NOT overridden here (2026-08-26 v216, still true) — Ged
   asked for those to stay fully Opaque while the shell goes Frosted, so
   they just fall back to each element's normal app-wide solid styling
   (#btn-clear, .controls button, select/input, .search-result-item,
   etc. — all defined elsewhere and already solid). #course-status
   (Controls) and #search-status (Search) are back on this list as of
   v217 — see the courseStatusEl comment in app.js for why they left in
   v216 and came back here now that the top banner is gone. */
.range-panel .controls,
.range-panel .status-line,
.range-panel .pin-set-bar,
.range-panel .range-quick-log,
.range-panel .search-results-list {
  background: transparent;
}
/* Text sitting directly on the frosted shell (not inside an opaque
   button) still gets a shadow for legibility over a bright map area. */
.range-panel .status-line,
.range-panel .status-help-link,
.range-panel .pin-set-bar span,
.range-panel .quick-log-label {
  text-shadow: 0 1px 3px rgba(0,0,0,0.75);
}
/* .status-line's own bottom padding used to add the iPhone safe-area inset
   itself, back when it was the last, bottom-most element in the old
   drawer. .range-panel now handles that inset once, at the whole panel's
   own `bottom` offset — so status-line adding it a second time would just
   pad out extra empty space under it. Reset to a plain, non-safe-area 6px
   to match its own top padding. Covers both #course-status and the new
   #search-status, since both carry the shared .status-line class. */
.range-panel .status-line { padding-bottom: 6px; }

/* Map / Mark / Live Dist / Clear — icon-over-label layout (2026-08-26
   v216, mocked up as three options — 1×4 row, 2×2 grid, icon-over-label —
   Ged: "I prefer the Map/mark/Live Dist/Clear layout with the
   Icon-over-label"). At ~46px wide per button in a plain 1x4 row, an
   inline icon + a 9-character label like "Live Dist" couldn't read
   comfortably at any reasonable font size — stacking the label under the
   icon gives the text its own full-width line instead of splitting that
   46px with a glyph. #range-controls-row (id) + button (type) outweighs
   both the plain `.controls button` rule and the single-id
   `#btn-toggle-overlay`/`#btn-range-mark` rules it needs to override. */
#range-controls-row button {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 3px;
  padding: 9px 2px 8px;
}
#range-controls-row .btn-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  font-size: 1.05rem; /* readable size for the emoji icons (📍, ✕) */
}
#range-controls-row .btn-icon svg { display: block; }
#range-controls-row .btn-label {
  font-size: 0.62rem;
  line-height: 1.15;
  text-align: center;
  font-weight: 700;
  letter-spacing: 0.01em;
}
/* Live Dist / Log Shot when disabled (2026-08-26, v217) — Ged: "the log
   shot / live dist should be fixed (solid muted)". Both buttons used the
   app's global disabled style, `.controls button:disabled { opacity: 0.4 }`
   (style.css, near the top) — a flat alpha fade on the whole button. That
   read fine against the old drawer's solid background; now that Map/Mark/
   Clear sit next to it fully Opaque (see the panel-content comment above),
   the faded one looked broken/see-through by contrast. Scoped to just
   these two buttons — NOT a change to how disabled buttons look anywhere
   else in the app (hole nav, stroke counter, the Play tab's own GPS
   button all keep the plain opacity fade). `:disabled` on the selector
   keeps this from ever needing a class toggled in app.js — it just tracks
   the button's real disabled state automatically. */
#range-controls-row button:disabled,
#btn-log-shot:disabled {
  opacity: 1;
  background: #454b47;
  color: rgba(255,255,255,0.6);
  text-shadow: none;
}

/* Safe-area bottom padding for Clubs, Stats, and Strategy tabs so content
   isn't hidden under the iPhone home indicator or Safari toolbar. */
#tab-clubs { padding: 16px 16px calc(16px + env(safe-area-inset-bottom, 0px)); gap: 14px; }
/* Stats tab (2026-08-27, structural fix for the recurring sticky-header-gap
   bug — Ged reported it a third time after two rounds of compositing patches
   (2026-08-24 sticky-wrapping, 2026-08-25 GPU-layer promotion) hadn't closed
   it). #tab-stats is BOTH the scrolling container (.tab-panel has
   overflow-y:auto) AND a flex column, and its sticky first child
   (.stats-sticky-header) used to rely on that same element's own 16px
   padding scrolling away cleanly before the header stuck flush at top:0 —
   exactly the "position:sticky inside a padded flex/scroll container"
   combination WebKit has long had trouble resolving consistently, which
   plausibly explains why compositing-only fixes never fully closed it.
   #tab-stats itself is deliberately left unpadded now; each of its two
   direct children owns its own inset instead (see .stats-sticky-header's
   padding and .stats-sec-panel's padding below) — so there is no separate
   "padding region" outside the sticky box for scrolled content to ever
   flash through. */
#tab-stats { gap: 14px; }
h2 { font-size: 1rem; margin: 10px 0 8px; color: var(--app-text2); }

#shot-form, .inline-form {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  margin-bottom: 10px;
}
/* Retokenized 2026-08-26 (Ged, "Green Spots Round 2" item 13) — Log Shot
   Club/Distance/Lie field labels. */
#shot-form label { width: 100%; font-size: 0.8rem; color: var(--app-text3); margin-bottom: -4px; }
select, input[type="number"], input[type="text"] {
  padding: 10px;
  border-radius: 8px;
  border: 1px solid var(--app-accent-fill);
  background: var(--app-card-alt);
  color: var(--app-text1);
  font-size: 1rem;
  flex: 1;
  min-width: 100px;
}
/* Fix 7: desktop browsers (Chrome/Edge) show native dropdown with system background.
   Explicitly dark-background the option elements so white text stays readable. */
select option {
  background-color: var(--app-card-alt);
  color: var(--app-text1);
}
button[type="submit"] {
  padding: 10px 16px;
  border: none;
  border-radius: 8px;
  background: var(--app-accent-fill);
  color: var(--app-accent-ontext);
  font-weight: 600;
}

#bag-list { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
.bag-chip {
  background: var(--app-card-alt);
  border: 1px solid var(--app-accent-fill);
  border-radius: 16px;
  padding: 6px 10px;
  font-size: 0.8rem;
  display: flex;
  align-items: center;
  gap: 6px;
}
.bag-chip button { background: none; border: none; color: #ff8a8a; font-size: 0.9rem; padding: 0; }

/* Independently scrollable: with up to 10 shots x 15 clubs = 150 possible
   rows, an uncapped list would make the whole Clubs tab page extremely
   long, so it scrolls internally instead of growing the page. Was a fixed
   `max-height: 45vh` — Ged (2026-08-27): "data is only being displayed on
   half the screen now that it is scrollable... Can data be shown on the
   full screen but still be scrollable?" 45vh was a flat fraction of the
   viewport regardless of how much real space was actually free below the
   sticky button row + summary line, so on a tall screen it left a big
   unused gap. Replaced with flex-fill (below, plus the panel rule further
   down) — the list now grows to consume whatever height is genuinely
   available, on any device, and only scrolls internally once it actually
   runs out of room. Scoped to just this one panel, not every
   .clubs-sec-panel — see the data-sec-panel="history" rule below. */
#shot-history { display: flex; flex-direction: column; gap: 4px; font-size: 0.85rem; flex: 1; min-height: 0; overflow-y: auto; }
.shot-row { display: flex; justify-content: space-between; background: var(--app-card-alt); padding: 8px 10px; border-radius: 6px; }
.shot-row button { background: none; border: none; color: #ff8a8a; }

.unit-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 0 10px;
  border-bottom: 1px solid #154e2a;
  margin-bottom: 4px;
}
.unit-label { font-size: 0.8rem; color: #a8c9b6; flex-shrink: 0; }
.unit-btn {
  padding: 6px 16px;
  border: none;
  border-radius: 8px;
  background: var(--app-card-alt);
  color: var(--app-text2);
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
}
.unit-btn.active { background: var(--app-accent-fill); color: var(--app-accent-ontext); }

/* Appearance — simplified 2026-08-26 (Ged: "lets not have another theme"
   after living with Logo Navy for a day) from a 4-combo tap-to-reveal
   overlay (theme × Standard/Sunlight) down to a plain two-button toggle,
   now that Standard/Sunlight is genuinely the only choice left. Mirrors
   .unit-row/.unit-btn above rather than reusing those classes directly, so
   this stays its own component even though the look is identical. */
.appearance-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 0 10px;
  border-bottom: 1px solid rgba(255,255,255,0.12);
  margin-bottom: 4px;
}
.appearance-row-label { font-size: 0.8rem; color: var(--app-text3); flex-shrink: 0; }
.appearance-btns { display: flex; gap: 8px; }
.appearance-btn {
  padding: 6px 16px;
  border: none;
  border-radius: 8px;
  background: var(--app-card-alt);
  color: var(--app-text2);
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
  font-family: inherit;
}
.appearance-btn.active { background: var(--app-accent-fill); color: var(--app-accent-ontext); }

/* Example Data (2026-09-27) — mirrors .appearance-row/.appearance-btns above
   exactly (same plain Off/On two-button toggle, no iOS-style switch
   component exists anywhere else in the app) rather than inventing a new
   component for this one section. */
.example-data-row {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 0 10px;
  border-bottom: 1px solid rgba(255,255,255,0.12);
  margin-bottom: 4px;
}
.example-data-row-label { font-size: 0.8rem; color: var(--app-text3); flex-shrink: 0; }
.example-data-btns { display: flex; gap: 8px; margin-left: auto; }
.example-data-btn {
  padding: 6px 16px;
  border: none;
  border-radius: 8px;
  background: var(--app-card-alt);
  color: var(--app-text2);
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
  font-family: inherit;
}
.example-data-btn.active { background: var(--app-accent-fill); color: var(--app-accent-ontext); }

/* Example Data banner — prepended to whichever Stats/Plan card is currently
   showing the fixture instead of real data. Amber palette borrowed from
   .caddie-low-conf-warn, same "quiet, not alarming, but not to be confused
   with the real thing" register used everywhere else in the app for a
   temporary/in-progress state. */
.example-banner {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 0.78rem;
  color: #c8a84b;
  background: #1e1600;
  border: 1px solid #6e5a1a;
  border-radius: 8px;
  padding: 8px 10px;
  margin-bottom: 10px;
}
.example-banner-text { flex: 1; line-height: 1.35; }
.example-banner-link {
  flex-shrink: 0;
  border: none;
  background: none;
  color: #c8a84b;
  font-weight: 700;
  font-size: 0.76rem;
  text-decoration: underline;
  padding: 2px 0;
  cursor: pointer;
  font-family: inherit;
}

/* Retokenized 2026-08-26 (Ged, "Green Spots Round 2" item 18) — the
   suggested-club highlighted row used the same old pill green the Clubs/
   Stats section buttons used before those were fixed; --app-pill-bg is the
   same token that replaced it there. */
table.stats { width: 100%; border-collapse: collapse; font-size: 0.85rem; }
table.stats th, table.stats td { text-align: left; padding: 6px 4px; border-bottom: 1px solid rgba(255,255,255,0.12); }
table.stats tr.stats-rec td { color: #fff; font-weight: 600; background: var(--app-pill-bg); }
.prog-wrap { display: flex; align-items: center; gap: 5px; }
.prog-bar { flex-shrink: 0; width: 48px; height: 6px; background: #0a2c14; border-radius: 3px; overflow: hidden; }
.prog-fill { height: 100%; background: var(--app-accent-fill); border-radius: 3px; transition: width 0.4s; }
.prog-fill.full { background: #1fbf6e; }
.prog-label { font-size: 0.72em; opacity: 0.8; white-space: nowrap; }
/* Retokenized 2026-08-26 (Ged, "Green Spots Round 2" item 16) — global hint
   text class used across the app (Backup and Restore etc). */
.hint { font-size: 0.78rem; color: var(--app-text3); margin-top: 12px; }

/* ── Stats tab sticky header (2026-08-24, bug fix — Ged: "the Distance
   unit... slides under the Range, Plan, Clubs, Stats buttons" when
   scrolling the dispersion table) ───────────────────────────────────────
   .stats-sec-row alone was already sticky (see its own comment below), but
   .unit-row sat above it as a separate, non-sticky sibling — so scrolling
   still carried the Metres/Yards toggle up and off, out from under the
   app's own top tab bar. Wrapping both in one sticky container means
   neither can ever scroll away independently of the other. */
.stats-sticky-header {
  position: sticky;
  top: 0;
  z-index: 5;
  background: var(--app-bg);
  /* Owns its own inset now (2026-08-27 structural fix — see the #tab-stats
     comment above) instead of inheriting it from the scrolling container's
     padding. This keeps the padding INSIDE the sticky, opaque-background
     box at all times — stuck or not — so there's no separate padding
     region sitting outside the sticky element for scrolled content to ever
     show through. Left/right 16px matches #tab-clubs's own inset so Stats
     content still lines up with every other tab; no bottom padding here,
     .stats-sec-row's own padding-bottom already provides that gap. */
  padding: 16px 16px 0;
  /* iOS Safari fix (2026-08-25, Ged: "gap between the distance unit
     options and the Range/Plan/Clubs/Stats buttons... you can see the
     data scrolling in the gap"). The opaque background above already
     stops scrolled rows showing through in normal cases, but WebKit can
     still momentarily fail to composite a sticky element correctly during
     fast/momentum scrolling unless it's promoted to its own GPU layer —
     without that, a sliver of the scrolled content behind it can flash
     through for a frame, reading as a "gap". translateZ(0) forces that
     promotion; it's a paint/compositing hint only, not a layout change.
     Kept alongside the 2026-08-27 structural fix above — belt and braces,
     since it's still cheap insurance even though the padding restructure
     is the fix expected to actually close the gap for good this time. */
  transform: translateZ(0);
  -webkit-transform: translateZ(0);
  will-change: transform;
}

/* ── Stats tab section switcher (2026-08-22, locked in by Ged) ───────────
   Horizontally-scrolling row of pill buttons, styled off the existing
   top-level .tab-btn look (same active-state green) just smaller and in a
   row that scrolls instead of wrapping. */
.stats-sec-row {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  padding: 2px 2px 10px;
  margin: 4px -2px 2px;
  scrollbar-width: none;
  /* Sticky (2026-08-23, bug fix — Ged: "the data renders over the button
     and you can't select the buttons. Data should slide under the
     buttons.") — pins the button row at the top of the Stats tab's own
     scroll container (.tab-panel has overflow-y:auto) so a tall dispersion
     table or an expanded gated card can never leave the buttons
     unreachable; everything scrolls underneath instead. As of 2026-08-24
     this row's own stickiness is redundant (its parent .stats-sticky-header
     is sticky now, see above) but harmless, so it's left in place rather
     than risking a regression by removing it. */
  position: sticky;
  top: 0;
  z-index: 5;
  background: var(--app-bg);
  /* Same iOS compositing fix as .stats-sticky-header above — belt and
     braces since this row is itself sticky too, even though its parent's
     stickiness already makes this one redundant in normal layout terms. */
  transform: translateZ(0);
  -webkit-transform: translateZ(0);
  will-change: transform;
}
.stats-sec-row::-webkit-scrollbar { display: none; }
.stats-sec-btn {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 7px 12px;
  border-radius: 20px;
  /* Lightened 2026-08-24 (Ged: the unselected pill was nearly invisible
     against the sticky row's own background, which is the same shade as
     the page) — a clear step up from the page/row background but still
     well short of the bright selected state, so the two states stay
     distinct from each other as well as from the page. Retokenized
     2026-08-26 — was hardcoded to the Green value specifically (#1a5c35),
     so it stayed green under every theme; now var(--app-pill-bg), which
     carries that exact same "readable but quiet" relationship per theme
     (same value for Green — no visual change there — a matching blue for
     Logo Navy). */
  background: var(--app-pill-bg);
  border: none;
  color: var(--app-text2);
  font-size: 0.8rem;
  font-weight: 600;
  white-space: nowrap;
}
.stats-sec-btn.active { background: var(--app-accent-fill); color: var(--app-accent-ontext); }
/* Icons added 2026-08-26 (Ged, "Green Spots Round 2" item 25) — same
   convention as .clubs-sec-btn svg above. */
.stats-sec-btn svg { width: 15px; height: 15px; flex-shrink: 0; display: block; }
.stats-sec-lock {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #C9A227;
  flex-shrink: 0;
  display: none; /* shown via JS (applyTierToUI) only below the section's tier */
}
/* min-height:0 (2026-08-23, bug fix, belt-and-braces alongside the sticky
   row above): .tab-panel.active is a flex column, and flex items default to
   min-height:auto, which can let a very tall child (a long dispersion
   table, an expanded card) resist normal containment and bleed into
   surrounding layout instead of just growing the scrollable area like a
   plain block element would. Explicit min-height:0 removes that flex-only
   quirk so these panels behave like ordinary block content again. */
/* Left/right/bottom inset owned here now (2026-08-27 structural fix, see
   #tab-stats above) since the parent no longer carries any padding of its
   own — no top inset needed, #tab-stats's own flex `gap` already provides
   the vertical space below the sticky header. */
.stats-sec-panel { display: none; min-height: 0; padding: 0 16px calc(16px + env(safe-area-inset-bottom, 0px)); }
.stats-sec-panel.active { display: block; }

/* ── Round History totals strip (2026-08-22, locked in by Ged: "a section
   above the recorded rounds that gives a Rounds Total, Best Score, and Avg
   score") — sits above the existing per-course groups, which keep their
   own avg/best exactly as before. ── */
.rh-summary { display: flex; gap: 8px; margin-bottom: 6px; }
.rh-tile {
  flex: 1;
  background: var(--app-card);
  border: 1px solid #2a6e47;
  border-radius: 10px;
  padding: 9px 6px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
}
.rh-tile-val { font-size: 1rem; font-weight: 700; color: #fff; }
.rh-tile-best .rh-tile-val { color: #C9A227; }
.rh-tile-lbl { font-size: 0.6rem; color: #a8c9b6; text-transform: uppercase; letter-spacing: 0.03em; text-align: center; }

/* Handicap Estimate tile (2026-08-24, claude/golf-caddie-handicap-estimate-
   scope.md) — fourth .rh-tile, sage-green value colour keeps it visually
   distinct from the gold "best" tile without a new colour family. One
   disclaimer line under the strip, shown once, not per-tile. */
.rh-tile-handicap .rh-tile-val { color: var(--app-text3); }
.rh-disclaimer { font-size: 0.62rem; color: #7fa88f; line-height: 1.4; margin: 2px 2px 10px; text-align: center; }
/* Frozen summary / scrollable rest (2026-08-27) — Ged: "freeze the section
   where Rounds Total, Best Score, Avg Score and Handicap estimate [sit]...
   Stroke index and course history [should be] scrollable in that window,
   similar to how the Lie Segment data is scrollable." #round-history-summary
   (the 4 tiles + disclaimer, above) is no longer where the Stroke Index row
   lives — see renderRoundHistorySummary()/renderRoundHistoryCard() in
   app.js — so it now stays a fixed, always-visible block above this box.
   Same capped-height + internal-scroll idiom as .lie-stats-scroll/
   #shot-history, reused rather than inventing a new pattern. */
.rh-scroll { max-height: 45vh; overflow-y: auto; }
/* Stroke Index gender-preference row (2026-08-24, golf-caddie-handicap-
   estimate-scope.md Addendum 2) — sits under the Round History summary
   strip, controls which OSM handicap tag variant Stroke Index auto-fill
   reads. Appearance section (2026-08-25, colour themes) sits directly
   below it. Both rebuilt 2026-08-26 (Ged, colour-consistency pass, items
   10 & 11) from button-rows to one shared text + tap-to-reveal-overlay
   component — same interaction the Stroke Index number-entry overlay on
   the hole schematic already uses. Fully tokenized from the start (the
   old .si-gender-pref/-row/-btn rules these replaced were themselves
   hardcoded — #1e5b32 border, #7ab89a text — never actually part of the
   colour-theme problem being fixed elsewhere in this pass, just retired
   along with the button-row pattern). See siGenderPrefRowHtml()/
   appearanceSectionHtml()/wireSiGenderPrefRow()/wireAppearanceSection()
   in app.js. */
.tap-row { margin: 2px 2px 10px; position: relative; }
.tap-row + .tap-row { margin-top: 14px; }
.tap-text {
  display: flex; align-items: center; justify-content: space-between;
  width: 100%; background: var(--app-card); border: 1px solid var(--app-accent-fill);
  border-radius: 10px; padding: 10px 12px; cursor: pointer; font-family: inherit;
}
.tap-text-label { font-size: 0.62rem; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; color: var(--app-text3); display: block; text-align: left; }
.tap-text-val { font-size: 0.86rem; font-weight: 700; color: var(--app-text1); margin-top: 2px; display: block; text-align: left; }
.tap-chevron { color: var(--app-text3); font-size: 0.9rem; transition: transform 0.2s ease; flex-shrink: 0; margin-left: 8px; }
.tap-row.open .tap-chevron { transform: rotate(180deg); }
.tap-overlay {
  position: absolute; left: 0; right: 0; top: calc(100% + 6px);
  background: var(--app-card); border: 1px solid var(--app-accent-fill); border-radius: 12px;
  padding: 10px; box-shadow: 0 14px 28px -10px rgba(0,0,0,0.55);
  display: none; flex-direction: column; gap: 6px; z-index: 5;
}
.tap-overlay.open { display: flex; }
.tap-opt { text-align: left; padding: 9px 10px; border-radius: 8px; background: var(--app-card-alt); color: var(--app-text2); font-size: 0.78rem; font-weight: 600; border: 1px solid transparent; cursor: pointer; font-family: inherit; }
.tap-opt.selected { background: var(--app-accent-fill); color: var(--app-accent-ontext); border-color: var(--app-accent-fill); }
.rh-disclaimer .info-dot {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  background: #1a5c35;
  color: var(--app-text2);
  font-size: 0.6rem;
  font-weight: 800;
  margin-right: 4px;
  vertical-align: -1px;
}

.storage-notice { margin-bottom: 10px; line-height: 1.45; }
.backup-filename-row {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-bottom: 8px;
}
.backup-filename-row input {
  flex: 1;
  font-size: 0.9rem;
  padding: 8px 10px;
  min-width: 0;
}
.backup-ext {
  font-size: 0.85rem;
  color: #7ab89a;
  flex-shrink: 0;
}
.backup-row { display: flex; gap: 8px; margin-bottom: 6px; }
.backup-row button,
.btn-import-label {
  flex: 1;
  padding: 10px 6px;
  border: none;
  border-radius: 10px;
  background: var(--app-accent-fill);
  color: var(--app-accent-ontext);
  font-size: 0.85rem;
  font-weight: 600;
  text-align: center;
  cursor: pointer;
  display: block;
}
.btn-import-label input[type="file"] { display: none; }

.leaflet-control-attribution { font-size: 9px !important; }

/* Leaflet only sets pointer-events on .leaflet-interactive elements.
   Non-interactive marker wrapper divs default to pointer-events:auto which
   lets them intercept taps meant for the underlying hit line.  Force them
   transparent to pointer events so touches always reach the SVG path below. */
.leaflet-marker-icon:not(.leaflet-interactive),
.leaflet-marker-shadow:not(.leaflet-interactive) {
  pointer-events: none !important;
}

/* Course search bar */
.search-row input[type="text"] {
  min-width: 0;   /* override the global 100px so it shrinks inside flex */
  font-size: 0.9rem;
  padding: 10px 8px;
}
#btn-search-course {
  flex: 0 0 auto;
  min-width: 62px;
}
/* Sizing reset 2026-08-26 (Range Controls Redesign): #btn-toggle-overlay
   used to be a narrow icon-only button squeezed next to the search input
   (flex:0 0 auto, min-width:42px). It now lives in #range-controls-row as
   a peer of Mark/Live Dist/Clear — same flex:1 row-sharing and icon+label
   sizing those already get from the shared #btn-search-course,
   #btn-toggle-overlay, #btn-range-mark, #btn-log-shot rule above, so this
   only needs to keep the long-press-suppression behaviour, not its own size. */
#btn-toggle-overlay {
  /* Long-press triggers a refresh (see app.js) — suppress iOS/Android's
     default long-press affordances (callout menu, text-selection highlight)
     so holding the button feels like a deliberate app gesture, not like the
     browser chrome is about to interrupt it. */
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
}
#btn-toggle-overlay.overlay-off { background: #5a5a5a; }
/* Long-press-to-refresh feedback — the status line also updates ("Refreshing
   course data…"), but the button itself pulses too since a long-press on a
   toggle button doesn't otherwise look any different from a normal tap. */
#btn-toggle-overlay.refreshing { animation: overlay-refresh-pulse 0.9s ease-in-out; }
@keyframes overlay-refresh-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(201, 162, 39, 0.65); }
  70%  { box-shadow: 0 0 0 8px rgba(201, 162, 39, 0); }
  100% { box-shadow: 0 0 0 0 rgba(201, 162, 39, 0); }
}

/* "Please wait…" spinner (v267, 2026-09-14) — replaces the original
   animated-dots + whole-line-brightness-pulse treatment (2026-08-06/07)
   with the same spinning-refresh icon already used by the "Checking for
   updates…" banner/button, per Ged's request to keep the app's two
   "something is loading" status lines visually consistent. Used in the
   course-status line while a course-data search is in flight (both the
   Search panel and Controls panel share this — see findNearbyCourse() in
   app.js); the icon's `update-spin` keyframe is shared with the
   update-check UI further down this file. No JS class toggling needed —
   the icon markup is only ever present in the DOM while the "please wait"
   message itself is showing, and disappears the instant that's overwritten
   with a different status. */
.status-spin { display: inline-flex; vertical-align: middle; margin-left: 2px; }
.status-spin svg { animation: update-spin 1s linear infinite; }

/* Course search results picker — shown when a search returns more than one
   match (or when the top hit isn't confidently a golf course), so a
   misspelling or ambiguous name doesn't silently jump to the wrong place
   or dead-end with no way forward. Empty by default so it takes no space. */
.search-results-list {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 0 12px;
  /* Retokenized 2026-08-26 — spotted alongside the rest of the drawer fix
     (same hardcoded green the search bar/control-row/status-line all had);
     same var(--app-header) swap as those. */
  background: var(--app-header);
}
.search-results-list:empty { display: none; padding: 0; }
.search-result-item {
  text-align: left;
  padding: 9px 10px;
  border: 1px solid #1d6b3a;
  border-radius: 8px;
  background: var(--app-card-alt);
  color: #e8f5ee;
  font-size: 0.85rem;
}
.search-result-item:active { background: #1d6b3a; }
.search-result-item.search-result-golf { border-color: #3aad6e; }

/* Star-to-favourite toggle appended to the "Course detected: X" / "Loaded
   saved plan: X" status line (v239, Your Courses — Caddie+ only, built by
   setCourseNameStatus()/courseStarButton() in app.js). Base .status-line
   isn't flex (most status messages are plain single-line text), so this
   modifier class is added only when a star is actually present. */
.status-line-with-star { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.status-line-with-star .status-msg-text { flex: 1; }
.status-star-btn {
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: none;
  background: rgba(0,0,0,0.2);
  color: #b9c4ba;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
}
.status-star-btn.on { color: #C9A227; }

/* "Your Courses" — favourite-course quick-load row (v239), sits above the
   search box (see #your-courses-block in index.html / renderYourCoursesRow()
   in app.js). Locked-below-Caddie state reuses .gated-card-collapsible
   verbatim (see gated-tier-chip-intro above) — only the unlocked shape
   below is new. Pills reuse the same opaque/solid treatment
   .search-result-item already uses inside this frosted panel, per the
   established "shell frosted, contents opaque" rule further down this file. */
.yc-toggle {
  margin: 4px 12px 6px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 9px 12px;
  border-radius: 10px;
  background: var(--app-card-alt);
  border: 1px solid #1d6b3a;
  color: var(--app-text1);
  font-size: 0.83rem;
  font-weight: 600;
}
.yc-toggle .yc-left { display: flex; align-items: center; gap: 7px; }
.yc-toggle .yc-star { color: #C9A227; flex-shrink: 0; }
.yc-toggle .chev { transition: transform 0.18s; color: var(--app-text3); flex-shrink: 0; }
.yc-toggle.open .chev { transform: rotate(180deg); }
.yc-dropdown {
  margin: 0 12px 8px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.22s ease;
}
.yc-dropdown.open { max-height: 178px; overflow-y: auto; }
.yc-count { padding: 2px 2px 4px; font-size: 0.66rem; color: var(--app-text3); opacity: 0.85; }
.yc-pill {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 9px 8px 9px 14px;
  border-radius: 999px;
  background: var(--app-card-alt);
  border: 1px solid #C9A227;
  color: var(--app-text1);
  font-size: 0.82rem;
  font-weight: 600;
}
.yc-pill-name { flex: 1; }
.yc-pill .chip-star { color: #C9A227; flex-shrink: 0; display: flex; }
.yc-pill .chip-star svg { display: block; }
.yc-empty { padding: 10px 14px; font-size: 0.78rem; color: var(--app-text2); line-height: 1.4; }

/* Tee marker tap target (2026-08-19): invisible 40x40 box, centred on the
   same point as the dot inside it — same fix pattern as the crosshair
   target marker's own iconSize/iconAnchor. Enlarges only the tappable/
   draggable area; the visible dot is unchanged. */
.tee-tap-target {
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
}
/* Tee marker: animated glowing blue dot. No longer needs its own centering
   transform — the wrapper above handles that now via iconAnchor + flex. */
.tee-dot {
  width: 16px;
  height: 16px;
  background: #2196F3;
  border-radius: 50%;
  border: 2px solid #fff;
  animation: tee-pulse 1.5s ease-in-out infinite alternate;
}
@keyframes tee-pulse {
  from { box-shadow: 0 0 4px 2px rgba(33,150,243,0.6); }
  to   { box-shadow: 0 0 14px 7px rgba(33,150,243,0.9), 0 0 28px 12px rgba(33,150,243,0.3); }
}

/* Target/crosshair SVG: non-interactive so touches pass through to the hit line */
.crosshair-icon { pointer-events: none; display: block; }

/* Hole-number badge overlaid on the map */
.hole-label {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.85);
  border: 2px solid rgba(255, 255, 255, 0.9);
  color: #fff;
  font-size: 0.9rem;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  transform: translate(-50%, -50%);
  letter-spacing: 0;
}

/* Distance labels pinned to the middle of each line segment on the map.
   No background pill — bold white text with a thick dark outline (stacked
   text-shadows in every direction, since CSS has no native text-stroke
   with reliable cross-browser support) so it stays readable over any
   satellite imagery without a solid box behind it. */
.dist-label {
  color: #fff;
  font-size: 1.55rem;
  font-weight: 800;
  white-space: nowrap;
  transform: translate(-50%, -50%);
  pointer-events: none;
  letter-spacing: 0.02em;
  text-shadow:
    -2px -2px 0 #000, 2px -2px 0 #000, -2px 2px 0 #000, 2px 2px 0 #000,
    0 -2px 0 #000, 0 2px 0 #000, -2px 0 0 #000, 2px 0 0 #000,
    0 2px 6px rgba(0, 0, 0, 0.6);
}

/* Target-crosshair variants (v82, reverted from v83's rotation-aware radial
   attempt in v86 — see distLabelIcon()/updateDistances() comments): when a
   target/bend point is set, both distance labels are placed at the
   crosshair's OWN lat/lng and rely purely on this transform for position,
   instead of the default translate(-50%,-50%) center-on-point placement
   above. The offset is a fixed 5% of viewport height (5dvh), not a
   geographic fraction of the line — that's what keeps them pinned the same
   screen distance from the crosshair (and therefore always in view)
   whether you're zoomed way in or way out, instead of drifting off-screen
   on a long hole at a tight zoom the way a distance-along-the-line
   placement would. Tee→target sits below the crosshair, target→green sits
   above it (Ged's spec), fixed regardless of map rotation. */
.dist-label.dist-label-below {
  transform: translate(-50%, 5dvh);
}
.dist-label.dist-label-above {
  transform: translate(-50%, calc(-100% - 5dvh));
}

/* Total distance badge pinned next to the green flag.
   The 🚩 emoji anchor is at the pole base (ground level).  The flag body sits
   ~23 px above the anchor and the emoji is ~26 px wide, centred on the pole
   (so right edge ≈ +13 px from anchor).  Translating the badge to (16px, -26px)
   places it just to the right of the flag tip with no overlap. */
.green-dist-label {
  display: inline-block;       /* shrink-wrap to content width */
  width: max-content;          /* force horizontal layout inside 0×0 divIcon */
  background: rgba(5, 30, 15, 0.92);
  border: 2px solid var(--app-accent-fill);
  color: #fff;
  font-size: 1.6rem;
  font-weight: 700;
  line-height: 1.1;
  padding: 4px 10px;
  border-radius: 6px;
  white-space: nowrap;
  transform: translate(16px, -26px);   /* right of pole, level with flag tip */
  pointer-events: none;
  letter-spacing: 0.02em;
}
/* Flip variant (v137): mirrors the default right-side placement to the left
   of the pole, used when the dispersion ellipse's real bias offset leans
   right of the green (see dispersionShapeBiasSide in app.js) so the badge
   never sits on top of it. Same vertical offset, horizontal offset negated
   and measured from the label's own right edge via -100%. */
.green-dist-label.green-dist-label-left {
  transform: translate(calc(-100% - 16px), -26px);
}

/* #btn-align is now hidden (functionality preserved via holeAligned state / applyMapRotation) */

/* Corner-block stack grows together with the double-tap UI zoom
   (2026-08-27, Ged: mocked up at https://claude.ai/code/artifact/
   39a0d77c-5af6-47b9-9248-578b3b80678b and approved; trigger corrected
   2026-08-28 — see app.js's double-tap tab-button handler). The Hole chip,
   Weather block, Club block, and My Location button (see
   buildMapControls() in app.js) are the only four controls registered at
   Leaflet's "topleft" position, so they already share ONE real DOM
   container — Leaflet's own .leaflet-top.leaflet-left — with no custom
   wrapper needed. The double-tap-a-tab-button handler in app.js toggles
   .corner-stack-zoomed on that shared container in lockstep with the
   uiZoomed state (double-tap only — NOT the map's own pinch/double-tap
   zoom, which no longer has any effect on this); this rule just supplies
   the visual transform. transform-origin stays top-left (where the stack
   is anchored) so growing never drifts the blocks over the map or the
   Search/Controls panel at the bottom. */
.leaflet-top.leaflet-left {
  transform-origin: top left;
  transition: transform 0.28s cubic-bezier(0.34, 1.2, 0.4, 1);
}
.leaflet-top.leaflet-left.corner-stack-zoomed {
  transform: scale(1.3);
}
@media (prefers-reduced-motion: reduce) {
  .leaflet-top.leaflet-left { transition: none; }
}

/* ── Range tab control-stack redesign (2026-08-22, locked in by Ged) ──────
   Replaces the old zoom buttons + wind-arrow circle + direction lozenge +
   temp badge + locate button (five separate 34px pieces) with two: one
   merged weather block (compass/direction/speed/temp) and a matching My
   Location button, sharing one 52px black/edge rounded-square style so they
   read as one visual family. Zoom buttons are gone entirely — see the
   zoomControl:false comment on the map init in app.js.
   Edge colour + tap feedback revised 2026-08-23 (Ged, after reviewing the
   v3 mockup at https://claude.ai/code/artifact/3543444d-c85a-4c73-96be-
   7be8ae715aeb): (1) the ↻ refresh-hint badge is removed — tapping the
   block already refreshes, the badge was redundant now that tap feedback
   exists; (2) edge colour changed from blue (#4fc3f7) to the same pale
   sage-green (#9be7b8) already used by the Range drawer toggle, so the
   whole Range tab reads as one colour family instead of two; (3) both
   blocks now pulse on tap (see .blk.refreshing/@keyframes
   weather-refresh-pulse below), reusing the exact ripple shape/timing the
   course-overlay button's long-press-refresh pulse already uses
   (#btn-toggle-overlay.refreshing/@keyframes overlay-refresh-pulse above),
   just recoloured to match.
   Background + size revised 2026-08-24 (Ged, mocked up at
   https://claude.ai/code/artifact/e5f204c5-4d60-4c85-bd77-8a3016b3da34):
   background changes from near-black to the same dark green already used
   by the drawer below the map (#07210f — .controls, .range-handle,
   .range-readout all already use it), and both blocks grow from 52px to
   68px so the compass arrow, location pin, and direction/speed/temperature
   text all have more room to read clearly at a glance. */
.blk {
  width: 68px;
  border-radius: 18px;
  /* Retokenized 2026-08-26 (Ged, colour-consistency pass) — was hardcoded
     to the app's original green regardless of theme. Now the same pair
     used everywhere else, so the whole corner stack goes navy under Logo
     Navy and darkens correctly under Sunlight. */
  background: var(--app-header);
  border: 2px solid var(--app-text3);
  box-shadow: 0 6px 16px -6px rgba(0,0,0,0.65);
}
.blk-wrap {
  position: relative;
}
/* ── Per-tile headers (WIND / GREEN / CLUB) ──
   2026-09-24, Ged: "mock up what it would look like if each tile had its
   own header" — mocked up at
   https://claude.ai/artifact/C2Vacs2PZoTzYCGLbpmH35 and approved ("don't
   change how the current information is displayed... change the font
   colour so it is white", 8px). Shared by all three data tiles (weather/
   wind, Front-Pin-Back/green, club) — the My Location button and Hole chip
   stay unlabelled since they're controls, not data tiles, matching the
   mockup's own carve-out. Weather block's header is static markup (see
   buildMapControls() in app.js); the FPB and Club blocks' headers are
   prepended to the HTML string each block's own update function builds
   (updateFpbBlock()/updateClubBlock()), since those two blocks' entire
   contents are rebuilt from scratch on every update. */
.blk-header {
  font-size: 0.5rem; /* 8px */
  font-weight: 800;
  letter-spacing: 0.05em;
  color: #ffffff;
  text-transform: uppercase;
  text-align: center;
  line-height: 1;
}
.blk-header-div {
  width: 26px;
  height: 1px;
  background: rgba(255,255,255,0.18);
  margin: 4px 0 5px;
}
.blk.refreshing { animation: weather-refresh-pulse 0.9s ease-in-out; }
@keyframes weather-refresh-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(155, 231, 184, 0.7); }
  70%  { box-shadow: 0 0 0 10px rgba(155, 231, 184, 0); }
  100% { box-shadow: 0 0 0 0 rgba(155, 231, 184, 0); }
}
@media (prefers-reduced-motion: reduce) {
  .blk.refreshing { animation: none; }
}

/* Stack order swapped 2026-08-24 (Ged: "make the my location be on top of
   the weather block"), then the stack grew to four blocks and My Location
   moved back to the bottom 2026-08-25 (see .locate-map-control's own
   comment above, and buildMapControls() in app.js) — the weather block is
   now second in a Hole chip / weather / club / My Location stack, still
   carrying the standard 10px "gap below the control above it" margin. */
.weather-blk-control {
  background: none !important;
  border: none !important;
  box-shadow: none !important;
  margin-top: 10px !important;
}
.weather-blk {
  padding: 8px 0 9px;
  display: none; /* shown via JS (updateWindArrow) only when wind data is active */
  flex-direction: column;
  align-items: center;
  cursor: pointer;
}
/* Direction label. Sized up 0.7rem → 0.9rem 2026-08-24 alongside the rest of
   the block's text (mocked up at the URL noted on .blk above) — carried
   forward from the old standalone lozenge before that. */
.w-dir {
  /* Sized up 0.9rem → 1.05rem and retokenized 2026-08-26 (Ged, "more space
     in the blocks to make it bigger" + colour-consistency pass) — was
     hardcoded sage green regardless of theme. */
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--app-text3);
  text-align: center;
  white-space: nowrap;
  line-height: 1.2;
  letter-spacing: 0.02em;
}
/* Speed sits below direction, with its own breathing room (2026-08-22, Ged:
   "more space between the direction WNW and the wind speed... currently
   looks squashed in"). Number sized up again 2026-08-24, 0.8rem → 1.15rem —
   made possible by giving its unit its own margin below (.w-speed-unit)
   instead of a flush <sub>, which was the actual constraint on how big the
   number could go without feeling cramped against it. */
.w-speed {
  font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
  font-size: 1.15rem;
  font-weight: 700;
  color: #fff;
  line-height: 1.05;
  margin-top: 9px;
  text-align: center;
}
/* Unit ("km/h"/"mph") — a real span now, not a <sub>, so its spacing from
   the number is an explicit margin rather than default <sub> baseline
   crowding. Colour changed pale-blue → white 2026-08-24 (Ged). */
.w-speed-unit {
  font-size: 0.64rem;
  font-weight: 600;
  color: #fff;
  margin-left: 6px;
}
.w-div {
  display: none; /* shown via JS (updateTempBadge) only alongside a shown temp reading */
  width: 34px;
  height: 1px;
  background: rgba(255,255,255,0.18);
  margin: 7px 0 6px;
}
.w-temp {
  display: none; /* shown via JS (updateTempBadge) only after a successful weather fetch + Pro/Tour tier */
  font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
  font-size: 1.15rem; /* matched to .w-speed 2026-08-25 (Ged) — was 1.04rem, smaller than the wind-speed number above it */
  font-weight: 700;
  color: #fff;
  line-height: 1;
  text-align: center;
}
.w-temp .w-unit {
  font-size: 0.55rem;
  color: #fff; /* was #bfe6f7 (pale blue) — Ged noticed it read as blue against the wind-speed unit's white; matched 2026-08-25 */
  margin-left: 1px;
}

/* ── My Location map button ──
   Stack position swapped again 2026-08-25 (Ged: "let's move the my
   location to the bottom of the stack" — reversing the 2026-08-24 "put it
   on top" call now that the stack has grown from two blocks to four; see
   buildMapControls() in app.js for the full stacking order). Now added to
   the map LAST, so it sits at the BOTTOM — this control's own margin-top
   changes from 5px (top-of-stack offset) to the standard 10px inter-block
   gap the other three blocks already use. */
.locate-map-control {
  background: none !important;
  border: none !important;
  box-shadow: none !important;
  margin-top: 10px !important;
}
.locate-map-btn {
  width: 68px;
  height: 68px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}
.locate-map-btn:active {
  background: rgba(24, 24, 28, 0.9);
}

/* ── Hole chip ──
   "Hole N" (plus a shot index on multi-shot holes) at the top of the
   corner stack — 2026-08-25 redesign, mocked up at
   https://claude.ai/code/artifact/8c4b4190-fade-4ede-ac66-9d36ff57876a and
   locked in by Ged. Reuses the exact green pill styling the old header
   banner's inline hole badge used (background #1a7a40), just relocated
   and promoted to its own block instead of sitting inline in a text line.
   Shown/hidden by updateHoleChip() in app.js — only visible during an
   active round with a current hole, same rule the old inline badge had. */
.hole-chip-control {
  background: none !important;
  border: none !important;
  box-shadow: none !important;
  margin-top: 5px !important; /* top-of-stack offset, same value .locate-map-control used to carry */
}
.hole-chip {
  display: inline-block; /* shrink-to-fit its own text, regardless of the Leaflet control wrapper's own sizing */
  /* Retokenized 2026-08-26 (Ged, colour-consistency pass) — was a solid
     green pill (#1a7a40) regardless of theme, the one piece of the corner
     stack that didn't already match the weather/club/locate blocks' own
     dark-fill/tinted-edge look. Now the same block family: background +
     2px edge from the same two tokens as .blk, text stays plain white
     (var(--app-text1), not the softer edge tint) since it reads more
     clearly against the dark fill. */
  background: var(--app-header);
  border: 2px solid var(--app-text3);
  color: var(--app-text1);
  border-radius: 12px;
  padding: 3px 10px;
  font-size: 0.74rem;
  font-weight: 800;
  letter-spacing: 0.02em;
  text-align: center;
  white-space: nowrap;
  box-shadow: 0 4px 10px -4px rgba(0,0,0,0.5);
}

/* ── Front / Pin / Back block ──
   Live distance to the green's front edge, the pin, and the back edge
   (golf-caddie-front-pin-back-tile-scope.md, 2026-09-21) — sits directly
   under the Weather block, above the Club block. Back on top, Front on the
   bottom, matching the hole's own orientation on the map (you play
   up-screen toward the green, so the far edge sits higher than the near
   edge); Pin is the hero number between them, same visual weight as the
   Club block's own hero distance. Hidden whenever there's nothing to show
   (no green polygon, no known play direction, no position yet) via
   updateFpbBlock() in app.js — same "hide rather than show an empty shell"
   rule the weather/club blocks already use. */
.fpb-blk-control {
  background: none !important;
  border: none !important;
  box-shadow: none !important;
  margin-top: 10px !important;
}
.fpb-blk {
  padding: 8px 0 9px;
  display: none; /* shown via JS (updateFpbBlock) only when there's a green to measure against */
  flex-direction: column;
  align-items: center;
  transition: border-color 0.2s;
}
.fpb-row-block { display: flex; flex-direction: column; align-items: center; }
.fpb-row { font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace; text-align: center; line-height: 1.05; }
/* Tightened label-to-number gap (2026-09-22, mocked up and approved:
   https://claude.ai/artifact/Je5u6SKPfxW5d9C51wUGZn) — the old markup put a
   literal space between the "B"/"F" label and the number, which rendered
   at the row's own (larger) font size regardless of how small the label
   itself was, so it didn't shrink to fit. At the bigger Front/Back size
   below that fixed-width space pushed the row wider than the 68px tile,
   shoving the label toward the edge. An explicit small flex gap fixes the
   width at 3px no matter what size the number renders at. */
.fpb-row.tight { display: inline-flex; align-items: baseline; gap: 3px; }
.fpb-lbl { font-size: 0.56rem; font-weight: 800; letter-spacing: 0.05em; color: var(--app-text3); text-transform: uppercase; }
/* Front/Back number + unit (2026-09-22 revision, same mockup as above):
   1rem / 0.55rem — matches the Club block's own secondary "±9m" figure,
   not its bigger hero number. Colour lightened from --app-text2 (#cdeede)
   toward white — Ged: "front and back distance colour lighter... it's a
   bit hard to read." */
.fpb-front, .fpb-back { font-size: 1rem; font-weight: 700; color: #eafff1; }
.fpb-front .unit, .fpb-back .unit { font-size: 0.55rem; color: #eafff1; margin-left: 1px; }
.fpb-pin-wrap { margin: 4px 0; }
.fpb-pin { font-size: 1.3rem; font-weight: 700; color: #fff; text-align: center; line-height: 1.05; }
.fpb-pin .unit { font-size: 0.6rem; font-weight: 600; margin-left: 3px; }
.fpb-est-note { font-size: 0.5rem; color: var(--app-text3); opacity: 0.85; text-align: center; margin-top: 3px; line-height: 1.2; }
/* "Plays like" (wind + elevation + air, tier-gated — see
   getPlaysLikeFactors() in app.js) — each row's small raw line only
   appears when the adjustment actually moves the rounded number, so a
   calm/flat/sea-level shot renders identically to the plain version.
   Size bumped 0.44rem → 0.5rem and colour lightened (was --app-text3 at
   opacity 0.8 — two dimming effects stacked on already-small text; now a
   lighter colour at full opacity) per the same mockup round. */
.fpb-raw-sub { font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace; font-size: 0.5rem; font-weight: 600; color: #cdf7dd; opacity: 1; margin-top: 1px; letter-spacing: 0.01em; }
.fpb-raw-sub-pin { margin-top: 2px; }
.fpb-blk.fpb-windy { border-color: #ffd34d; }
.fpb-wind-tag { font-size: 0.52rem; font-weight: 800; letter-spacing: 0.06em; color: #ffd34d; margin-bottom: 3px; text-align: center; }
/* Live Dist tracking active — reuses the same red #btn-toggle-tracking.tracking
   already uses for "GPS is live right now", instead of introducing a new
   accent colour just for this tile. Lightened 2026-09-22 (Ged: "make the
   red colour lighter for live, currently it's too dark") — border moved
   from #b33a2a up to the tag's old #e8604c, tag/dot lightened again above
   that to #ff8a70, so the whole live state reads brighter without
   drifting toward the gold used for PLAYS. Also confirmed over a blue
   (#2196F3, matching the Tee marker) alternate mocked up alongside it. */
.fpb-blk.fpb-live { border-color: #e8604c; }
.fpb-live-tag { display: flex; align-items: center; justify-content: center; gap: 3px; font-size: 0.52rem; font-weight: 800; letter-spacing: 0.06em; color: #ff8a70; margin-bottom: 3px; }
.fpb-live-dot { width: 5px; height: 5px; border-radius: 50%; background: #ff8a70; animation: fpb-pulse 1.6s ease-in-out infinite; }
@keyframes fpb-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }
@media (prefers-reduced-motion: reduce) {
  .fpb-live-dot { animation: none; }
}

/* ── Club recommendation block ──
   Club (abbreviated — see abbreviateClubName() in app.js), avg distance,
   dispersion, and confidence — 2026-08-25 redesign, relocated here from
   the old header banner (#topbar-rec, now removed) as part of the same
   mockup/lock-in as the Hole chip above. Hidden whenever there's nothing
   to show (no target set yet, or not enough logged shots for any club at
   this distance) via updateClubBlock() in app.js — same "hide rather than
   show an empty shell" rule the weather block already uses. */
.club-blk-control {
  background: none !important;
  border: none !important;
  box-shadow: none !important;
  margin-top: 10px !important;
}
.club-blk {
  padding: 8px 4px 9px;
  display: none; /* shown via JS (updateClubBlock) only when there's a recommendation to show */
  flex-direction: column;
  align-items: center;
  position: relative;
  transition: border-color 0.2s;
}
/* Plan-vs-recommendation mismatch (replaces the old header banner's
   "Plan: X → Y" line) — amber border + "!" badge + struck-through old
   plan, a soft warning rather than red since this isn't an error. */
.club-blk.mismatch { border-color: #ffd34d; }
.club-blk .lbl {
  /* Sized up 0.9rem → 1.05rem 2026-08-26 (Ged, "more space in the blocks
     to make it bigger") and retokenized — was hardcoded sage green. */
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--app-text3);
  text-align: center;
  line-height: 1.15;
}
.club-blk.mismatch .lbl { color: #ffd34d; }
.club-blk .hero {
  font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
  /* Sized up 1.1rem → 1.3rem 2026-08-26 (Ged) */
  font-size: 1.3rem;
  font-weight: 700;
  color: #fff;
  margin-top: 7px;
  text-align: center;
  line-height: 1.05;
}
.club-blk .hero .unit { font-size: 0.6rem; font-weight: 600; margin-left: 3px; }
.club-blk .divider { width: 30px; height: 1px; background: rgba(255,255,255,0.18); margin: 6px 0 5px; }
.club-blk .sub {
  font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace;
  /* Sized up 0.86rem → 1.0rem 2026-08-26 (Ged) */
  font-size: 1.0rem;
  font-weight: 700;
  color: #fff;
  text-align: center;
  line-height: 1;
}
.club-blk .sub .unit { font-size: 0.55rem; color: #fff; margin-left: 1px; }
.club-blk .plan-was {
  font-size: 0.62rem;
  font-weight: 700;
  color: #d8c9a3;
  text-decoration: line-through;
  opacity: 0.8;
  line-height: 1;
  margin-top: 3px;
}
.club-blk .warn-dot {
  position: absolute;
  top: -7px;
  right: -7px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: #ffd34d;
  color: #17110a;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 0.68rem;
  font-weight: 800;
  box-shadow: 0 2px 6px rgba(0,0,0,0.4);
}
.club-blk .mini-conf { width: 100%; margin-top: 7px; display: flex; flex-direction: column; align-items: center; gap: 3px; }
.club-blk .mini-conf-bar { width: 46px; height: 5px; border-radius: 3px; background: rgba(255,255,255,0.18); overflow: hidden; }
.club-blk .mini-conf-fill { display: block; height: 100%; background: var(--app-text3); border-radius: 3px; }
.club-blk .mini-conf-pct { font-size: 0.6rem; color: var(--app-text3); font-weight: 700; }

/* Wind row */
.wind-row { padding-top: 4px; padding-bottom: 4px; gap: 8px; }
.wind-row select {
  min-width: 0;
  flex: 2;
  font-size: 0.85rem;
  padding: 9px 8px;
}
.wind-row input[type="number"] {
  min-width: 0;
  flex: 1;
  max-width: 90px;
  font-size: 0.9rem;
  padding: 9px 8px;
}
/* Auto-fetch button — neutral by default, green when auto-wind is active */
/* Retokenized 2026-08-26 (Ged, "Green Spots Round 2" item 21) — off/default
   state was fully hardcoded; on/active state's fill/text were already
   tokenized, only its border wasn't. */
#btn-wind-auto {
  flex: 0 0 auto;
  min-width: 42px;
  font-size: 1rem;
  padding: 10px 6px;
  background: var(--app-card-alt);   /* neutral — clearly "off" */
  color: var(--app-text3);
  border: 1px solid var(--app-accent-fill);
}
#btn-wind-auto.auto-on {
  background: var(--app-accent-fill);   /* accent fill — clearly "on" */
  color: var(--app-accent-ontext);
  border-color: var(--app-accent-fill);
}
/* Auto wind mode elements — hidden by default, shown when auto-active */
.wind-auto-text {
  display: none;
  flex: 1;
  min-width: 0;
  font-size: 0.82rem;
  color: #9be7b8;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  align-self: center;
}
.wind-clear-btn {
  display: none;
  flex: 0 0 auto;
  min-width: 36px;
  background: #5a5a5a;
  padding: 10px 6px;
}
/* When auto wind is active: swap manual inputs for the fetched wind text */
#wind-row.auto-active #wind-dir,
#wind-row.auto-active #wind-speed { display: none; }
#wind-row.auto-active .wind-auto-text { display: flex; align-items: center; }
#wind-row.auto-active .wind-clear-btn { display: flex; align-items: center; justify-content: center; }

/* ── Strategy / Plan tab ────────────────────────────────────────── */
#tab-strategy { padding: 0; }
#strategy-content {
  flex: 1;
  overflow-y: auto;
  overscroll-behavior-y: contain; /* 2026-08-26, same fix as .tab-panel above */
  /* Pad the bottom of the scrollable plan content for the home indicator */
  padding-bottom: env(safe-area-inset-bottom, 0px);
}

/* Sticky header wrapper (2026-08-11) — groups the course-name header and
   the "Shots (max N) / Start Round" row so both stay pinned to the top of
   #strategy-content (the tab's own existing scroll container — flex:1;
   overflow-y:auto, no second scroll region needed) while the per-hole
   Clubs/Par/Pin/Map Green/Notes list scrolls underneath. Opaque background
   matching html/body's #0b3d20 is load-bearing: without it, hole rows
   would visibly show through as they scroll past behind this wrapper. */
.strategy-sticky-header {
  position: sticky;
  top: 0;
  z-index: 3;
  background: var(--app-bg);
  /* Same iOS Safari compositing fix as .stats-sticky-header (see its
     comment in this file) — prevents scrolled hole rows flashing through
     during fast/momentum scrolling. */
  transform: translateZ(0);
  -webkit-transform: translateZ(0);
  will-change: transform;
}

/* Course header in plan mode */
/* Retokenized 2026-08-26 (Ged, "Green Spots Round 2" item 12 sweep).
   Restructured 2026-09-05 for the collapsed-row planner redesign (approved
   mockup: https://claude.ai/code/artifact/94fcbc06-71b7-4275-9223-cf51263aac72,
   Ged's 6-point restructure) — course name now pairs with a planning-
   progress readout instead of a static hole-count, backed by a slim
   progress bar, with Start Round on its own row beneath. */
.strategy-course-header {
  display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
}
.strategy-course-name    { font-size: 0.95rem; font-weight: 700; color: var(--app-text2); }
.strategy-progress-text  { font-size: 0.78rem; color: var(--app-text3); font-weight: 600; white-space: nowrap; }
.strategy-progress-bar {
  height: 4px; border-radius: 2px; background: var(--app-card-alt);
  margin-top: 7px; overflow: hidden;
}
/* Fixed brighter status green rather than var(--app-accent-fill) — colour-
   refinement round 2 (approved mockup:
   https://claude.ai/code/artifact/664c008d-4435-4d66-87c0-433b9ae1ecac).
   Deliberately matches the hole-stripe/badge status green below so the
   header stat and the per-hole signals read as one system; independent of
   theme/Sunlight, same as the app's other status colours. */
.strategy-progress-fill { height: 100%; background: #3fae70; border-radius: 2px; }
.strategy-start-row {
  display: flex; justify-content: flex-end;
  margin-top: 8px; padding-bottom: 8px;
  border-bottom: 1px solid rgba(255,255,255,0.12);
}

/* Hole rows in plan mode */
.strategy-table { display: flex; flex-direction: column; gap: 0; margin-bottom: 4px; }

/* One row per hole — collapsed by default (point 1: "collapse holes into a
   scannable list view ... tap to expand into the editor").
   Colour-refinement round 2 (Ged: "everything just seems a bit blended" on
   round 1 — approved mockup:
   https://claude.ai/code/artifact/664c008d-4435-4d66-87c0-433b9ae1ecac):
   position:relative + left padding make room for the status stripe below.
   Divider thickened/brightened (Ged: "can the hole dividing lines be a
   brighter or just thicker?") from 1px/8% opacity — barely visible against
   the dark row background — to 1.5px/18%, still a hairline, not a heavy
   rule. */
.strategy-hole-row { border-bottom: 1.5px solid rgba(255,255,255,0.18); position: relative; padding-left: 12px; }

/* Status stripe — replaces the old right-side pin dot. A 4px bar down the
   left edge reads in peripheral vision scanning down a full 18-hole list;
   an 8px dot at the far edge doesn't. Green = confirmed pin, amber =
   estimated, solid grey = nothing set (Ged: "change the status stripe for
   a hole that is not planned yet to a solid grey colour" — reuses the
   exact grey the old pin-dot's "no pin" state used). Deliberately fixed
   status colours (brighter than --app-accent-fill), not theme-driven —
   same reasoning as the pin-estimated amber elsewhere. */
.strategy-hole-stripe {
  position: absolute; left: 0; top: 9px; bottom: 9px; width: 4px; border-radius: 3px;
  background: #3fae70;
}
.strategy-hole-stripe.est  { background: #e0a83c; }
.strategy-hole-stripe.none { background: #3a4a41; }

.strategy-hole-row-head {
  display: flex; align-items: center; gap: 10px;
  padding: 11px 2px; cursor: pointer;
}
/* Badge — pulled back toward the pre-refinement look after two rounds of
   Ged's own follow-up feedback: "make the circle background colour ... the
   same dark green background as the current plan" (both states now share
   var(--app-card-alt), exactly what the pre-refinement badge always used)
   and "the edge also be the same colour as the current when the hole has
   been planned" (var(--app-accent-fill) border on .done, matching the
   pre-refinement badge's border exactly). Planned vs not-yet-planned is
   now signalled by the border colour plus the stripe/row-dim elsewhere,
   not by the fill. Number colour stays a fixed light mint rather than
   following text tokens — a near-black digit on a mid-value green fill
   was "difficult to see" (Ged) regardless of theme, the complaint that
   started this whole thread. */
.strategy-hole-badge {
  width: 28px; height: 28px; border-radius: 50%; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  font-size: 0.8rem; font-weight: 700;
}
.strategy-hole-badge.done    { background: var(--app-card-alt); border: 1px solid var(--app-accent-fill); color: #eafff1; }
.strategy-hole-badge.pending { background: var(--app-card-alt); border: 1px solid rgba(255,255,255,0.22); color: var(--app-text1); }
/* Point 2: par + distance promoted into the row title, not a side pill —
   read-only here (the actual editor lives in the expanded body's Par
   section below); this is the "at a glance" fact, not the control. */
.strategy-hole-title { font-size: 0.88rem; font-weight: 700; color: #fff; white-space: nowrap; }
.strategy-hole-sub {
  flex: 1; min-width: 0; font-size: 0.8rem; color: var(--app-text3);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Unplanned rows dim as a WHOLE block — title along with the sub-text, not
   just the "Not planned yet" caption — so the eye lands on planned rows
   first and the unplanned ones read as one clear group to work through. */
.strategy-hole-row.unset .strategy-hole-title { color: rgba(255,255,255,0.42); font-weight: 700; }
.strategy-hole-row.unset .strategy-hole-sub   { color: rgba(255,255,255,0.32); }
.strategy-chev {
  color: var(--app-text3); font-size: 0.75rem; flex-shrink: 0;
  transition: transform 0.15s ease;
}
.strategy-hole-row.open .strategy-chev { transform: rotate(90deg); }

/* Expanded editor — Par / Planned shots / Pin & distance / Green / Note,
   indented to sit under the row title rather than the badge. */
.strategy-hole-body { padding: 4px 4px 16px 40px; }
/* Lifted CARD surface for the expanded body — its own fill, border, and
   inset highlight, instead of text floating on the same dark floor the
   collapsed rows sit on. This was the single biggest fix for "everything
   blends": previously there was no surface change at all between closed
   and open, just indentation. */
.strategy-hole-card {
  background: #123d27; border: 1px solid rgba(255,255,255,0.12); border-radius: 12px;
  padding: 14px 14px 16px; margin: 2px 0 10px;
  box-shadow: inset 0 1px 0 rgba(255,255,255,0.05);
}
/* 0.62rem (≈9.9px) → 0.8125rem (13px), per two rounds of Ged's feedback
   ("the font [is] too small" on the planner) — this was the single
   smallest text anywhere on the Plan tab; everything else already sat at
   12px or larger. */
.strategy-sec-label {
  font-size: 0.8125rem; font-weight: 700; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--app-text3);
  margin: 12px 0 6px;
}
.strategy-sec-label:first-child { margin-top: 0; }

/* Par + Pin & distance share one row (Ged: "can the Par and the set Pin be
   on the same line ... Par on the left and set pin on the right") instead
   of two full-width stacked sections. */
.strategy-parpin-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 14px; }
.strategy-parpin-col { display: flex; flex-direction: column; align-items: flex-start; min-width: 0; }

/* Par selector — scorecard scope doc Phase 1, relocated into the expanded
   body by the planner redesign (the row title above shows the value, this
   is where it's actually changed). appearance:none is load-bearing on iOS
   Safari — without it a native <select> renders at its own intrinsic
   width, ignoring any CSS width set here.
   Bug fix (Ged: "too much space around the P3, P4, P5 ... when you zoom in
   ... it shifts the Pin & Distance down because the pill is too large"):
   the app-wide `select, input[...] { flex: 1; min-width: 100px; }` base
   rule (meant for the Log-a-Shot form's wider dropdowns) was silently
   forcing this select to a 100px floor and letting it grow to fill its
   flex column, even though `.par-select` itself never asked for either —
   `.strategy-parpin-col` gave it to fill, then the sibling "Pin & distance"
   column had that much less room, which is what made its own longer label
   wrap under 1.3x zoom. `width`/`min-width`/`flex` here override that base
   rule so the pill sizes to its own short "P3"/"P4"/"P5" content again,
   matching the plain (non-<select>) pill the approved mockup used. */
.par-select {
  -webkit-appearance: none; -moz-appearance: none; appearance: none;
  font-size: 0.78rem; font-weight: 700; letter-spacing: 0.02em;
  background: var(--app-bg); border: 1px solid var(--app-accent-fill); border-radius: 8px;
  color: var(--app-text3); padding: 6px 10px; text-align: center; text-align-last: center;
  cursor: pointer;
  width: auto; min-width: 0; flex: 0 0 auto;
}
.par-select.par-set { background: var(--app-pill-bg); color: var(--app-text3); border-color: var(--app-accent-fill); }

/* Pin/clear button pair, and the button group's own row — grouped visually
   (rounded-left pin button + rounded-right red clear button sharing a
   border), same pairing pattern the approved mockup used. */
.strategy-pin-btn-row { display: flex; }

/* Horizontal wrapping list of club chips (point 4: plain chips + one
   "+ Add shot" chip, replacing the old per-shot dropdown-in-a-chip
   pattern). */
.strategy-clubs-row {
  display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
}
/* Solid, saturated tags (colour-refinement round 2) — white bold text on a
   bright status green, instead of a dark outline sitting on a dark row, so
   a planned club reads at a glance as "a chosen thing," not a border
   artifact. */
.strategy-club-chip {
  display: flex; align-items: center;
  background: #2e8b57; border: 1px solid #3fae70;
  border-radius: 8px; overflow: hidden; font-size: 0.8rem;
}
.chip-club-name { padding: 6px 4px 6px 10px; color: #fff; font-weight: 700; }
.btn-rm-shot { background: none; border: none; color: #eafff0; opacity: 0.85; font-size: 1rem; padding: 0 8px 0 4px; cursor: pointer; line-height: 1; }
.btn-cancel-addshot {
  background: none; border: none; color: #ff8a8a;
  font-size: 1rem; padding: 0 8px 0 4px; cursor: pointer; line-height: 1;
}
/* "+ Add shot" — a dashed outline rather than a filled chip, so it still
   reads as "there's room for another shot here" without competing with the
   now much brighter, filled club chips beside it. */
.btn-add-shot {
  background: transparent; border: 1.5px dashed #4fbd82; border-radius: 8px;
  color: #8ff0b6; font-size: 0.8rem; font-weight: 800;
  padding: 6px 11px; cursor: pointer; line-height: 1.2;
}
/* The add-shot picker itself reuses the .strategy-club-chip shell so it
   sits visually where the "+ Add shot" chip it replaced was. */
.strategy-addshot-chip select {
  /* appearance:none is load-bearing, not decorative — without it Chromium/
     Safari paint the <select>'s own native widget chrome (a solid white
     pill) regardless of the background set below, which is exactly what
     showed up when this was first tested against the real app. */
  -webkit-appearance: none; -moz-appearance: none; appearance: none;
  font-size: 0.8rem; padding: 6px 4px 6px 10px; border: none; border-radius: 0;
  background: transparent; color: #fff; max-width: 150px;
}
/* The <select>'s own popup is a separate rendering surface that does NOT
   inherit the transparent background above — without this the dropdown
   list itself came up white-on-white (caught on the approved mockup
   before this was ever built). */
.strategy-addshot-chip select option { background: var(--app-card); color: var(--app-text1); }

/* Note — collapsed by default (point 5: "+ Add note" instead of an
   always-visible empty field per hole). */
.strategy-note-sub { font-size: 0.82rem; padding: 7px 9px; }
.btn-add-note {
  background: none; border: none; color: var(--app-text3);
  font-weight: 600; font-size: 0.8rem; padding: 0; cursor: pointer;
}

/* Lay-Up Assist (Tour tier), Plan-tab "Lay-Up Zone" card —
   golf-caddie-layup-confidence-scope.md. Sits inside the expanded body
   now (planner redesign), indentation coming from .strategy-hole-body's
   own padding rather than a hardcoded margin. Mocked up at
   https://claude.ai/code/artifact/8d4ea76f-f915-43e0-a666-330faa8f24bd and
   approved before building. */
.zone-card {
  margin-top: 10px;
  background: var(--app-card-alt);
  border: 1px solid var(--app-accent-fill);
  border-radius: 10px;
  padding: 9px 11px;
}
.zone-card-head { display: flex; align-items: center; gap: 7px; margin-bottom: 5px; }
.zone-card-ic { font-size: 0.85rem; }
.zone-card-title { font-size: 0.78rem; font-weight: 700; color: var(--app-text1); }
.zone-card-sub { font-size: 0.72rem; color: var(--app-text3); line-height: 1.4; }
.zone-card-veto {
  font-size: 0.68rem; color: #e0c073; margin-top: 5px; line-height: 1.4;
  text-decoration: line-through; opacity: 0.8;
}
.zone-card-final { font-size: 0.68rem; color: var(--app-text3); margin-top: 2px; line-height: 1.4; font-weight: 600; }

/* Start Round button — inline variant (header row) */
.btn-start-round-inline {
  margin-left: auto; flex-shrink: 0;
  padding: 6px 12px; background: var(--app-accent-fill); border: none;
  border-radius: 8px; color: var(--app-accent-ontext);
  font-size: 0.82rem; font-weight: 700; cursor: pointer;
  white-space: nowrap;
}
.btn-start-round-inline:active { background: #256f47; }

/* Active round: header */
.round-header {
  display: flex; justify-content: space-between; align-items: center;
}
.round-course { font-size: 0.82rem; color: #a8c9b6; }
.btn-end-round {
  padding: 8px 14px; background: #5a5a5a; border: none;
  border-radius: 8px; color: white; font-size: 0.85rem; font-weight: 600; cursor: pointer;
}

/* Active round: shot navigator (appears between hole nav and plan card) */
.round-shot-nav {
  display: flex; align-items: center; justify-content: space-between;
  background: var(--app-bg); border: 1px solid var(--app-accent-fill); border-radius: 10px; padding: 6px 10px;
}
.shot-nav-btn {
  background: var(--app-card-alt); border: 1px solid var(--app-accent-fill); border-radius: 6px;
  color: var(--app-text3); font-size: 1.3rem; padding: 4px 14px; cursor: pointer;
}
.shot-nav-btn:disabled { opacity: 0.25; }
.round-shot-label { font-size: 0.88rem; font-weight: 700; color: var(--app-text2); }

/* Active round: hole navigator */
.round-hole-nav {
  display: flex; align-items: center; justify-content: space-between;
  background: var(--app-header); border-radius: 14px; padding: 10px 10px;
}
.hole-nav-btn {
  background: var(--app-accent-fill); border: none; border-radius: 8px;
  color: var(--app-accent-ontext); font-size: 1.1rem; padding: 10px 18px; cursor: pointer;
}
.hole-nav-btn:disabled { opacity: 0.3; }
.round-hole-display { text-align: center; line-height: 1; }
.round-hole-label {
  display: block; font-size: 0.68rem; color: var(--app-text3);
  text-transform: uppercase; letter-spacing: 0.1em; margin-bottom: 2px;
}
.round-hole-num { display: block; font-size: 2.8rem; font-weight: 700; color: #fff; }

/* ── Scorecard row (2026-08-07) — sits above the Play/Detail switch,
   visible on both sub-tabs; see the Option-C mockup this matches. ────── */
.round-score-row {
  display: flex; align-items: center; justify-content: space-between;
  background: var(--app-header); border-radius: 14px; padding: 10px 10px;
}
.score-total-badge {
  display: flex; flex-direction: column; align-items: center; line-height: 1.1;
  padding: 6px 14px; border-radius: 10px; background: var(--app-bg); min-width: 68px;
  /* 2026-08-26: always-on 1px edge in var(--app-text3) — the badge relied
     purely on a background-contrast gap against the row behind it to read
     as a separate shape, which narrows enough under Sunlight to nearly
     disappear. Invisible extra contrast under Standard, the actual fix
     under Sunlight. */
  border: 1px solid var(--app-text3);
}
.score-total-num { font-size: 1.3rem; font-weight: 800; color: var(--app-text2); }
.score-total-num.under { color: #6fe08a; }
.score-total-num.even  { color: var(--app-text2); }
.score-total-num.over  { color: #e0a030; }
.score-thru {
  font-size: 0.6rem; color: var(--app-text3); text-transform: uppercase;
  letter-spacing: 0.08em; margin-top: 2px;
}
.stroke-stepper { display: flex; align-items: center; gap: 10px; }
.stroke-btn {
  background: var(--app-accent-fill); border: none; border-radius: 8px;
  color: var(--app-accent-ontext); font-size: 1.2rem; font-weight: 700; padding: 6px 15px;
  cursor: pointer; line-height: 1;
}
.stroke-btn:active { background: #256f47; }
.stroke-btn:disabled { opacity: 0.35; cursor: default; }
/* Same box recipe as .score-total-badge (padding/radius/background/
   min-width) so the stroke-entry pill is visually the same height and
   width as the under/even/over badge on the other side of the row —
   Ged's 2026-08-07 request. Deliberately the exact same properties rather
   than copied pixel values, so the two stay in sync if the badge's own
   sizing ever changes. MOCKUP as of this change — not yet shipped in a
   version bump, pending Ged's look before it's finalized. */
.stroke-display {
  display: flex; flex-direction: column; align-items: center; line-height: 1.1;
  padding: 6px 14px; border-radius: 10px; background: var(--app-bg); min-width: 68px;
  /* Same Sunlight-visibility fix as .score-total-badge, 2026-08-26 — see
     its comment above. */
  border: 1px solid var(--app-text3);
}
/* #stroke-count-input (ID), not .stroke-count-input (class) — this needs
   to beat "select, input[type=number], input[type=text]" further up this
   file, and that base rule's specificity is actually element+attribute
   (0,0,1,1: the "input" tag counts too), which OUTRANKS a same-order class
   selector (0,0,1,0) regardless of which comes later in the file. Spent a
   round of measurement confirming a plain class override silently lost to
   that base rule specifically on min-width, which is what was blowing this
   pill out wider than .score-total-badge despite width:2ch being set. The
   ID selector settles it outright. */
#stroke-count-input {
  /* A fixed character-based width, NOT 100% — inside a shrink-to-fit flex
     column (no align-items:stretch here, to match .score-total-badge's own
     center-aligned children), a percentage width on the only sizeable
     child resolves against the browser's native <input type="number">
     default width instead of the container's min-width/padding recipe,
     which would otherwise blow the pill out much wider than the badge
     it's supposed to match. 2 digits covers the overwhelming majority of
     holes; a rare triple-digit-adjacent score just overflows the box
     slightly rather than resizing it, which is fine here. */
  display: block; width: 2ch; min-width: 0; flex: none;
  font-size: 1.3rem; font-weight: 800; color: #fff; line-height: 1;
  text-align: center; font-family: inherit;
  background: transparent; border: none;
  border-radius: 0; padding: 0;
  -moz-appearance: textfield;
}
#stroke-count-input::-webkit-outer-spin-button,
#stroke-count-input::-webkit-inner-spin-button {
  -webkit-appearance: none; margin: 0;
}
#stroke-count-input:focus {
  outline: 2px solid #6fe08a; outline-offset: 2px;
  border-radius: 4px;
}
.stroke-par-label {
  /* Matches .score-thru's sizing exactly — same reasoning as .stroke-display
     above, now that the two pills sit side by side in the same row. */
  display: block; font-size: 0.6rem; color: var(--app-text3);
  text-transform: uppercase; letter-spacing: 0.08em; margin-top: 2px;
}

/* ── Play/Detail sub-tabs (2026-08-07, scorecard scope doc Option C) ──
   Round mode's own internal switch, separate from the top-level tab bar —
   Play shows only what's actionable mid-swing; Detail holds the rest. ── */
.subtab-row {
  display: flex; gap: 6px; background: var(--app-header); border-radius: 10px; padding: 3px;
}
.subtab-btn {
  flex: 1; padding: 7px 4px; background: none;
  /* 2026-08-26: always-on 1px edge, unselected state only — relied purely
     on a background-contrast gap against the row behind it, which narrows
     enough under Sunlight to nearly disappear. Invisible extra contrast
     under Standard, the actual fix under Sunlight. */
  border: 1px solid var(--app-text3); border-radius: 7px;
  color: var(--app-text3); font-size: 0.85rem; font-weight: 700; cursor: pointer;
}
.subtab-btn.active { background: var(--app-accent-fill); border-color: var(--app-accent-fill); color: var(--app-accent-ontext); }
.subview { display: none; flex-direction: column; gap: 12px; }
.subview.active { display: flex; }

/* Round-mode sticky header (2026-08-11) — same recipe as the Plan tab's
   .strategy-sticky-header (v141): pinned to the top of #strategy-content
   (the tab's own existing scroll container, shared by both Plan and Round
   mode — no second scroll region needed), opaque background matching
   html/body's #0b3d20 so scrolled content never shows through underneath.
   Whether the score card is inside this wrapper depends on which Round
   sub-tab is active — see renderRoundMode()'s stickyHeaderHtml in app.js
   for why.
   Deliberately NOT display:flex (unlike the very first shipped version,
   v146) and its outer wrapper in app.js is deliberately plain
   `padding:16px`, not flex, either (2026-08-12 fix) — a sticky element that
   is itself a flex item of a flex-direction:column parent, and/or a flex
   container itself, is a well-known iOS Safari/WebKit combination that can
   silently stop sticking after a scroll-direction change (works fine in
   Chromium, which is how this shipped without the bug being caught in
   testing — Ged found it on a real iPhone: "When I scroll up and then
   scroll down again the buttons move down with the scroll"). The Plan
   tab's .strategy-sticky-header never had this problem because it was
   never given flex in the first place (its own children get their spacing
   from ordinary margins, not a flex gap) and its own wrapper
   (`renderPlanMode()`'s outer `<div style="padding:16px">`) was always
   plain block too — this fix makes Round mode match that already-working
   structure instead of introducing a second, different pattern. Internal
   spacing between this header's children (round-header / round-hole-nav /
   subtab-row / score-row) now comes from the child-combinator margin rule
   directly below instead of flex gap. */
.round-sticky-header {
  position: sticky;
  top: 0;
  z-index: 3;
  background: var(--app-bg);
  margin-bottom: 12px;
  /* Same iOS Safari compositing fix as .stats-sticky-header (see its
     comment in this file). */
  transform: translateZ(0);
  -webkit-transform: translateZ(0);
  will-change: transform;
}
.round-sticky-header > * + * {
  margin-top: 12px;
}

/* Active round: plan display */
.round-plan {
  background: var(--app-card-alt); border-radius: 10px; padding: 12px 14px;
}
.round-plan-label {
  display: block; font-size: 0.7rem; color: #a8c9b6;
  text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 4px;
}
.round-plan-club { display: block; font-size: 1.5rem; font-weight: 700; color: #fff; }
.round-plan-note { display: block; font-size: 0.82rem; color: var(--app-text3); margin-top: 5px; font-style: italic; }

/* Detect hole button */
.btn-detect-hole {
  display: block; width: 100%; padding: 12px;
  background: var(--app-card-alt); border: 1px solid var(--app-accent-fill);
  border-radius: 10px; color: var(--app-text2);
  font-size: 0.9rem; font-weight: 600; cursor: pointer;
}

/* ── Pin-set mode bar (Range tab) ───────────────────────────────── */
.pin-set-bar {
  background: #06200f;
  border-top: 2px solid var(--app-accent-fill);
  padding: 8px 12px;
  align-items: center;
  gap: 8px;
}
.pin-set-bar span {
  flex: 1;
  font-size: 0.82rem;
  color: var(--app-text3);
  font-weight: 700;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.pin-set-bar #btn-save-pin {
  flex: 0 0 auto;
  background: var(--app-accent-fill);
  min-width: 90px;
  font-size: 0.82rem;
}
.pin-set-bar #btn-cancel-pin {
  flex: 0 0 auto;
  background: #5a5a5a;
  min-width: 36px;
  font-size: 0.85rem;
}

/* Per-hole pin button in Plan tab — retokenized 2026-08-26 (Ged, "Green
   Spots Round 2" item 12). */
.btn-set-pin {
  flex-shrink: 0;
  padding: 6px 9px;
  border: 1px solid var(--app-accent-fill);
  border-radius: 8px 0 0 8px;
  background: var(--app-bg);
  color: var(--app-text3);
  font-size: 0.75rem;
  cursor: pointer;
  white-space: nowrap;
  line-height: 1;
}
.btn-set-pin.has-pin {
  background: var(--app-pill-bg);
  color: var(--app-text3);
  border-color: var(--app-accent-fill);
}
/* Auto-placed (unconfirmed) pin — a green-polygon centroid guess, not yet
   confirmed by the golfer. Amber/dashed so it never reads identically to a
   confirmed pin. See golf-caddie-auto-pin-scope.md. */
.btn-set-pin.pin-estimated {
  background: #3d2f0b;
  color: #e0c069;
  border-color: #a3852e;
  border-style: dashed;
}
/* Clear-pin × button — sits right of the pin button, shares the border */
.btn-clear-pin {
  flex-shrink: 0;
  padding: 6px 8px;
  border: 1px solid var(--app-accent-fill);
  border-left: none;
  border-radius: 0 8px 8px 0;
  background: var(--app-card);
  color: #ff8a8a;
  font-size: 1rem;
  cursor: pointer;
  line-height: 1;
}

/* Walk & Log — elevation mapping button, below the pin row. Base ("no
   samples yet") state retokenized 2026-08-26 (Ged, "Green Spots Round 2"
   item 12) — the .has-samples/.active states below were already correct;
   only this rest state was hardcoded green. */
.strategy-elev-row { display: flex; } /* indentation now comes from .strategy-hole-body's own padding */
.btn-map-green {
  flex-shrink: 0;
  padding: 5px 9px;
  border: 1px solid var(--app-accent-fill);
  border-radius: 8px;
  background: var(--app-bg);
  color: var(--app-text3);
  font-size: 0.72rem;
  cursor: pointer;
  white-space: nowrap;
  line-height: 1;
}
.btn-map-green.has-samples {
  background: var(--app-bg);
  color: var(--app-text3);
  border-color: var(--app-accent-fill);
}
.btn-map-green.active {
  background: #b33a2a;
  border-color: #b33a2a;
  color: #fff;
}
/* Reset button — clears a hole's logged elevation samples entirely */
.btn-reset-elev {
  flex-shrink: 0;
  margin-left: 6px;
  padding: 5px 8px;
  border: 1px solid #4a1e1e;
  border-radius: 8px;
  background: #2e0d0d;
  color: #ff8a8a;
  font-size: 0.85rem;
  cursor: pointer;
  line-height: 1;
}

/* Hole distance badge in Plan tab (shown when tee+pin both saved) */
.hole-dist-badge {
  font-size: 0.75rem;
  color: var(--app-text2);
  font-weight: 600;
  margin-left: 3px;
  opacity: 0.85;
}

/* Centered distance-to-pin display in round mode */
.round-dist-display {
  background: var(--app-header);
  border: 1px solid var(--app-accent-fill);
  border-radius: 14px;
  padding: 12px 14px 10px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
}
.round-dist-label {
  font-size: 0.68rem;
  /* Retokenized 2026-08-26 (Ged, "Green Spots Round 2" item 4) — was a
     hardcoded #b3f0cc (itself a 2026-08-20 brightening of an earlier
     hardcoded green), now follows the live theme. */
  color: var(--app-text3);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}
.round-dist-value {
  font-size: 3.5rem;
  font-weight: 700;
  color: #fff;
  line-height: 1;
  letter-spacing: -0.01em;
  text-align: center;
}
.btn-plan-gps {
  padding: 7px 18px;
  border: none;
  border-radius: 8px;
  background: var(--app-card-alt);
  border: 1px solid var(--app-accent-fill);
  color: var(--app-text3);
  font-size: 0.82rem;
  font-weight: 600;
  cursor: pointer;
}
.btn-plan-gps.active {
  background: #b33a2a;
  border-color: #b33a2a;
  color: #fff;
}
.btn-plan-gps:disabled {
  opacity: 0.35;
  cursor: default;
}

/* ── Mark + Live Dist button row (inside round-dist-display) ─────── */
.round-dist-btn-row {
  display: flex;
  gap: 8px;
  justify-content: center;
}
/* Play-tab Mark button — 2026-08-26 (Ged, "Green Spots Round 2" item 3): a
   second, separate Mark button from the Range tab's #btn-range-mark (round-
   mode caddie card, Play view) that had its own hardcoded green the whole
   time and was never touched by the earlier colour pass. Retokenized to the
   exact same pattern as #btn-range-mark so both Mark buttons match. */
.btn-mark-shot {
  padding: 7px 14px;
  border-radius: 8px;
  background: var(--app-accent-fill);
  border: 1px solid var(--app-accent-fill);
  color: var(--app-accent-ontext);
  font-size: 0.82rem;
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  transition: background 0.15s, border-color 0.15s;
}
.btn-mark-shot svg { flex-shrink: 0; }
.btn-mark-shot.marked {
  background: var(--app-card-alt);
  border-color: var(--app-accent-fill);
  color: var(--app-text1);
  box-shadow: 0 0 0 2px rgba(255,255,255,0.22);
}
.btn-mark-shot:active { opacity: 0.75; }
.carry-hint {
  font-size: 0.74rem;
  color: rgba(255,255,255,0.4);
  font-style: italic;
  text-align: center;
  line-height: 1.3;
}
/* GPS accuracy tightening Phase 3 — round-mode caddie card has no
   courseStatusEl-equivalent status line, so the weak-signal/locating
   feedback reuses the existing carry-hint slot with a warning treatment
   borrowed from .caddie-low-conf-warn's amber palette. */
.carry-hint-warn {
  color: #c8a84b;
  font-style: normal;
}

/* ── Caddie card v57 ─────────────────────────────────────────────── */
.caddie-card {
  /* Retokenized 2026-08-26 (Ged, colour-consistency pass) — was hardcoded
     to its own near-black green + green edge, so it only matched
     .round-dist-display above it by coincidence under Standard Green.
     Now reuses the exact same two tokens, so the edge goes blue under
     Logo Navy right along with everything else. */
  background: var(--app-header);
  border: 1px solid var(--app-accent-fill);
  border-radius: 14px;
  padding: 14px 14px 12px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* Risk-Aware Targeting row (Tour only), golf-caddie-optimal-target-
   scope.md Phases 1-2 — sits between shot-nav and the club section.
   Two states: a retarget (sage, borrowed from .tier-badge-tour's Tour
   accent) and a quiet non-retargeting note (amber, borrowed from
   .caddie-low-conf-warn's palette) — same "quiet, not alarming" register
   as the rest of this card's advisory-style rows. */
.risk-advisory {
  display: flex;
  flex-direction: column;
  gap: 4px;
  border-radius: 8px;
  padding: 7px 9px;
  font-size: 0.82rem;
}
.risk-advisory-badge {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 700;
  width: fit-content;
  padding: 1px 6px;
  border-radius: 4px;
}
.risk-advisory-retarget {
  background: rgba(143,163,150,0.14);
  border: 1px solid #4c6b58;
}
.risk-advisory-retarget .risk-advisory-badge {
  background: #8FA396;
  color: #082b16;
}
.risk-advisory-retarget .risk-advisory-text { color: #cfe0d6; }
.risk-advisory-note {
  background: #1e1600;
  border: 1px solid #6e5a1a;
}
.risk-advisory-note .risk-advisory-badge {
  background: #6e5a1a;
  color: #f0dfa0;
}
.risk-advisory-note .risk-advisory-text { color: #c8a84b; }

/* Lay-Up Assist (Tour tier), golf-caddie-layup-confidence-scope.md — a
   quiet reasoning note ("no club confidently covers this") plus a distinct
   sage/Tour advisory card, same palette family as .risk-advisory-retarget
   but its own badge label so it never reads as Risk-Aware Targeting
   itself. Mocked up at
   https://claude.ai/code/artifact/8d4ea76f-f915-43e0-a666-330faa8f24bd and
   approved before building. Shares the caddie card's advisory slot with
   .risk-advisory/teeShotAdvisoryHtml's output, mutually exclusive by
   construction (renderRoundMode()). */
.no-cover-note {
  font-size: 0.78rem;
  color: #c8a84b;
  background: #1e1600;
  border: 1px solid #6e5a1a;
  border-radius: 8px;
  padding: 7px 9px;
  line-height: 1.4;
}
.layup-advisory {
  display: flex;
  flex-direction: column;
  gap: 5px;
  border-radius: 8px;
  padding: 8px 10px;
  font-size: 0.82rem;
  background: rgba(143,163,150,0.14);
  border: 1px solid #4c6b58;
}
.layup-advisory-badge {
  font-size: 0.68rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 700;
  width: fit-content;
  padding: 1px 6px;
  border-radius: 4px;
  background: #8FA396;
  color: #082b16;
}
.layup-advisory-text { color: #cfe0d6; font-size: 0.95rem; font-weight: 600; }
.layup-advisory-why {
  font-size: 0.74rem;
  color: #9fb3a7;
  line-height: 1.45;
  border-top: 1px solid rgba(143,163,150,0.25);
  padding-top: 5px;
  margin-top: 1px;
}
.layup-advisory-why b { color: #cfe0d6; }

/* Shot nav inside caddie card */
.caddie-shot-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
  padding-bottom: 6px;
  border-bottom: 1px solid #1a4a2a;
}

/* Detail tab row-header blocks (Wind / Distance Adjustment / Dispersion),
   2026-08-10 — mocked up and approved by Ged before building. Each block
   gets a small caption label (same visual language as .caddie-col-head /
   .caddie-solo-head below) and blocks are separated by a thin divider,
   joined in JS via detailRowsHtml so an empty block never leaves a stray
   divider behind. */
.card-row {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.card-row-label {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  /* Brightened 2026-08-20 — mocked up and approved by Ged (Option A: was
     #6a9e7a, 5.56:1 contrast — hard to read outdoors at a glance). Shared
     by the Lie/Wind/Green/Pin row labels on both the Play and Details
     sub-tabs. Retokenized 2026-08-26 (Ged, "Green Spots Round 2" item 6) —
     the brightened value was still a hardcoded green, now follows theme. */
  color: var(--app-text3);
  font-weight: 700;
}
.card-row-divider {
  border-top: 1px solid rgba(255,255,255,0.12);
  margin: 2px 0;
}

/* Wind row */
.caddie-wind-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  min-width: 0;
}
.caddie-wind-label {
  font-size: 1.05rem;
  color: #fff;
  font-weight: 600;
  white-space: nowrap;
  flex-shrink: 0;
}
.wind-row-right {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}
/* Retokenized 2026-08-26 (Ged, "Green Spots Round 2" item 7) — this is the
   "Pin to target" pill Ged was asking about: the Wind row's source badge
   ("→ target" once a target is set), previously hardcoded green. */
.wind-src-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 0.78rem;
  color: var(--app-text3);
  white-space: nowrap;
  background: var(--app-header);
  border: 1px solid var(--app-accent-fill);
  border-radius: 6px;
  padding: 2px 7px;
  flex-shrink: 0;
}
.wind-src-badge.no-bearing {
  color: var(--app-text3);
  opacity: 0.6;
  border-color: rgba(255,255,255,0.15);
}
/* Saved-tee source icon: a small static version of the map's own .tee-dot
   marker (see that rule above) — same blue, same white ring, no pulse
   animation needed at badge scale. Sized down from an initial 10px mockup
   after Ged's feedback that it read too big next to the GPS pin icon. */
.badge-tee-dot {
  width: 7px;
  height: 7px;
  background: #2196F3;
  border-radius: 50%;
  border: 1px solid #fff;
  flex-shrink: 0;
}

/* Adjusted distance display */
.caddie-adj-dist {
  display: flex;
  align-items: baseline;
  gap: 8px;
  justify-content: center;
}
/* Retokenized 2026-08-26 (Ged, "Green Spots Round 2" item 5) — Play tab
   club/adjustment text, all previously hardcoded green regardless of theme. */
.caddie-adj-base  { font-size: 1.05rem; color: var(--app-text3); text-decoration: line-through; }
.caddie-adj-arrow { font-size: 0.88rem; color: var(--app-text3); }
.caddie-adj-plays { font-size: 1.4rem;  font-weight: 700; color: var(--app-text1); }
.caddie-adj-breakdown {
  font-size: 0.85rem;
  color: #c8e8d8;
  text-align: center;
}

/* Green depth/width row (2026-08-11) — golf-caddie-green-geometry-scope.md */
.caddie-green-stat {
  font-size: 0.95rem;
  color: #e0f5ea;
  font-weight: 600;
}
.caddie-green-approx {
  /* Brightened + enlarged 2026-08-20 — mocked up and approved by Ged
     (Option A colour, font-size taken to 0.8rem instead of Option A's
     original 0.82rem per Ged's follow-up: was 0.68rem/#6a9e7a, both too
     small and too dim to read at a glance — this is the pin-row numbers
     text, "19m on · 1m right · 11m to spare", and the "tap once you see
     the flag" hint). */
  font-size: 0.8rem;
  color: var(--app-text3);
  font-weight: 400;
  margin-left: 4px;
}

/* Pin row + 3x3 pin-zone quick-set sheet (2026-08-11) —
   golf-caddie-pin-zone-quickset-scope.md. Mocked up and approved by Ged
   before building; classes/values ported verbatim from
   pin_zone_round_mockup and pin_zone_numbers_mockup. */
.card-row.pin-row-tappable { cursor: pointer; }
.caddie-pin-row-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.caddie-pin-edit-hint {
  color: #4a7a60;
  font-size: 0.85rem;
  flex-shrink: 0;
}
/* Amber-ish, same family as the pin-estimated tone elsewhere — still just
   the plan's default, not a golfer-confirmed call yet. */
.caddie-pin-stat-default { color: #e0c069; }
/* Full-brightness once the golfer has actually dialed the pin in this round.
   Retokenized 2026-08-26 (Ged, "Green Spots Round 2" item 6). */
.caddie-pin-stat-confirmed { color: var(--app-text1); }

.pin-zone-overlay {
  position: fixed;
  inset: 0;
  background: rgba(6, 33, 15, 0.72);
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}
.pin-zone-sheet {
  width: 100%;
  max-width: 334px;
  background: var(--app-card);
  border: 1px solid var(--app-accent-fill);
  border-radius: 14px;
  padding: 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
/* Retokenized 2026-08-26 (Ged, "Green Spots Round 2" item 10) — the outer
   sheet was already tokenized (see .pin-zone-sheet above), the grid/cells
   inside it weren't. .current's amber ring is a status colour (unconfirmed
   selection) and stays as-is. */
.pin-zone-sheet-title {
  color: var(--app-text1);
  font-size: 0.85rem;
  font-weight: 700;
}
.pin-zone-sheet-sub {
  color: var(--app-text3);
  font-size: 0.7rem;
  margin-bottom: 2px;
}
.pin-zone-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(3, 1fr);
  gap: 4px;
  aspect-ratio: 0.72;
  background: var(--app-header);
  border: 1px solid var(--app-accent-fill);
  border-radius: 50% / 18%;
  padding: 10px;
}
.pin-zone-cell {
  background: var(--app-card-alt);
  border: 1px solid var(--app-accent-fill);
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  color: var(--app-text3);
  /* 0.68rem -> 0.84rem (2026-08-27, Ged: "text font is too small") — mocked
     up at https://claude.ai/code/artifact/91695c9e-166d-47b7-97e0-f8ddc2d27ea5
     and approved. Plenty of headroom in each ~90x140px cell, so this jump
     doesn't crowd or wrap oddly even for two-line labels like "Front Left". */
  font-size: 0.84rem;
  font-weight: 700;
  line-height: 1.15;
  cursor: pointer;
  font-family: inherit;
}
.pin-zone-cell.current {
  border-color: #e0c069;
  border-style: dashed;
}
.pin-zone-cancel {
  align-self: center;
  margin-top: 2px;
  background: none;
  border: none;
  color: var(--app-text3);
  font-size: 0.75rem;
  cursor: pointer;
}

/* Club comparison: Plan → Caddie */
.caddie-club-compare {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
}
.caddie-club-col {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  flex: 1;
}
/* Retokenized 2026-08-26 (Ged, "Green Spots Round 2" item 5). */
.caddie-col-head {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--app-text3);
}
.caddie-col-club {
  font-size: 1.7rem;
  font-weight: 700;
  line-height: 1.1;
}
.caddie-col-club.plan-club { color: var(--app-text2); }
.caddie-col-club.rec-club  { color: #fff; }
.caddie-club-arrow {
  font-size: 1.4rem;
  color: var(--app-accent-fill);
  flex-shrink: 0;
  padding-top: 16px;
}

/* Single club display */
.caddie-club-solo {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
}
/* Retokenized 2026-08-26 (Ged, "Green Spots Round 2" item 5). */
.caddie-solo-head {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--app-text3);
}
.caddie-solo-club {
  font-size: 2.4rem;
  font-weight: 700;
  color: #fff;
  line-height: 1.1;
}
/* Retokenized 2026-08-26 (Ged, "Green Spots Round 2" item 5). */
.caddie-wind-alt {
  font-size: 0.82rem;
  color: var(--app-text3);
  margin-top: 2px;
}

/* Dispersion band */
.caddie-disp {
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.caddie-disp-confrow {
  display: flex;
  align-items: center;
  justify-content: center;
}
/* Sized up from the topbar's inline .rec-prog-wrap defaults — here it sits
   alone, centered, on its own row rather than squeezed inline in a sentence,
   so it can afford to be a bit more readable. Same component/markup, no new
   classes on the JS side, just a context-specific size override. */
.caddie-disp-confrow .rec-prog-wrap { margin-left: 0; gap: 6px; }
.caddie-disp-confrow .rec-prog-bar { width: 46px; height: 8px; }
.caddie-disp-confrow .rec-prog-pct { font-size: 0.78rem; }
.caddie-disp-text { font-size: 0.92rem; color: #c8e8d8; text-align: center; }
.caddie-disp-bar {
  position: relative;
  height: 5px;
  background: linear-gradient(90deg, #1a4a2a 0%, #2e8b57 35%, #4fc3f7 50%, #2e8b57 65%, #1a4a2a 100%);
  border-radius: 3px;
}
.caddie-disp-fill {
  position: absolute;
  inset: 0;
  border-radius: 3px;
}
.caddie-disp-center {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 9px;
  height: 9px;
  background: #fff;
  border-radius: 50%;
  border: 2px solid #4fc3f7;
}
.caddie-disp-bounds {
  display: flex;
  justify-content: space-between;
  font-size: 0.82rem;
  color: #5aaa7a;
}
.caddie-low-conf-warn {
  font-size: 0.82rem;
  color: #c8a84b;
  background: #1e1600;
  border: 1px solid #6e5a1a;
  border-radius: 6px;
  padding: 5px 8px;
  text-align: center;
  margin-top: 2px;
}
.caddie-disp-hint {
  font-size: 0.88rem;
  color: #4a7a5a;
  text-align: center;
  font-style: italic;
}

/* No plan message */
.caddie-no-plan {
  font-size: 0.88rem;
  /* Brightened 2026-08-20 — mocked up and approved by Ged (Option A: was
     #5a8a6a, 4.33:1 contrast — actually below the WCAG AA minimum for
     normal text, not just a preference). */
  color: #8fd9ab;
  text-align: center;
  line-height: 1.4;
}

/* No-plan/no-history manual club picker (2026-08-20) — Ged: "unable to log
   any shots unless I enter clubs for each hole". Mocked up and approved:
   select + Log Result on one line, matching the Range tab's own
   .controls/#btn-log-shot row exactly. Deliberately NOT sharing the real
   .controls selector — that rule carries fixed-toolbar padding/background
   meant for the Range tab's own layout, which would look wrong inside the
   caddie card. select itself needs no new rule — it already inherits the
   app-wide `select { ... }` base styling; only flex/min-width are
   overridden here so it shares the row evenly with the button. */
.caddie-manual-log { border-top: 1px solid #1a4a2a; padding-top: 10px; }
.caddie-manual-log-label {
  font-size: 0.78rem;
  color: #7aaa90;
  text-align: center;
  margin: 0 0 8px;
}
.caddie-manual-log-row { display: flex; gap: 6px; }
.caddie-manual-log-row select { flex: 1; min-width: 0; }
.caddie-manual-log-row button {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  padding: 10px 4px;
  border: none;
  border-radius: 10px;
  background: var(--app-accent-fill);
  color: var(--app-accent-ontext);
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
}
.caddie-manual-log-row button:disabled { opacity: 0.4; }
.caddie-manual-log-row button svg { flex-shrink: 0; }

/* Crosswind aim tip — v57: white, slightly larger, sits below disp.
   Still used verbatim for entry tier / no-tendency-data Pro+Tour clubs —
   see Phase 3 combined variant below for when tendency data exists too. */
.caddie-cross-tip {
  font-size: 0.96rem;
  color: #fff;
  text-align: center;
  font-weight: 500;
}

/* Combined wind + club-tendency aim tip — Phase 3 (v135, 2026-08-10),
   mocked up and approved by Ged: the instruction stays big/prominent, the
   reasoning underneath stays small/quiet — a golfer only has a couple of
   seconds to read this mid-round. */
.aim-combined { text-align: center; }
.aim-combined .aim-big { font-size: 1.15rem; color: #fff; font-weight: 700; }
.aim-combined .aim-onLine { font-size: 1.02rem; color: var(--app-text3); font-weight: 600; }
.aim-combined .aim-breakdown { font-size: 0.74rem; color: #7aaa90; margin-top: 2px; }

/* Plan note */
.caddie-note {
  font-size: 0.96rem;
  color: #fff;
  font-style: italic;
  border-top: 1px solid #1a4a2a;
  padding-top: 8px;
}

/* ── Bottom action row: Log + Use Plan side by side (v57) ────────── */
.caddie-bottom-row {
  display: flex;
  gap: 8px;
}

/* Override buttons (now flex items in bottom row) */
.btn-caddie-override {
  flex: 1;
  padding: 9px 10px;
  border-radius: 9px;
  font-size: 0.88rem;
  font-weight: 600;
  cursor: pointer;
  text-align: center;
  border: 1px solid;
}
.btn-caddie-override.plan {
  background: var(--app-card);
  border-color: #2e5e3a;
  color: var(--app-text3);
}
.btn-caddie-override.follow {
  background: #1a3d20;
  border-color: #3aad6e;
  color: #c8f5da;
}

/* Legacy */
.round-wind-card { display: none; }

/* ── Wind refresh (caddie card) ─────────────────────────────────── */
.caddie-wind-age {
  font-size: 0.72rem;
  color: rgba(255,255,255,0.4);
  flex-shrink: 0;
  align-self: center;
}
.btn-caddie-wind-refresh {
  background: none;
  border: none;
  color: rgba(255,255,255,0.45);
  font-size: 1.1rem;
  padding: 0 2px;
  cursor: pointer;
  line-height: 1;
  flex-shrink: 0;
}
.btn-caddie-wind-refresh:active { color: var(--app-text3); }

/* ── Brass primary button (Log Result) ───────────────────────────── */
.btn-caddie-primary {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 9px 14px;
  background: #C9A227;
  border: none;
  border-radius: 9px;
  color: #10241C;
  font-size: 0.9rem;
  font-weight: 700;
  cursor: pointer;
  letter-spacing: 0.01em;
}
.btn-caddie-primary svg { flex-shrink: 0; }
.btn-caddie-primary:active {
  opacity: 0.82;
  transform: scale(0.98);
}

/* ── Quick shot log ──────────────────────────────────────────────── */
.btn-caddie-log-open {
  flex: 1;
  display: block;
  padding: 9px 14px;
  background: #0a2416;
  border: 1px solid #1e4a2e;
  border-radius: 9px;
  color: #6aaa8a;
  font-size: 0.88rem;
  font-weight: 600;
  cursor: pointer;
  text-align: center;
}
.caddie-quick-log {
  border-top: 1px solid #1a4a2a;
  padding-top: 10px;
}
.quick-log-label {
  display: block;
  font-size: 0.8rem;
  color: #7aaa90;
  margin-bottom: 6px;
}
.quick-log-row {
  display: flex;
  align-items: center;
  gap: 6px;
}
.caddie-log-input {
  flex: 0 0 72px;
  font-size: 1rem;
  padding: 8px;
  background: var(--app-card);
  border: 1px solid #2a6e47;
  border-radius: 8px;
  color: #fff;
  text-align: center;
}
.caddie-log-unit {
  font-size: 0.85rem;
  color: #7aaa90;
  flex-shrink: 0;
}
.btn-caddie-log-ok {
  flex: 1;
  background: var(--app-accent-fill);
  border: none;
  border-radius: 8px;
  color: var(--app-accent-ontext);
  font-size: 0.9rem;
  font-weight: 600;
  padding: 9px 10px;
  cursor: pointer;
}
.btn-caddie-log-cancel {
  flex: 0 0 auto;
  background: #1a2e20;
  border: 1px solid #2a4a30;
  border-radius: 8px;
  color: #7aaa90;
  font-size: 0.9rem;
  padding: 9px 10px;
  cursor: pointer;
}

/* ── Range tab quick shot log (mirrors caddie-quick-log) ─────────── */
.range-quick-log {
  display: flex;
  flex-direction: column;
  padding: 8px 12px;
  /* Retokenized 2026-08-26 — same hardcoded drawer green as the search
     bar/control-row/status-line above it; border-top switched to the same
     neutral separator .range-handle already uses, since a fixed dark-green
     hairline would read oddly against a now-blue panel under Logo Navy. */
  background: var(--app-header);
  border-top: 1px solid rgba(255,255,255,0.05);
  flex-shrink: 0;
}
.range-quick-log .quick-log-label { margin-bottom: 6px; }

/* ── Lie chip row (Lie Phase 1) ────────────────────────────────────
   Same 5-chip control at all three shot-log points (Clubs tab, Range tab,
   caddie card). Which chip starts selected can vary (the caddie card
   pre-selects "Tee" via GPS proximity — see isLikelyTeeShot() in app.js)
   but the control itself, and this styling, is identical everywhere. */
.lie-chip-row {
  width: 100%;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 8px;
}
/* Retokenized 2026-08-26 (Ged, "Green Spots Round 2" item 13) — unselected
   lie chip; .selected state below was already correct. Shared by the
   Range-tab and caddie-card lie rows too (see comment above), so this
   fixes all three surfaces at once. */
.lie-chip {
  flex: 0 0 auto;
  background: var(--app-card);
  border: 1px solid var(--app-accent-fill);
  border-radius: 14px;
  color: var(--app-text3);
  font-size: 0.78rem;
  font-weight: 600;
  padding: 6px 12px;
  cursor: pointer;
}
.lie-chip.selected {
  background: var(--app-accent-fill);
  border-color: var(--app-accent-fill);
  color: var(--app-accent-ontext);
}

/* ── Tier scaffold: gated-feature card + dev toggle (2026-08-04) ────
   One reusable "locked" card style for any not-yet-accessible feature
   (see gatedCardHtml() in app.js), plus the Clubs-tab dev-only tier
   switcher used to preview it before real checkout exists. */
.gated-card {
  background: var(--app-card);
  border: 1px solid #3a3320;
  border-radius: 12px;
  padding: 12px 14px;
  margin: 10px 0;
}
.gated-card-top { display: flex; align-items: center; gap: 8px; }
.gated-lock { font-size: 1rem; flex-shrink: 0; }
.gated-card-title { flex: 1; font-weight: 700; font-size: 0.9rem; color: #EFE7D6; }
.gated-tier-chip {
  font-size: 0.66rem;
  font-weight: 800;
  letter-spacing: 0.03em;
  padding: 2px 8px;
  border-radius: 20px;
  background: #C9A227; /* fallback — always overridden below, minTier/getTier() is always "pro" or "tour" */
  color: #082b16;
  flex-shrink: 0;
}
/* Tier-coloured chips (2026-08-20, extends the Silver/Gold/Platinum header-
   badge treatment to the Stats-tab cards) — same two colours as
   .tier-badge-pro/.tier-badge-tour, applied here to both the locked
   "Unlock with Pro/Tour" teaser chip and the unlocked "you have this tier"
   chip. Supersedes the old flat gold-locked/sage-unlocked split, which
   didn't differentiate Pro from Tour at all on this screen. */
.gated-tier-chip-pro  { background: #C9A227; color: #082b16; }
.gated-tier-chip-tour { background: linear-gradient(135deg, #A2ACB5, #7C858C); color: #082b16; }
/* First Caddie ("intro") minTier gatedCardHtml() use (Your Courses, v239)
   — every prior call passed "pro"/"tour" only (see the fallback comment
   above), so there was no colour for the entry paid tier's chip yet. Uses
   the app's own core accent green rather than borrowing Pro's gold or
   Tour's silver, so Caddie reads as its own rung, not a dimmer Pro. */
.gated-tier-chip-intro { background: var(--app-accent-fill); color: #fff; }
/* Retokenized 2026-08-26 (Ged, "Green Spots Round 2" item 19) — blurb text
   only; gold border/title/Unlock button are the reserved Pro-tier gold and
   stay as-is. */
.gated-card-blurb { font-size: 0.78rem; color: var(--app-text3); line-height: 1.45; margin: 8px 0 10px; }
.gated-upgrade-btn {
  width: 100%;
  padding: 8px;
  border: 1px solid #C9A227;
  border-radius: 8px;
  background: none;
  color: #C9A227;
  font-size: 0.82rem;
  font-weight: 700;
  cursor: pointer;
}
.gated-upgrade-btn:active { background: rgba(201, 162, 39, 0.15); }
.gated-card-unlocked { border-color: #2a6e47; }

/* Collapsed-by-default locked teaser (2026-08-11), mocked up and approved
   by Ged: title + tier chip only until tapped, saving the vertical space two
   (soon three) always-open teasers were costing on the Stats tab. Uses the
   same chevron-rotation + expand-animation collapse mechanics the Clubs
   tab's old .csec accordion sections used (since replaced by the section-
   button row, 2026-08-23 — this card's own mechanics are independent and
   unaffected) rather than a new component — only .gated-card-top (the
   *unlocked* card header, still used by renderLieStatsHtml()/
   renderShotTendencyHtml() above) is untouched by this. */
.gated-card-collapsible { padding: 0; overflow: hidden; }
.gated-card-toggle {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 14px;
  background: none;
  border: none;
  cursor: pointer;
  text-align: left;
  font: inherit;
  color: inherit;
}
.gated-card-toggle .gated-lock { font-size: 1rem; }
.gated-card-toggle .gated-card-title { flex: 1; }
.gated-chevron { font-size: 0.85rem; transition: transform 0.3s ease; flex-shrink: 0; color: var(--app-text3); }
.gated-card-collapsible.collapsed .gated-chevron { transform: rotate(-90deg); }
/* max-height transition (2026-08-23, bug fix — was grid-template-rows 1fr/0fr):
   Ged reported that expanding Decision Intelligence/Risk-Aware Targeting
   rendered their content over the new Stats-tab section buttons, blocking
   taps on them. The CSS-grid-row-animation technique this used to use is a
   known source of transient layout/hit-testing desync on iOS Safari
   specifically — the card's own overflow:hidden stopped anything from
   being visible outside its box, but tap targets nearby could still land on
   stale (pre-animation) coordinates while the grid track was mid-animation.
   max-height is the standard, reliable cross-browser alternative for a
   collapsible panel; 800px is a generous ceiling no real card content
   reaches (the tallest scrollable content inside these cards, e.g.
   .lie-stats-scroll, already caps itself at 45vh independently). */
.gated-body-wrap { max-height: 800px; overflow: hidden; transition: max-height 0.3s ease; }
.gated-card-collapsible.collapsed .gated-body-wrap { max-height: 0; }
.gated-body-inner { overflow: hidden; }
.gated-card-collapsible .gated-card-blurb { margin: 8px 14px 10px; }
.gated-card-collapsible .gated-upgrade-btn { width: calc(100% - 28px); margin: 0 14px 12px; }

/* CaddiTQ engine credit (2026-08-24) — kicker caption under the Decision
   Intelligence/Risk-Aware Targeting titles (visible even collapsed) plus
   one credited sentence appended to each blurb. See golf-caddie-naming-
   positioning.md's CaddiTQ section — marketing-layer name for the combined
   engine; card titles themselves are unchanged. .title-col replaces the
   old bare flex:1 on .gated-card-title as the title's flex item so the
   kicker can sit on its own line underneath; harmless where no kicker is
   passed (Shot tendency, Lie-segmented club stats). */
.title-col { flex: 1; min-width: 0; }
.caddi-kicker {
  display: block;
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--app-text3);
  margin-top: 2px;
  opacity: 0.92;
}
.caddi-kicker .tm { font-weight: 400; opacity: 0.75; }
/* Named .caddi-credit, not .credit-line — that class is already used by the
   About & Credits data-source attribution list (index.html) with a
   different colour/purpose; reusing it here would collide. */
.caddi-credit { color: #c9e6d3; font-weight: 600; }

/* Lie-segmented stats (real Pro-tier content, v107) */
.lie-stats-card { padding-bottom: 12px; }
/* Capped + independently scrollable, same pattern as #shot-history on the Clubs
   tab: up to 14 clubs x 5 lie chip-rows is too much to reasonably fit on one
   screen, so the card itself stays a fixed size and only this inner list scrolls. */
.lie-stats-scroll { max-height: 45vh; overflow-y: auto; }
.lie-stat-row { margin-top: 10px; }
.lie-stat-row:first-of-type { margin-top: 2px; }
.lie-stat-club { font-weight: 700; font-size: 0.85rem; color: #EFE7D6; margin-bottom: 5px; }
.lie-stat-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.lie-stat-chip {
  background: var(--app-card-alt);
  border: 1px solid #2a6e47;
  border-radius: 12px;
  padding: 4px 10px;
  font-size: 0.74rem;
  color: var(--app-text2);
}
.lie-stat-chip.building { opacity: 0.75; border-style: dashed; }
.lie-stat-tag { opacity: 0.7; margin-left: 2px; }

/* Round history (scorecard scope doc Phase 2, 2026-08-07) — grouped by
   course, one small section per course played, each showing its own
   last-5-at-that-course. Not tier-gated, unlike the cards above; see
   renderRoundHistoryCard(). */
#round-history-card { margin: 0 16px 4px; }
/* Retokenized 2026-08-26 (Ged, "Green Spots Round 2" item 20) — course-name
   was already tokenized; the rest of Round History's row text wasn't.
   .rh-score stays #fff as-is. */
.round-history-course { padding: 8px 0; border-top: 1px solid rgba(255,255,255,0.12); }
.round-history-course:first-child { border-top: none; padding-top: 0; }
.round-history-course-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.round-history-course-name { font-size: 0.85rem; font-weight: 700; color: var(--app-text2); }
.round-history-avg { font-size: 0.74rem; color: var(--app-text3); white-space: nowrap; }
.round-history-rows { margin-top: 4px; }
.round-history-row {
  display: flex; align-items: center; gap: 10px;
  font-size: 0.8rem; color: var(--app-text2); padding: 3px 0;
}
.round-history-row .rh-date { flex: 0 0 54px; color: var(--app-text3); }
.round-history-row .rh-thru { flex: 0 0 56px; color: var(--app-text3); font-size: 0.74rem; }
.round-history-row .rh-score { flex: 1; text-align: right; font-weight: 600; color: #fff; }
.btn-delete-round {
  flex: 0 0 auto;
  margin-left: 2px;
  padding: 2px 7px;
  border: 1px solid #4a1e1e;
  border-radius: 8px;
  background: #2e0d0d;
  color: #ff8a8a;
  font-size: 0.85rem;
  line-height: 1.2;
  cursor: pointer;
}

.tier-dev-row { display: flex; align-items: stretch; gap: 8px; margin-top: 4px; }
.tier-dev-btn {
  flex: 1;
  padding: 8px 4px;
  border: none;
  border-radius: 8px;
  background: var(--app-card-alt);
  color: var(--app-text2);
  font-size: 0.78rem;
  font-weight: 600;
  line-height: 1.2;
  cursor: pointer;
}
/* Entry tier's real name ("TeeForm Caddie") is longer than "Pro"/"Tour" —
   let it wrap to two lines rather than shrinking font size app-wide or
   truncating the name. */
.tier-dev-btn[data-tier="intro"] { white-space: normal; }
.tier-dev-btn.active { background: var(--app-accent-fill); color: var(--app-accent-ontext); }

/* Freemius test (dev) — see index.html for the block comment. Reuses
   .tier-mini/.tier-mini-label as its outer wrapper (same look as App Tier
   (dev) immediately above it); these rules are just the row/input/output
   pieces unique to this block. */
.freemius-test-row { display: flex; align-items: stretch; gap: 8px; margin-top: 6px; }
.freemius-test-row input[type="text"] {
  flex: 1;
  min-width: 0;
  padding: 8px 10px;
  border: none;
  border-radius: 8px;
  background: var(--app-card-alt);
  color: var(--app-text1);
  font-size: 0.8rem;
}
.freemius-test-row button {
  flex: 1;
  padding: 8px 6px;
  border: none;
  border-radius: 8px;
  background: var(--app-card-alt);
  color: var(--app-text2);
  font-size: 0.76rem;
  font-weight: 600;
  cursor: pointer;
}
.freemius-test-row button:disabled { opacity: 0.6; }
.freemius-test-output {
  margin: 8px 0 0;
  padding: 8px 10px;
  border-radius: 8px;
  background: #05100a;
  color: #a9d6bd;
  font-size: 0.72rem;
  line-height: 1.4;
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 220px;
  overflow-y: auto;
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
}

/* ── Plan tab first-run guidance ────────────────────────────────── */
.plan-getting-started {
  background: #071f0d;
  border: 1px solid #1a5a30;
  border-radius: 10px;
  padding: 12px 14px;
  margin-bottom: 10px;
  font-size: 0.82rem;
  color: var(--app-text3);
  line-height: 1.55;
}
.plan-getting-started p { margin: 4px 0 0; }
.plan-gs-title {
  font-size: 0.88rem;
  font-weight: 600;
  color: #c8f5da;
  margin-bottom: 4px;
}

/* ── Manual course entry form (Plan tab, no course detected) ────── */
.manual-course-form {
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin: 12px 0 6px;
}
.manual-course-form input[type="text"] {
  width: 100%;
  font-size: 0.95rem;
}
.manual-course-holes {
  display: flex;
  gap: 20px;
  font-size: 0.88rem;
  color: var(--app-text3);
}
.manual-course-holes label {
  display: flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
}
.btn-create-course {
  width: 100%;
  padding: 13px 0;
  border-radius: 10px;
  background: var(--app-accent-fill);
  border: none;
  color: var(--app-accent-ontext);
  font-size: 0.95rem;
  font-weight: 700;
  cursor: pointer;
  letter-spacing: 0.01em;
}
.btn-create-course:active { background: #256f47; }

/* ── Mini hole schematic (v52) ──────────────────────────────────── */
.caddie-schematic-wrap {
  /* Bleed to card edges, flush under the card border-radius */
  margin: -2px -2px 8px;
  border-radius: 10px 10px 4px 4px;
  overflow: hidden;
  line-height: 0;
  position: relative; /* anchors the Stroke Index entry overlay, below */
}
/* Stroke Index overlay needs to float outside the schematic's own rounded-
   corner clip (2026-08-24) — toggled on only while the overlay is open, so
   the bleed-to-card-edges clipping above still applies normally otherwise. */
.caddie-schematic-wrap.si-overlay-open { overflow: visible; }
.caddie-schematic {
  width: 100%;
  height: auto;   /* height governed by SVG viewBox ratio (220/280 ≈ 79%, enlarged 2026-08-08 now that Detail is scorecard-free) */
  display: block;
}
.si-hit { cursor: pointer; }

/* Stroke Index number-entry overlay (2026-08-24, golf-caddie-handicap-
   estimate-scope.md Addendum 4, mockup 3a7293eb approved by Ged: "I like
   it"). SVG can't host a real <input> inside itself, so this floats next
   to the schematic instead of drawing inline. Fixed bottom/right anchor —
   the SI label's corner is always the same spot at this schematic's fixed
   280×220 aspect ratio, so no live measurement is needed. */
/* Retokenized 2026-08-26 (Ged, "Green Spots Round 2" item 11) — this is the
   Stroke Index schematic-entry card, the reference pattern the newer
   Stroke Index gender-preference picker was modeled on, but the original
   itself was never converted when the theming system shipped. */
.si-overlay {
  position: absolute;
  right: 4px;
  bottom: 30px;
  background: var(--app-card);
  border: 1px solid var(--app-accent-fill);
  border-radius: 12px;
  padding: 14px;
  box-shadow: 0 10px 24px -8px rgba(0, 0, 0, 0.55);
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: 180px;
  z-index: 5;
}
.si-overlay-label { font-size: 0.68rem; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; color: var(--app-text3); }
.si-overlay input {
  width: 100%;
  padding: 9px 10px;
  border-radius: 8px;
  border: 1px solid var(--app-accent-fill);
  background: var(--app-header);
  color: #fff;
  font-size: 1.05rem;
  font-weight: 700;
  text-align: center;
  font-family: inherit;
}
.si-overlay input:focus { outline: 2px solid var(--app-text1); outline-offset: 1px; }
.si-overlay-hint { font-size: 0.66rem; color: var(--app-text3); line-height: 1.4; }
.si-overlay-btns { display: flex; gap: 8px; }
.si-btn { flex: 1; padding: 7px 0; border-radius: 8px; font-size: 0.78rem; font-weight: 700; border: 1px solid; cursor: pointer; font-family: inherit; }
.si-btn-cancel { background: none; border-color: var(--app-accent-fill); color: var(--app-text3); }
.si-btn-save { background: var(--app-accent-fill); border-color: var(--app-accent-fill); color: var(--app-accent-ontext); }

/* ── Clubs tab section switcher (2026-08-23, locked in by Ged: "As I like
   the new Stats tab with the row scrolling buttons can we make the Clubs
   tab the same") — replaces the old always-all-expanded .csec accordion
   with the same horizontally-scrolling button row + single-visible-panel
   pattern already built and approved for the Stats tab. Styled identically
   (same sizing/colors as .stats-sec-row/.stats-sec-btn/.stats-sec-panel)
   plus icons, since each button carries the section's existing icon. */
.clubs-sec-row {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  padding: 2px 2px 10px;
  margin: 4px -2px 2px;
  scrollbar-width: none;
  /* Sticky, same fix already shipped for the Stats tab — pins the row at
     the top of the Clubs tab's own scroll container (.tab-panel has
     overflow-y:auto) so a long Recent Shots list or the Manage Bag list can
     never leave the buttons unreachable underneath it. */
  position: sticky;
  top: 0;
  z-index: 5;
  background: var(--app-bg);
  /* Same iOS Safari compositing fix as .stats-sticky-header (see its
     comment in this file). */
  transform: translateZ(0);
  -webkit-transform: translateZ(0);
  will-change: transform;
}
.clubs-sec-row::-webkit-scrollbar { display: none; }
.clubs-sec-btn {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 5px;
  padding: 7px 12px;
  border-radius: 20px;
  /* Lightened 2026-08-24, same fix and reasoning as .stats-sec-btn above.
     Retokenized 2026-08-26, also same as .stats-sec-btn above. */
  background: var(--app-pill-bg);
  border: none;
  color: var(--app-text2);
  font-size: 0.8rem;
  font-weight: 600;
  white-space: nowrap;
}
.clubs-sec-btn.active { background: var(--app-accent-fill); color: var(--app-accent-ontext); }
.clubs-sec-btn svg { width: 15px; height: 15px; flex-shrink: 0; display: block; }
.clubs-sec-panel { display: none; min-height: 0; }
.clubs-sec-panel.active { display: block; }
/* Recent Shots (2026-08-27) — flex-fill instead of plain block flow, so
   #shot-history's own flex:1 (see above) can actually consume the real
   remaining height inside #tab-clubs (a flex column itself) instead of
   being capped at an arbitrary viewport fraction. Scoped to just this
   panel via the attribute selector — Manage Bag/Log a Shot/Backup/About
   all rely on normal page scrolling and don't want this treatment. */
.clubs-sec-panel[data-sec-panel="history"].active { display: flex; flex-direction: column; flex: 1; min-height: 0; }
/* Retokenized 2026-08-26 (Ged, "Green Spots Round 2" items 14·15·16) —
   shared by Manage Bag's club-count, Recent Shots' shot-count, and Backup
   and Restore's summary text. */
.clubs-panel-summary { font-size: 0.78rem; font-weight: 500; color: var(--app-text3); margin: 2px 0 10px; }

/* ── Upgrade section (build phase 8 UI, 2026-09-11) ───────────────────────
   Clubs tab's 6th section button + panel — the real license-entry/checkout
   screen, replacing the "App Tier (dev)" toggle and "Freemius test (dev)"
   panel as the actual way a golfer changes tier. Ported from the approved
   "Upgrade Screen" mockup (round 2, customers.freemius.com added) —
   https://claude.ai/code/artifact/8809e65e-db4e-41f3-86f4-5034c847bbcc —
   values/classes below mirror that mockup's CSS as closely as the app's
   existing token names allow, so no fresh design pass was needed. */
.upgrade-status-card {
  background: var(--app-card);
  border-radius: 14px;
  padding: 14px 15px;
  margin-bottom: 16px;
  border: 1px solid rgba(255,255,255,0.06);
}
.upg-status-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 4px; }
.upg-tier-name { font-size: 1.05rem; font-weight: 800; }
.upg-tier-name.free { color: var(--app-text2); }
.upg-tier-name.pro { color: #C9A227; }
.upg-tier-name.tour {
  background: linear-gradient(135deg, #A2ACB5, #7C858C);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.upg-status-sub { font-size: 0.78rem; color: var(--app-text2); line-height: 1.45; margin: 2px 0 0; }
.upg-trial-track { margin-top: 10px; height: 6px; border-radius: 4px; background: rgba(255,255,255,0.12); overflow: hidden; }
.upg-trial-fill { height: 100%; background: #e0c069; border-radius: 4px; }
.upg-status-meta-row {
  display: flex; justify-content: space-between; align-items: baseline;
  margin-top: 10px; padding-top: 10px; border-top: 1px dashed rgba(255,255,255,0.12);
  font-size: 0.76rem; color: var(--app-text2);
}
.upg-status-meta-row .num { font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace; color: var(--app-text1); font-variant-numeric: tabular-nums; }
.upg-manage-link {
  font-family: inherit; background: none; border: none;
  color: var(--app-text3); font-size: 0.76rem; font-weight: 700;
  padding: 0; margin-top: 10px; cursor: pointer;
  text-decoration: underline; text-underline-offset: 2px;
}
.upg-manage-panel { margin-top: 10px; background: var(--app-card-alt); border-radius: 10px; padding: 10px 12px; font-size: 0.76rem; color: var(--app-text2); }
.upg-manage-panel .row { display: flex; justify-content: space-between; padding: 3px 0; gap: 10px; }
.upg-manage-panel .row .num { font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace; color: var(--app-text1); font-variant-numeric: tabular-nums; text-align: right; word-break: break-all; }
.upg-btn-remove-device {
  margin-top: 8px; width: 100%; font-family: inherit;
  background: transparent; border: 1px solid #7a3838; color: #e58a8a;
  font-size: 0.76rem; font-weight: 700; padding: 8px 10px; border-radius: 8px;
}
.upg-btn-remove-device:disabled { opacity: 0.6; }
.upg-manage-portal-hint { margin-top: 9px; padding-top: 9px; border-top: 1px dashed rgba(255,255,255,0.12); font-size: 0.7rem; color: var(--app-text2); line-height: 1.45; }
.upg-manage-portal-hint strong { color: var(--app-text1); }
.upg-manage-msg { margin-top: 8px; font-size: 0.72rem; color: var(--app-text2); line-height: 1.4; }
.upg-manage-msg.err { color: #e58a8a; }

.upg-cycle-row { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
.upg-cycle-label { font-size: 0.72rem; color: var(--app-text2); font-weight: 600; }
.upg-cycle-toggle { display: inline-flex; background: var(--app-card-alt); border-radius: 8px; padding: 3px; }
.upg-cycle-toggle button {
  font-family: inherit; border: none; background: transparent; color: var(--app-text2);
  font-size: 0.72rem; font-weight: 700; padding: 5px 10px; border-radius: 6px;
}
.upg-cycle-toggle button.active { background: var(--app-accent-fill); color: var(--app-accent-ontext); }

/* ==== Wallet-style tier cards (2026-09-13, replaces the flat card list
   below) ====
   Approved mockup: https://claude.ai/code/artifact/c938214e-4858-4973-a56a-7ce8676aae6b
   Free/Caddie/Pro/Tour stack like cards in Apple Wallet — each collapsed to
   a colored "face" (just name + pitch + a price/Current teaser) that
   overlaps the next card by 10px; tapping a face expands that card's full
   detail and collapses whichever other one was open (see
   renderUpgradeWallet() in app.js). The face colors intentionally reuse the
   exact hex values .upg-badge-strip below already used for each tier, so
   this is a re-skin of already-approved colors, not a new palette. */
.upg-wallet-stack { display: flex; flex-direction: column; margin-bottom: 4px; }
.upg-wallet-card { position: relative; border-radius: 14px; margin-top: -10px; box-shadow: 0 -6px 14px -8px rgba(0,0,0,0.45); }
.upg-wallet-card:first-child { margin-top: 0; }
.upg-wallet-card.is-open { z-index: 5; margin-bottom: 8px; }

.upg-wc-face {
  width: 100%; font-family: inherit; border: none; border-radius: 14px; padding: 14px 15px;
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  cursor: pointer; text-align: left; position: relative;
}
.upg-wc-face .upg-wc-id { display: flex; align-items: baseline; gap: 7px; min-width: 0; }
.upg-wc-face .upg-wc-name { font-size: 0.98rem; font-weight: 800; letter-spacing: 0.01em; white-space: nowrap; }
.upg-wc-face .upg-wc-pitch { font-size: 0.68rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; opacity: 0.85; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.upg-wc-face .upg-wc-right { display: flex; align-items: center; gap: 9px; flex-shrink: 0; }
.upg-wc-face .upg-wc-teaser { font-size: 0.72rem; font-weight: 700; font-variant-numeric: tabular-nums; white-space: nowrap; }
.upg-wc-face .upg-wc-chev { font-size: 0.7rem; transition: transform 0.2s ease; opacity: 0.8; }
.upg-wallet-card.is-open .upg-wc-chev { transform: rotate(180deg); }
.upg-wc-badge { font-size: 0.58rem; font-weight: 800; letter-spacing: 0.04em; text-transform: uppercase; padding: 2px 6px; border-radius: 5px; flex-shrink: 0; }
.upg-wc-face:focus-visible { outline: 2px solid var(--app-text3); outline-offset: -2px; }

.upg-wc-face.face-free { background: var(--app-card-alt); color: var(--app-text1); }
.upg-wc-face.face-free .upg-wc-pitch, .upg-wc-face.face-free .upg-wc-teaser { color: var(--app-text2); }
.upg-wc-face.face-free .upg-wc-badge { background: rgba(255,255,255,0.14); color: var(--app-text1); }

.upg-wc-face.face-intro { background: linear-gradient(135deg, #379a63, #1c5c39); color: #fff; }
.upg-wc-face.face-intro .upg-wc-pitch, .upg-wc-face.face-intro .upg-wc-teaser { color: rgba(255,255,255,0.82); }
.upg-wc-face.face-intro .upg-wc-badge { background: rgba(255,255,255,0.22); color: #fff; }

.upg-wc-face.face-pro { background: linear-gradient(135deg, #ddc067, #a9812a); color: #2c2306; }
.upg-wc-face.face-pro .upg-wc-pitch, .upg-wc-face.face-pro .upg-wc-teaser { color: rgba(43,34,6,0.72); }
.upg-wc-face.face-pro .upg-wc-badge { background: rgba(43,34,6,0.16); color: #2c2306; }

.upg-wc-face.face-tour { background: linear-gradient(135deg, #b7c0c8, #838c94); color: #20242a; }
.upg-wc-face.face-tour .upg-wc-pitch, .upg-wc-face.face-tour .upg-wc-teaser { color: rgba(32,36,42,0.72); }
.upg-wc-face.face-tour .upg-wc-badge { background: rgba(32,36,42,0.14); color: #20242a; }

/* Fixed generous max-height + transition, same convention as the existing
   .gated-body-wrap accordion (style.css, Clubs-tab gated cards) rather than
   a JS-measured scrollHeight — simpler, and this content never approaches
   the cap. */
.upg-wc-body-wrap { max-height: 900px; overflow: hidden; transition: max-height 0.28s ease; background: var(--app-card); border-radius: 0 0 14px 14px; }
.upg-wallet-card:not(.is-open) .upg-wc-body-wrap { max-height: 0; }
.upg-wc-body { padding: 14px 15px 15px; border: 1px solid rgba(255,255,255,0.06); border-top: none; border-radius: 0 0 14px 14px; }

/* ==== Flat tier-card classes below (pre-2026-09-13) are now DEAD CODE for
   the card container/head/badge-strip pieces specifically — superseded by
   the wallet-face classes above. Left in place rather than deleted: the
   tier-scoped intro/pro/tour color rules for the *content* classes further
   down (.upg-price-*, .upg-tier-blurb, .upg-tier-feats, .upg-tier-cta) are
   still live, reused as-is inside the new wallet card bodies. ==== */
.upg-tier-card { background: var(--app-card); border-radius: 14px; padding: 14px 15px; margin-bottom: 12px; border: 1px solid rgba(255,255,255,0.06); position: relative; }
.upg-tier-card.is-current { border-color: rgba(155,231,184,0.45); }
.upg-badge-strip { position: absolute; top: 0; left: 0; right: 0; height: 4px; border-radius: 14px 14px 0 0; }
.upg-badge-strip.intro { background: var(--app-accent-fill); }
.upg-badge-strip.pro { background: #C9A227; }
.upg-badge-strip.tour { background: linear-gradient(90deg, #A2ACB5, #7C858C); }

.upg-tier-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 10px; margin-top: 2px; }
.upg-tier-name-wrap { display: flex; align-items: baseline; gap: 7px; flex-wrap: wrap; }
.upg-tier-title { font-size: 1rem; font-weight: 800; }
.upg-tier-pitch { font-size: 0.68rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; opacity: 0.85; }
.upg-tier-pitch.intro { color: var(--app-accent-fill); }
.upg-tier-pitch.pro { color: #C9A227; }
.upg-tier-pitch.tour { color: #A2ACB5; }

.upg-price-block { text-align: right; flex-shrink: 0; }
.upg-price-now { font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace; font-size: 1.05rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.upg-price-now.upg-price-unavailable { font-family: inherit; font-size: 0.76rem; font-weight: 700; color: var(--app-text2); white-space: nowrap; }
.upg-price-was { font-family: "SFMono-Regular", Consolas, "Liberation Mono", monospace; font-size: 0.72rem; color: var(--app-text2); text-decoration: line-through; opacity: 0.7; }
.upg-price-cycle { font-size: 0.66rem; color: var(--app-text2); }
.upg-intro-ribbon {
  display: inline-block; margin-top: 3px; font-size: 0.6rem; font-weight: 800; letter-spacing: 0.03em;
  background: rgba(224,192,105,0.18); color: #e0c069; padding: 1px 6px; border-radius: 6px; text-transform: uppercase;
}

.upg-tier-blurb { font-size: 0.78rem; color: var(--app-text2); margin: 9px 0 8px; line-height: 1.4; }
.upg-tier-feats { list-style: none; margin: 0 0 12px; padding: 0; display: flex; flex-direction: column; gap: 5px; }
.upg-tier-feats li { font-size: 0.78rem; color: var(--app-text1); display: flex; gap: 7px; align-items: flex-start; line-height: 1.4; }
.upg-tier-feats li::before { content: "✓"; color: var(--app-text3); font-weight: 800; flex-shrink: 0; }
.upg-tier-feats li.inherited { color: var(--app-text2); }
.upg-tier-feats li.inherited::before { color: var(--app-text2); opacity: 0.6; }

.upg-tier-cta { width: 100%; font-family: inherit; border: none; border-radius: 9px; padding: 10px; font-size: 0.85rem; font-weight: 700; cursor: pointer; }
.upg-tier-cta.upgrade { background: var(--app-accent-fill); color: #fff; }
.upg-tier-cta.upgrade.pro { background: #C9A227; color: #082b16; }
.upg-tier-cta.upgrade.tour { background: linear-gradient(135deg, #A2ACB5, #7C858C); color: #082b16; }
.upg-tier-cta.current { background: var(--app-card-alt); color: var(--app-text3); cursor: default; }
.upg-tier-cta.trialling { background: rgba(224,192,105,0.18); color: #e0c069; cursor: default; }
.upg-tier-cta:disabled { cursor: default; }

.upg-restore-toggle {
  width: 100%; text-align: left; font-family: inherit; background: var(--app-card-alt);
  border: none; border-radius: 10px; padding: 11px 13px; margin-top: 4px;
  color: var(--app-text2); font-size: 0.78rem; font-weight: 600;
  display: flex; justify-content: space-between; align-items: center; cursor: pointer;
}
.upg-restore-toggle .upg-chev { color: var(--app-text3); transition: transform 0.15s; flex-shrink: 0; }
.upg-restore-toggle.open .upg-chev { transform: rotate(180deg); }
.upg-restore-panel { background: var(--app-card-alt); border-radius: 0 0 10px 10px; padding: 12px 13px 13px; margin-top: -4px; display: none; }
.upg-restore-panel.open { display: block; }
.upg-restore-panel input {
  width: 100%; box-sizing: border-box; background: var(--app-card);
  border: 1px solid rgba(255,255,255,0.14); border-radius: 8px; color: var(--app-text1);
  padding: 9px 10px; font-size: 0.8rem; font-family: inherit; margin-bottom: 8px;
}
.upg-restore-panel .upg-hint { font-size: 0.7rem; color: var(--app-text2); margin: 0 0 8px; line-height: 1.4; }
.upg-btn-activate { width: 100%; font-family: inherit; border: none; border-radius: 8px; background: var(--app-accent-fill); color: #fff; font-size: 0.8rem; font-weight: 700; padding: 9px; }
.upg-btn-activate:disabled { opacity: 0.6; }

/* The shared "Paste from clipboard" row (added 2026-09-15, next to every
   license-key input) was removed again the same week (2026-09-17, Ged:
   "remove the paste button as it's not needed" — iOS's own long-press
   paste on the input already covers it). Removed from here, the restore
   panel's own markup, and the welcome screen's "Already have a license?"
   panel in app.js. */
.upg-restore-msg { margin-top: 8px; font-size: 0.72rem; color: var(--app-text2); line-height: 1.4; }
.upg-restore-msg.err { color: #e58a8a; }

/* Trial-activate prompt (2026-09-17, x34) — the Tour wallet card's body
   while a trial has started (state.pendingTrialStartedAt set) but hasn't
   been activated on this device yet. Sits inside .upg-wc-body, above the
   normal blurb/feature list, so the golfer still sees what they're
   trialling while being told exactly what to do next. Amber-tinted to
   match .upg-trial-fill/.tier-badge-trial — the same "trial" color used
   everywhere else in the app. */
.trial-prompt {
  background: rgba(224, 192, 105, 0.12);
  border: 1px solid rgba(224, 192, 105, 0.3);
  border-radius: 10px;
  padding: 12px 13px 13px;
  margin-bottom: 12px;
}
.trial-prompt-head { font-size: 0.84rem; font-weight: 800; color: #e0c069; margin: 0 0 5px; }
.trial-prompt-body { font-size: 0.76rem; color: var(--app-text2); line-height: 1.42; margin: 0 0 10px; }
.trial-prompt input {
  width: 100%;
  box-sizing: border-box;
  background: var(--app-card);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 8px;
  color: var(--app-text1);
  padding: 9px 10px;
  font-size: 0.8rem;
  font-family: inherit;
  margin-bottom: 9px;
}
.upg-restore-msg.err a { color: inherit; font-weight: 700; text-decoration: underline; text-underline-offset: 2px; }
.upg-restore-msg.ok { color: var(--app-text3); }
.upg-forgot-line { margin: 10px 0 0; font-size: 0.72rem; color: var(--app-text2); line-height: 1.4; text-align: center; }
.upg-forgot-link { color: var(--app-text3); font-weight: 700; text-decoration: underline; text-underline-offset: 2px; }

/* Freemius checkout escape hatch (v260, 2026-09-11) — see index.html's
   comment on #freemius-checkout-escape for why this exists. Anchored to the
   BOTTOM of the screen deliberately, nowhere near the status bar/notch this
   app's black-translucent status-bar mode draws Freemius's overlay under.
   z-index set as high as CSS allows so it stays clickable above whatever
   z-index Freemius's own overlay uses (unknown/uncontrolled — this is a
   third-party, cross-origin widget). */
.freemius-checkout-escape {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: max(18px, env(safe-area-inset-bottom));
  z-index: 2147483647;
  font-family: inherit;
  font-size: 0.82rem;
  font-weight: 700;
  color: #fff;
  background: #7a2f2f;
  border: 1px solid rgba(255,255,255,0.25);
  border-radius: 24px;
  padding: 11px 18px;
  box-shadow: 0 6px 20px rgba(0,0,0,0.45);
  cursor: pointer;
  white-space: nowrap;
}
.freemius-checkout-escape:active { background: #692828; }

/* Checkout handoff — a plain status line while FS.Checkout's own hosted
   overlay is opening/closing over this screen, not a custom payment form
   (that's Freemius's widget, not ours to design — see the mockup callout).
   The success path intentionally does NOT assume the checkout callback
   hands back a raw license key string — Freemius's own published JS API
   docs (checked 2026-09-11) only confirm a numeric license_id in that
   response, not the key text itself, so this points the golfer at the
   confirmation email + the Activate panel below instead of guessing at an
   unconfirmed auto-activation path. */
.upg-checkout-note { font-size: 0.74rem; color: var(--app-text2); line-height: 1.45; margin: 8px 0 0; padding-top: 8px; border-top: 1px dashed rgba(255,255,255,0.12); }
.upg-checkout-note.ok { color: var(--app-text3); }
.upg-checkout-note.err { color: #e58a8a; }

/* App Tier (dev) mini-block (2026-08-23) — lives at the bottom of the Log a
   Shot panel now instead of its own section button; see index.html. Dashed
   top border sets it visually apart from the shot form above it without a
   full card treatment. */
.tier-mini { margin-top: 18px; padding-top: 14px; border-top: 1px dashed #1c4a2c; }
.tier-mini-label { font-size: 0.74rem; color: #a8c9b6; line-height: 1.45; margin: 0 0 8px; }

/* ── About & Credits (2026-08-18) ──────────────────────────────────
   Reuses the same visual language as the rest of the Clubs tab (card bg,
   accent-green section title) rather than inventing a new component. */
.credit-block { background: var(--app-bg); border-radius: 10px; padding: 12px 13px; margin-top: 10px; }
.credit-block:first-child { margin-top: 0; }
.credit-block + .credit-block { margin-top: 8px; }
.credit-block-title { font-size: 0.78rem; font-weight: 700; color: var(--app-text3); text-transform: uppercase; letter-spacing: 0.05em; margin: 0 0 6px; }
.credit-line { font-size: 0.82rem; color: var(--app-text1); line-height: 1.55; margin: 0; }
.credit-line a { color: var(--app-text3); }
/* Retokenized 2026-08-26 (Ged, "Green Spots Round 2" item 17). */
.app-version-line { font-size: 0.76rem; color: var(--app-text3); margin: 10px 0 0; }

/* Course Data credit-block's action link (v237) — visually a step up from
   a plain .credit-line citation link since this one's meant to be tapped,
   not just read. Reuses the same underlined-text-button treatment as the
   v236 install-nudge card's "Export" link rather than inventing a new
   button style. Mocked up at
   https://claude.ai/code/artifact/6864b171-b28e-496e-9721-cd9a94403de9 and
   approved ("Good to build"). */
.credit-link-btn { display: inline-block; margin-top: 8px; font-size: 0.79rem; font-weight: 700; color: var(--app-text3); text-decoration: underline; text-underline-offset: 2px; }

/* ── Update check — auto (every 28 days) + manual (About & Credits) ──────
   Mocked up at https://claude.ai/code/artifact/496d0604-b178-47e7-8ac9-1c2c31f3bf2d
   ("Update Refresh") and confirmed by Ged: opening the app auto-checks on a
   28-day timer (no manual tap needed, and no need to check every single
   launch since real updates are expected to be infrequent) and always
   confirms either way once a check actually runs — an "up to date" toast
   that clears itself, or a persistent "a new version is ready" banner with
   a Refresh button that keeps reappearing on every launch until you tap it.
   The button below (About & Credits) triggers the same check on demand,
   bypassing the 28-day gate — see runScheduledUpdateCheck() in app.js. */
#update-banner[hidden] { display: none; }
.update-banner {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  font-size: 0.78rem;
  font-weight: 600;
  line-height: 1.3;
  flex-shrink: 0;
}
.update-banner.checking { background: var(--app-card-alt); color: var(--app-text2); font-weight: 500; }
.update-banner.ok { background: var(--app-text3); color: #0d2e18; }
.update-banner.found { background: #e0b84a; color: #23200f; }
.update-banner .ub-text { flex: 1; display: flex; align-items: center; gap: 6px; }
.update-banner .ub-text svg { flex-shrink: 0; animation: update-spin 1s linear infinite; }
.update-banner button { border: none; font-weight: 700; font-size: 0.74rem; padding: 6px 10px; border-radius: 7px; }
.update-banner .ub-refresh { background: #23200f; color: #e0b84a; }
.update-banner .ub-close { background: none; color: inherit; padding: 2px 4px; font-size: 0.9rem; opacity: 0.6; font-weight: 400; }

.update-check-block { margin-top: 8px; }
.check-updates-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  width: 100%;
  border: none;
  border-radius: 9px;
  background: var(--app-accent-fill);
  color: var(--app-accent-ontext);
  font-weight: 700;
  font-size: 0.83rem;
  padding: 10px 12px;
}
.check-updates-btn.checking .cu-icon { animation: update-spin 1s linear infinite; }
.check-updates-btn.found { background: #e0b84a; color: #23200f; }
@keyframes update-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .update-banner .ub-text svg,
  .check-updates-btn.checking .cu-icon,
  .status-spin svg { animation: none; }
}
.update-check-status { font-size: 0.78rem; margin: 8px 0 0; line-height: 1.5; }
.update-check-status.ok { color: var(--app-text3); }
.update-check-status.found { color: #e0b84a; font-weight: 600; }

/* Example Data (2026-09-27) — one-time toast confirming the switch has been
   force-flipped off, mirroring #update-banner's structural precedent (flex
   sibling above <main>, visible regardless of active tab) but always in one
   quiet confirming state rather than several — see showExampleDataOffToast()
   in app.js. Green accent (not the amber used for the banner/switch itself)
   since this message is reassuring ("back to your real data"), not a
   reminder that something else is showing fixture data right now. */
#example-data-toast[hidden] { display: none; }
.example-data-toast {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  font-size: 0.78rem;
  font-weight: 600;
  line-height: 1.3;
  flex-shrink: 0;
  background: var(--app-text3);
  color: #0d2e18;
}

/* ── User Guide overlay ──────────────────────────────────────────────
   Added v243 per Ged: "Could the TeeForm Golf Caddie [user guide] be
   included as part of the app build... a link on the About & Credits tab
   above the Map & Data section?" — so it ships inside the app instead of
   living only as an external file, and can be revised before release.
   Built lazily in app.js (openUserGuide()) and appended to <body>, same
   fixed-overlay-with-dimmed-backdrop family as #welcome-overlay, its own
   z-index band above the normal UI, reopenable any time (not a one-shot
   first-run card). Deliberately uses the app's own system font stack —
   no Google Fonts import, unlike the standalone artifact version this was
   adapted from — so it renders identically online or fully offline on
   the course, matching the app's own battery/offline design values.
   Every expand/collapse and directional mark here is inline SVG, never a
   Unicode glyph: Ged reported (2026-09-04) that dashes and arrow/chevron
   characters were mojibake'ing in the standalone exported copy of this
   guide, traced to that file lacking an explicit charset declaration —
   SVG sidesteps that whole failure class regardless of where this
   content ever gets reused, rather than just fixing the one file. */
#guide-overlay { position: fixed; inset: 0; z-index: 2000; background: rgba(0,0,0,0.88); display: flex; justify-content: center; opacity: 1; transition: opacity 0.25s ease; }
#guide-overlay.guide-hide { opacity: 0; pointer-events: none; }
#guide-card { background: var(--app-header); width: 100%; max-width: 560px; height: 100%; overflow-y: auto; -webkit-overflow-scrolling: touch; overscroll-behavior-y: contain; padding: 0 0 48px; }
.guide-topbar { position: sticky; top: 0; z-index: 2; display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: env(safe-area-inset-top, 14px) 16px 12px; background: var(--app-header); border-bottom: 1px solid rgba(242,242,242,0.12); }
.guide-topbar h2 { font-size: 1.02rem; margin: 0; color: var(--app-text1); }
.guide-close { background: var(--app-card-alt); border: none; color: var(--app-text1); width: 32px; height: 32px; border-radius: 50%; display: flex; align-items: center; justify-content: center; flex-shrink: 0; font-size: 1rem; }
.guide-body { padding: 18px 16px 0; }
.guide-kicker { font-size: 0.7rem; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: var(--app-text3); margin: 0 0 4px; }
.guide-intro { font-size: 0.86rem; color: var(--app-text2); margin: 0 0 6px; }
.guide-section { margin: 28px 0 0; padding-top: 22px; border-top: 1px solid rgba(242,242,242,0.1); }
.guide-section:first-of-type { border-top: none; margin-top: 20px; padding-top: 0; }
.guide-section h3 { font-size: 1.05rem; color: var(--app-text1); margin: 0 0 6px; }
.guide-section > p.guide-dek { font-size: 0.83rem; color: var(--app-text2); margin: 0 0 16px; }

.guide-tier { border-radius: 12px; padding: 13px 14px 11px; margin-bottom: 9px; background: var(--app-card); border-left: 3px solid var(--app-text3); }
.guide-tier.pro { border-left-color: #e0b84a; }
.guide-tier.tour { border-left-color: #c9ced6; }
.guide-tier-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; flex-wrap: wrap; }
.guide-tier-name { font-weight: 700; font-size: 0.95rem; color: var(--app-text1); }
.guide-tier-pitch { font-size: 0.76rem; font-style: italic; color: var(--app-text3); margin: 2px 0 6px; }
.guide-tier.pro .guide-tier-pitch { color: #e0b84a; }
.guide-tier.tour .guide-tier-pitch { color: #e4e7ea; }
.guide-tier-desc { font-size: 0.81rem; color: var(--app-text2); margin: 0; }

.guide-journey { position: relative; padding-left: 26px; }
.guide-journey::before { content: ""; position: absolute; left: 8px; top: 4px; bottom: 4px; width: 2px; background: rgba(155,231,184,0.3); }
.guide-step { position: relative; padding-bottom: 20px; }
.guide-step:last-child { padding-bottom: 0; }
.guide-step-dot { position: absolute; left: -26px; top: 1px; width: 19px; height: 19px; border-radius: 50%; background: var(--app-card-alt); border: 2px solid var(--app-text3); color: var(--app-text3); display: flex; align-items: center; justify-content: center; font-size: 0.64rem; font-weight: 700; }
.guide-step-title { font-weight: 700; font-size: 0.92rem; color: var(--app-text1); margin-bottom: 4px; }
.guide-step-body p { font-size: 0.82rem; color: var(--app-text2); margin: 0 0 0.6em; }
.guide-step-body p:last-child { margin-bottom: 0; }
.guide-tier-inline { display: inline-flex; align-items: center; font-size: 0.6rem; font-weight: 800; letter-spacing: 0.03em; padding: 1px 6px; border-radius: 6px; margin-right: 4px; text-transform: uppercase; background: rgba(155,231,184,0.16); color: var(--app-text3); }
.guide-tier-inline.pro { background: rgba(224,184,74,0.18); color: #e0b84a; }
.guide-tier-inline.tour { background: rgba(200,205,214,0.22); color: #e4e7ea; }

.guide-ref { border: 1px solid rgba(242,242,242,0.1); border-radius: 12px; margin-bottom: 9px; background: var(--app-card); overflow: hidden; }
.guide-ref > summary { list-style: none; cursor: pointer; padding: 12px 13px; display: flex; align-items: center; justify-content: space-between; font-weight: 700; font-size: 0.9rem; color: var(--app-text1); }
.guide-ref > summary::-webkit-details-marker { display: none; }
.guide-ref > summary .guide-chev { color: var(--app-text3); transition: transform 0.15s ease; flex-shrink: 0; }
.guide-ref[open] > summary .guide-chev { transform: rotate(90deg); }
.guide-ref-body { padding: 0 13px 13px; }
.guide-ref-body h4 { font-size: 0.64rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--app-text3); margin: 13px 0 5px; }
.guide-ref-body h4:first-child { margin-top: 2px; }
.guide-feat { padding: 7px 0; border-top: 1px solid rgba(242,242,242,0.08); }
.guide-feat:first-of-type { border-top: none; }
.guide-feat-name { font-weight: 700; font-size: 0.82rem; color: var(--app-text1); }
.guide-feat-desc { font-size: 0.78rem; color: var(--app-text2); margin-top: 2px; }

.guide-shot { margin: 14px 0; text-align: center; }
.guide-shot img { width: 100%; max-width: 230px; display: block; margin: 0 auto; border-radius: 14px; border: 1px solid rgba(242,242,242,0.14); box-shadow: 0 12px 26px rgba(0,0,0,0.4); }
.guide-shot-cap { margin-top: 8px; font-size: 0.7rem; color: var(--app-text3); letter-spacing: 0.01em; }
.guide-shot-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin: 14px 0; }
.guide-shot-row .guide-shot { margin: 0; }
.guide-shot-row .guide-shot img { max-width: none; }

.guide-tips { list-style: none; margin: 0; padding: 0; display: grid; gap: 9px; }
.guide-tips li { padding-left: 19px; position: relative; font-size: 0.82rem; color: var(--app-text2); }
.guide-tips li svg { position: absolute; left: 0; top: 3px; color: var(--app-text3); }
.guide-tips b { color: var(--app-text1); }

.guide-refine { background: var(--app-card-alt); border-radius: 12px; padding: 13px 13px 1px; }
.guide-refine p { font-size: 0.8rem; color: var(--app-text2); margin: 0 0 12px; }
.guide-refine b { color: var(--app-text1); }

.guide-flow-tabs { display: flex; align-items: center; flex-wrap: wrap; gap: 4px; font-size: 0.84rem; color: var(--app-text1); font-weight: 600; margin: 4px 0 0; }
.guide-flow-tabs svg { color: var(--app-text3); }

/* Trigger button, About & Credits (above Map & Data per Ged's placement) */
.guide-open-block { background: var(--app-bg); border-radius: 10px; padding: 12px 13px; margin-bottom: 8px; }
.guide-open-btn {
  display: flex; align-items: center; justify-content: center; gap: 7px;
  width: 100%; border: none; border-radius: 9px;
  background: var(--app-accent-fill); color: var(--app-accent-ontext);
  font-weight: 700; font-size: 0.83rem; padding: 10px 12px;
}

/* ── First-run welcome overlay ──────────────────────────────────── */
#welcome-overlay {
  position: fixed;
  inset: 0;
  z-index: 9999;
  background: rgba(0, 0, 0, 0.88);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  opacity: 1;
  transition: opacity 0.35s ease;
}
#welcome-overlay.welcome-hide {
  opacity: 0;
  pointer-events: none;
}
#welcome-card {
  background: var(--app-header);
  border: 1px solid var(--app-accent-fill);
  border-radius: 18px;
  padding: 24px 20px 20px;
  max-width: 420px;
  width: 100%;
  max-height: 90vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 14px;
  box-shadow: 0 8px 40px rgba(0, 0, 0, 0.7);
}
.welcome-hero {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0;
}
.welcome-hero-top {
  display: grid;
  grid-template-columns: 52px 1fr 52px;
  align-items: center;
  column-gap: 10px;
  width: 100%;
}
.welcome-badge {
  grid-column: 1;
  width: 52px;
  height: 52px;
  display: block;
}
.welcome-wordmark {
  grid-column: 2;
  justify-self: center;
  /* Shifts the wordmark right of dead-center so "Form"'s start (not the
     wordmark's own center) lines up over the "C" in "Caddie" below —
     tuned against the rendered mockup, not a generic guess. */
  transform: translateX(7.8px);
  display: flex;
  align-items: baseline;
  gap: 6px;
  white-space: nowrap;
}
.wm-bold {
  font-size: 2.1rem;
  font-weight: 800;
  color: #fff;
  letter-spacing: -0.01em;
}
.wm-light {
  font-size: 1.5rem;
  font-weight: 400;
  color: #fff;
}
.welcome-app-label {
  font-size: 1.3rem;
  font-weight: 700;
  color: #fff;
  margin-top: -6px;
  text-align: center;
}
#welcome-sub {
  font-size: 0.85rem;
  color: var(--app-text3);
  text-align: center;
  margin: 2px 0 0;
  font-style: italic;
}
.welcome-section {
  background: var(--app-bg);
  border-radius: 12px;
  padding: 12px 14px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.welcome-section-title {
  font-size: 0.85rem;
  font-weight: 700;
  color: var(--app-text2);
  margin-bottom: 2px;
}
.welcome-list {
  margin: 0;
  padding-left: 18px;
  display: flex;
  flex-direction: column;
  gap: 5px;
  font-size: 0.85rem;
  color: var(--app-text1);
  line-height: 1.4;
}
.welcome-storage p {
  font-size: 0.85rem;
  color: var(--app-text1);
  margin: 0;
  line-height: 1.5;
}
.welcome-storage strong {
  color: var(--app-text3);
}
/* Welcome-screen content refresh (v298) — Option C, approved 2026-09-28:
   why → trial value → privacy → start. See
   golf-caddie-trial-demo-scope.md / naming-positioning doc for background;
   mockup: https://claude.ai/artifact/3XQTgtMKEertqWTB5hN1w8 (Option-C board). */
.welcome-why-headline {
  font-size: 1.03rem;
  font-weight: 800;
  color: #fff;
  line-height: 1.3;
}
.welcome-why-tagline {
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--app-text3);
  margin-top: 3px;
  margin-bottom: 2px;
}
.welcome-why .welcome-list {
  margin-top: 2px;
}
.welcome-close {
  display: flex;
  flex-direction: column;
  gap: 0;
}
/* First-run trial-start flow (build phase 9, 2026-09-15) — replaces the
   old plain "Get Started" button (#welcome-btn, removed) with a real Tour
   trial CTA + an "Already have a license?" activation path, per the
   locked "Trial Start Screen, Round 4" mockup:
   https://claude.ai/code/artifact/9beee738-01d9-405f-b00e-d1a86152095c
   See showWelcomeIfNeeded() in app.js for the wiring. */
.welcome-trial-card {
  background: var(--app-card);
  border: 1px solid rgba(155, 231, 184, 0.3);
  border-radius: 14px;
  padding: 14px 15px;
}
.welcome-trial-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 8px;
}
.welcome-trial-title {
  font-size: 0.98rem;
  font-weight: 800;
  background: linear-gradient(135deg, #A2ACB5, #7C858C);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
.welcome-trial-days {
  font-size: 0.7rem;
  font-weight: 700;
  color: #e0c069;
}
.welcome-trial-feats {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.welcome-trial-feats li {
  font-size: 0.8rem;
  color: var(--app-text1);
  display: flex;
  gap: 7px;
  align-items: flex-start;
  line-height: 1.4;
}
.welcome-trial-feats li::before {
  content: "✓";
  color: var(--app-text3);
  font-weight: 800;
  flex-shrink: 0;
}
.welcome-trial-feats li.welcome-trial-feat-highlight {
  background: rgba(224, 192, 105, 0.1);
  margin: 0 -4px;
  padding: 2px 4px;
  border-radius: 4px;
}
.welcome-trial-feats li.welcome-trial-feat-highlight::before {
  color: #e0c069;
}
.welcome-btn-trial {
  width: 100%;
  font-family: inherit;
  border: none;
  border-radius: 10px;
  background: var(--app-accent-fill);
  color: var(--app-accent-ontext);
  font-size: 0.9rem;
  font-weight: 700;
  padding: 12px;
  cursor: pointer;
  transition: background 0.15s;
}
.welcome-btn-trial:active { background: #256f47; }
.welcome-btn-trial:disabled { opacity: 0.6; }
.welcome-trial-nocard {
  text-align: center;
  font-size: 0.7rem;
  color: var(--app-text3);
  margin: 8px 0 0;
}
.welcome-trial-msg {
  display: flex;
  align-items: flex-start;
  gap: 7px;
  margin-top: 10px;
  background: rgba(224, 96, 96, 0.12);
  border: 1px solid rgba(224, 96, 96, 0.35);
  border-radius: 8px;
  padding: 8px 10px;
  font-size: 0.74rem;
  color: #ffd7d7;
  line-height: 1.45;
}
.welcome-license-toggle {
  display: block;
  width: 100%;
  text-align: center;
  font-family: inherit;
  background: none;
  border: none;
  color: var(--app-text3);
  font-size: 0.78rem;
  font-weight: 700;
  padding: 12px 0 0;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.welcome-license-panel {
  margin-top: 10px;
  background: var(--app-card-alt);
  border-radius: 10px;
  padding: 12px 13px 13px;
}
.welcome-license-hint {
  font-size: 0.74rem;
  color: var(--app-text2);
  margin: 0 0 8px;
  line-height: 1.4;
}
.welcome-license-panel input {
  width: 100%;
  box-sizing: border-box;
  background: var(--app-card);
  border: 1px solid rgba(255, 255, 255, 0.14);
  border-radius: 8px;
  color: var(--app-text1);
  padding: 9px 10px;
  font-size: 0.84rem;
  font-family: inherit;
  margin-bottom: 8px;
}
.welcome-btn-activate {
  width: 100%;
  font-family: inherit;
  border: none;
  border-radius: 8px;
  background: var(--app-accent-fill);
  color: var(--app-accent-ontext);
  font-size: 0.82rem;
  font-weight: 700;
  padding: 9px;
  cursor: pointer;
}
.welcome-btn-activate:disabled { opacity: 0.6; }
.welcome-license-msg {
  margin: 9px 0 0;
  border-radius: 8px;
  padding: 8px 10px;
  font-size: 0.74rem;
  line-height: 1.45;
  color: var(--app-text2);
}
.welcome-license-msg.err {
  background: rgba(224, 96, 96, 0.12);
  border: 1px solid rgba(224, 96, 96, 0.35);
  color: #ffd7d7;
}
.welcome-license-msg.err a { color: inherit; font-weight: 700; text-decoration: underline; text-underline-offset: 2px; }
.welcome-license-msg.ok { color: var(--app-text3); }
.welcome-skip-note {
  text-align: center;
  font-size: 0.72rem;
  color: var(--app-text2);
  opacity: 0.8;
  margin: 0;
  line-height: 1.4;
}

/* ── Install & backup nudge (v236) ────────────────────────────────
   Shown at most once per End Round — see maybeShowEndRoundNudge()/
   showEndRoundNudge() in app.js for the full trigger logic. Same
   fixed-overlay-with-dimmed-backdrop family as #welcome-overlay above,
   just bottom-anchored (a lighter, less "stop everything" weight than the
   first-run welcome card) and z-indexed well below it. Mocked up and
   approved by Ged first: https://claude.ai/code/artifact/901069c1-a093-4ea0-8273-d7b9785843c4
   The card itself is deliberately a solid bordered card, NOT the
   backdrop-filter frosted recipe used for #topbar-rec / .range-panel — it
   can appear over any tab's content (not just the Range tab's map), so
   there's no guarantee of real varied imagery behind it to blur. Same
   lesson as the v235 shot-banner fix, applied in reverse here. */
#nudge-overlay {
  position: fixed;
  inset: 0;
  z-index: 500;
  background: rgba(0, 0, 0, 0.6);
  display: flex;
  align-items: flex-end;
  justify-content: center;
  padding: 16px;
  opacity: 1;
  transition: opacity 0.3s ease;
}
#nudge-overlay.nudge-hide {
  opacity: 0;
  pointer-events: none;
}
.nudge-card {
  width: 100%;
  max-width: 380px;
  background: var(--app-card-alt);
  border: 2px solid var(--app-text3);
  border-radius: 16px;
  padding: 16px 16px 14px;
  box-shadow: 0 10px 30px -10px rgba(0, 0, 0, 0.6);
}
.nudge-kicker {
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--app-text3);
  margin-bottom: 6px;
}
.nudge-title { font-size: 1rem; font-weight: 700; color: var(--app-text1); line-height: 1.3; }
.nudge-body { font-size: 0.85rem; color: var(--app-text2); line-height: 1.5; margin-top: 6px; }
.nudge-steps { margin-top: 10px; display: flex; flex-direction: column; gap: 6px; }
.nudge-step { display: flex; gap: 8px; font-size: 0.8rem; color: var(--app-text1); align-items: flex-start; }
.nudge-step-n {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--app-accent-fill);
  color: #fff;
  font-size: 0.66rem;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: 1px;
}
.nudge-divider { height: 1px; background: rgba(155, 231, 184, 0.28); margin: 12px 0 10px; }
.nudge-backup-line {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  font-size: 0.82rem;
  color: var(--app-text2);
}
.nudge-backup-line b { color: var(--app-text1); }
.nudge-link-btn {
  font-size: 0.82rem;
  font-weight: 700;
  color: var(--app-text3);
  text-decoration: underline;
  text-underline-offset: 2px;
  background: none;
  border: none;
  padding: 0;
  font-family: inherit;
  cursor: pointer;
}
.nudge-actions { display: flex; gap: 8px; margin-top: 14px; }
.nudge-btn {
  flex: 1;
  text-align: center;
  padding: 11px 8px;
  border-radius: 10px;
  font-size: 0.86rem;
  font-weight: 700;
  border: none;
  font-family: inherit;
  cursor: pointer;
}
.nudge-btn-primary { background: var(--app-accent-fill); color: var(--app-accent-ontext); }
.nudge-btn-ghost { background: transparent; color: var(--app-text2); border: 1.5px solid rgba(205, 238, 222, 0.4); }
.nudge-btn.full { flex: 1 1 100%; }

/* ── License risk prompt (v266) — reuses the .nudge-card shell above
   verbatim; only the kicker/border swap from mint-green to amber
   (#e0c069, the same tone already used for trial/pin-estimated status
   elsewhere in the app) to signal "this one's about your purchase," not
   the gentler backup reminder. See maybeShowLicenseRiskPrompt() in app.js. */
.nudge-card.risk { border-color: #e0c069; }
.nudge-kicker.risk { color: #e0c069; }

/* ── Double-tap-to-zoom on tab buttons (2026-08-18) ──────────────────
   Reimplements the OS double-tap-zoom gesture ourselves (see app.js's
   double-tap listener for the full why) using the `zoom` property rather
   than `transform: scale()` — `zoom` reflows layout like a real zoom (so
   overflow becomes scrollable and position:sticky/fixed elements keep
   behaving correctly) instead of just visually stretching content in
   place. Deliberately excludes #tab-range: it's the only tab holding a
   live Leaflet map, and scaling a map's container breaks its internal
   touch-coordinate math against its own tile layer — tap-to-set-point
   accuracy on the Range tab matters too much to risk for a readability
   feature. The header (just the brand/tab bar now) zooms regardless of
   which tab is active, since it's never part of the map itself.
   #topbar-rec used to live in this header and zoom along with it too, but
   moved onto the Range tab's map itself as of 2026-08-30 (see the
   .range-panel-adjacent comment for #topbar-rec) — it's now inside the one
   tab this rule deliberately excludes, so it no longer scales up with this
   feature. An accepted tradeoff of getting it real frosted-glass blur
   instead of sitting on flat opaque header colour — see that comment. */
body.ui-zoomed header,
body.ui-zoomed #tab-clubs,
body.ui-zoomed #tab-stats,
body.ui-zoomed #tab-strategy {
  zoom: 1.3;
}
