/* lint-rules:ignore-font-size-file lint-rules:ignore-hex-file
 *
 * Grandfathered: this file pre-dates strict enforcement of the type-scale
 * (--fs-xs/sm/base/lg/xl) and hex-color rules. Existing declarations are
 * exempt; new declarations should use tokens. The grandfathering is a
 * known debt; see scripts/lint-rules.py for context.
 */
/* ── CONNECT ── */
#connect-screen{position:relative;z-index:1;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;text-align:center;gap:28px;padding:40px;}
.connect-glow{position:absolute;top:50%;left:50%;transform:translate(-50%,-60%);width:700px;height:700px;background:radial-gradient(circle at 45% 50%,rgba(255,60,172,.18) 0%,transparent 55%),radial-gradient(circle at 65% 55%,rgba(0,245,212,.12) 0%,transparent 50%);pointer-events:none;}
.connect-eyebrow{font-size:10px;letter-spacing:.25em;text-transform:uppercase;color:var(--accent-hi);font-weight:600;}
.connect-title{font-family:var(--font-ui);font-size:clamp(28px,5vw,52px);font-weight:700;letter-spacing:-.025em;line-height:1.08;color:var(--text);}
.connect-title em{color:var(--accent-hi);font-style:normal;}
.connect-sub{color:var(--muted2);max-width:420px;font-size:14px;line-height:1.8;}
.btn-connect{display:inline-flex;align-items:center;gap:10px;background:#fc4c02;color:#fff;font-family:var(--font-ui);font-size:14px;font-weight:700;padding:14px 30px;border-radius:999px;text-decoration:none;border:none;cursor:pointer;transition:transform .15s,box-shadow .15s;box-shadow:0 0 48px rgba(252,76,2,.4);}
.btn-connect:hover{transform:translateY(-2px);box-shadow:0 0 64px rgba(252,76,2,.55);}
.btn-connect svg{width:18px;height:18px;}
.btn-connect-gpx{display:inline-flex;align-items:center;gap:10px;background:transparent;color:var(--muted2);font-family:var(--font-ui);font-size:14px;font-weight:600;padding:12px 30px;border-radius:999px;border:1px solid var(--border2);cursor:pointer;transition:color .15s,border-color .15s,background .15s;}
.btn-connect-gpx:hover{color:var(--text);border-color:var(--accent);background:var(--accent-dim);}
.btn-connect-gpx svg{width:18px;height:18px;}
.connect-feat{color:var(--muted2);font-size:12px;}
/* Wrapper centers the inline run of features. Inline (not flex) so the
   text wraps naturally on narrow viewports — and the ::before
   middle-dot separator below renders as part of the inline flow. */
.connect-features{text-align:center;max-width:640px;line-height:2;}
.connect-feat + .connect-feat::before{content:"·";margin:0 14px;color:var(--muted);opacity:.5;}
.feat-dot{width:6px;height:6px;border-radius:50%;background:var(--teal);}
.gpx-cluster{display:flex;flex-direction:column;align-items:center;gap:8px;}
.gpx-note{font-size:12px;color:var(--muted);font-style:italic;letter-spacing:.01em;}

/* ── HEADER ── */
header{position:sticky;top:0;z-index:200;display:flex;align-items:center;background:rgba(10,10,26,.93);backdrop-filter:blur(20px);border-bottom:1px solid var(--border);height:56px;transition:background .25s;}
[data-theme='light'] header{background:rgba(250,247,255,.93);}
.header-logo{display:flex;align-items:center;gap:10px;padding:0 20px;font-family:var(--font-ui);font-size:14px;font-weight:600;color:var(--text);letter-spacing:0;border-right:1px solid var(--border);height:100%;flex-shrink:0;}
/* Clickable brand: only the in-app header version has role=link and the
   id #header-logo-link, so the cursor + hover style scope to that. The
   landing-page wordmark stays static. */
.header-logo[role="link"]{cursor:pointer;transition:opacity .15s;}
.header-logo[role="link"]:hover{opacity:.75;}
.header-logo[role="link"]:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:4px;}
.logo-mark{width:28px;height:28px;background:var(--accent);border-radius:6px;display:flex;align-items:center;justify-content:center;color:#fff;}.logo-mark svg{width:20px;height:20px;display:block;}
.header-nav{display:flex;align-items:center;flex:1;overflow-x:auto;scrollbar-width:none;}
.header-nav::-webkit-scrollbar{display:none;}
.nav-tab{display:flex;align-items:center;gap:6px;padding:0 18px;height:56px;font-size:10px;letter-spacing:.06em;text-transform:uppercase;font-weight:500;color:var(--muted);border:none;background:none;cursor:pointer;border-bottom:2px solid transparent;transition:color .2s,border-color .2s;white-space:nowrap;}
.nav-tab:hover{color:var(--text);}
.nav-tab.active{color:var(--text);border-bottom-color:var(--accent);}
.tab-icon{display:none;}

/* ── Nav hierarchy (Phase 6, growth plan v2) ─────────────────────
   All tabs are always visible — we removed the More/Less toggle
   when feedback showed power users wanted everything one click
   away. Hierarchy now comes from color and a subtle separator
   rather than from hiding things.

   Primary tabs (Overview, Plan, Race Predictor, Activity Log,
   About, Upgrade) render in full text color so the eye lands on
   them first. Secondary tabs (Training, Heart Rate, VO2 Max,
   Fatigue & Load, Custom Metrics, Admin) stay in the muted default
   that .nav-tab already declares. Hover and active both bring any
   tab to full color, so the muted treatment is a hint, not a
   barrier.

   The first secondary tab gets a subtle border-left to mark the
   group break. Adjacent-sibling selector means it only fires
   when a primary tab actually precedes it; in the unlikely case
   that a primary tab moves or is removed at runtime, the border
   self-cancels rather than appearing in the wrong place. */
.header-nav .nav-tab[data-nav-group="primary"]{color:var(--text);}
.header-nav .nav-tab[data-nav-group="primary"]:hover{color:var(--text);}
.header-nav .nav-tab[data-nav-group="primary"] + .nav-tab[data-nav-group="secondary"]{border-left:1px solid var(--border);margin-left:6px;padding-left:24px;}

.header-right{display:flex;align-items:center;gap:12px;padding:0 20px;border-left:1px solid var(--border);height:100%;flex-shrink:0;}
.athlete-pill{display:flex;align-items:center;gap:8px;background:var(--surface);border:1px solid var(--border);border-radius:999px;padding:5px 12px 5px 6px;font-size:12px;}
.athlete-pill img{width:22px;height:22px;border-radius:50%;object-fit:cover;}
.btn-sm{background:none;border:1px solid var(--border);color:var(--muted);font-family:var(--font-data);font-size:10px;letter-spacing:.08em;text-transform:uppercase;padding:5px 10px;border-radius:6px;cursor:pointer;transition:border-color .2s,color .2s;}
.btn-sm:hover{border-color:var(--rose);color:var(--rose);}

/* ── LAYOUT ── */
#app{position:relative;z-index:1;}
.page{display:none;}
.page.active{display:block;}
.page-inner{max-width:1380px;margin:0 auto;padding:32px 28px;}
.section-head{display:flex;align-items:flex-end;justify-content:space-between;margin-bottom:20px;flex-wrap:wrap;gap:12px;}
.section-title{font-family:var(--font-ui);font-size:14px;font-weight:600;color:var(--text);letter-spacing:-.01em;}
.section-sub{font-size:12px;color:var(--muted);margin-top:2px;}

/* ============================================================
   LANDING v2 + DEMO FLOATING CTA

   Note: font-sizes are literal px here. The project rules describe
   a 5-step --fs-* scale (xs/sm/base/lg/xl). Those tokens aren't
   defined in tokens.css yet. When you add them, swap the literal
   sizes below for var() references.
   ============================================================ */

/* ── LANDING v2 ── */

/* Primary demo CTA. Cyan-accent button, the heaviest thing on the
   page above the fold. Replaces the old white Google button as the
   primary action. */
.landing-demo-primary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--accent);
  color: #ffffff;
  font-family: var(--font-ui);
  font-size: 15px;
  font-weight: 600;
  padding: 14px 32px;
  border-radius: 999px;
  text-decoration: none;
  border: none;
  cursor: pointer;
  height: 48px;
  min-width: 220px;
  transition: transform .15s, box-shadow .15s, background .15s;
  box-shadow: 0 0 32px var(--accent-dim);
}
.landing-demo-primary::after {
  content: " →";
  margin-left: 4px;
  transition: transform .15s;
}
.landing-demo-primary:hover {
  transform: translateY(-1px);
  background: var(--accent-hi);
  box-shadow: 0 0 48px var(--accent-dim);
}
.landing-demo-primary:hover::after {
  transform: translateX(2px);
}

/* Auth row: Google button + "Sign up with email" link side by side.
   On narrow viewports stacks vertically (flex-wrap). */
.landing-auth-row {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  justify-content: center;
}

/* Compact Google variant: smaller than the original landing-google,
   so it doesn't dominate the cyan demo CTA above it. Combines both
   classes (.landing-google.landing-google-compact) to outweigh the
   single-class .landing-google rule in components.css on specificity
   — components.css loads after layout.css, so single-class rules
   there would otherwise win on source order. */
.landing-google.landing-google-compact {
  width: auto;
  height: 36px;
  padding: 6px 16px;
  font-size: 12px;
  background: transparent;
  color: var(--text2);
  border: 1px solid var(--border);
  font-weight: 500;
  box-shadow: none;
}
.landing-google.landing-google-compact:hover {
  background: var(--surface);
  border-color: var(--border2);
  color: var(--text);
  box-shadow: none;
  transform: none;
}

/* Strava attribution line at the bottom of state A. Compliance and
   trust signal — Strava is the user's expectation. */
.landing-strava-attr {
  margin-top: 20px;
  font-size: 12px;
  color: var(--muted);
  letter-spacing: .01em;
  max-width: 420px;
  line-height: 1.5;
}

/* New feature block: three question/answer pairs instead of the
   dotted pill row. Vertical stack on desktop and mobile. */
.landing-features-v2 {
  display: flex;
  flex-direction: column;
  gap: 20px;
  max-width: 480px;
  margin-top: 24px;
  text-align: left;
}
.landing-feat-q {
  margin: 0 0 4px 0;
  font-family: var(--font-ui);
  font-size: 15px;
  font-weight: 600;
  color: var(--text);
  line-height: 1.4;
}
.landing-feat-a {
  margin: 0;
  font-size: 13px;
  color: var(--muted2);
  line-height: 1.5;
}

/* ── DEMO FLOATING CTA ── */

/* Sticky pill anchored to bottom-right. Revealed once the user
   scrolls past 320px (see demo-banner.js). Hidden on small viewports
   to avoid covering the mobile nav drawer toggle. */
.demo-floating-cta {
  position: fixed;
  right: 80px;
  bottom: 24px;
  z-index: 180;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: var(--accent);
  color: #ffffff;
  font-family: var(--font-ui);
  font-size: 14px;
  font-weight: 600;
  padding: 12px 22px;
  border-radius: 999px;
  text-decoration: none;
  box-shadow: 0 6px 24px rgba(0, 0, 0, .35), 0 0 32px var(--accent-dim);
  opacity: 0;
  transform: translateY(12px);
  pointer-events: none;
  transition: opacity .2s ease, transform .2s ease, background .15s;
}
.demo-floating-cta-visible {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}
.demo-floating-cta:hover {
  background: var(--accent-hi);
}
.demo-floating-cta-arrow {
  transition: transform .15s;
}
.demo-floating-cta:hover .demo-floating-cta-arrow {
  transform: translateX(2px);
}

@media (max-width: 480px) {
  /* Hide on mobile — the persistent demo banner at the top already
     provides the CTA, and a floating pill competes with the nav
     drawer toggle and mobile bottom-of-screen browser chrome. */
  .demo-floating-cta { display: none; }
}

/* ── Overview upper: rail, strip, or collapsed Numbers panel ──────
   Three modes based on viewport width:

   ≥960px  → rail mode: .overview-upper-grid becomes a two-column
             grid with the rail in column 2. #ov-rail visible.
             #ov-rail-strip and #ov-sidebar hidden.
   900–959 → strip mode: grid stays single-column. A 36px wide
             #ov-rail-strip is fixed to the right edge. Clicking
             it opens a slide-in drawer (handled in JS).
             #ov-rail and #ov-sidebar hidden.
   <900px  → collapsed mode: no rail, no strip. #ov-sidebar holds
             the existing <details> panel below This Week. The
             strip used to opt in at 640 but caught landscape
             phones and small tablets in portrait, where the
             fixed-right chrome ate viewport width. 900 keeps
             the strip on narrow desktop windows only.

   matchMedia checks in sidebar-stats.js mirror these breakpoints
   so JS paints into whichever host CSS is showing. */
.overview-upper-grid {
  display: block;
}

/* Hide the rail and the strip by default; specific media queries
   below opt them in for the modes that need them. */
.numbers-rail-host,
.numbers-rail-strip-host {
  display: none;
}

@media (min-width: 960px) {
  .overview-upper-grid {
    display: grid;
    grid-template-columns: 1fr 240px;
    gap: var(--space-section);
    align-items: start;
  }
  .overview-upper-grid > .numbers-rail-host {
    display: block;
  }
  /* In rail mode the collapsed <details> is redundant. The selector
     is ID-based now that #ov-sidebar lives inside the "More detail"
     expander rather than being a direct child of .overview-upper. */
  #ov-sidebar {
    display: none;
  }

  /* User-opted desktop collapse. body.numbers-rail-collapsed is
     toggled by sidebar-stats.js at render time based on
     readCollapseState(). When set at desktop widths the rail hides,
     the grid flattens, and the strip takes over on the right edge.
     The page-inner gets the same 36px right padding so content
     doesn't run under the strip. */
  body.numbers-rail-collapsed .overview-upper-grid {
    grid-template-columns: 1fr;
  }
  body.numbers-rail-collapsed .overview-upper-grid > .numbers-rail-host {
    display: none;
  }
  body.numbers-rail-collapsed .numbers-rail-strip-host {
    display: block;
  }
  body.numbers-rail-collapsed .page-inner {
    padding-right: calc(28px + 36px);
  }
}

@media (min-width: 900px) and (max-width: 959.98px) {
  .numbers-rail-strip-host {
    display: block;
  }
  /* The strip is fixed at the right edge of the viewport. Push the
     page-inner content 36px to the left so nothing runs under it.
     This is set on the page-inner specifically (not body) so the
     viewport edge stays the right place for the strip to anchor. */
  .page-inner {
    padding-right: calc(28px + 36px);
  }
  /* In strip mode the collapsed <details> is still useful for users
     who don't want to open the drawer, but the strip+drawer is the
     primary surface. Hide the collapsed <details> here too so we
     don't double-render the same numbers. ID-based selector now that
     #ov-sidebar lives inside the "More detail" expander. */
  #ov-sidebar {
    display: none;
  }
}

/* ── LANDING DEMO STRIP ────────────────────────────────────────────
   Live above-the-fold dashboard preview rendered by
   js/landing-demo-strip.js into #landing-features. Replaces the
   default three-feature-question block on snapshot-fetch success.

   Three-column grid at ≥860px: today card · race predictions · numbers.
   Below that breakpoint, single column stacked vertically.

   Visual language matches the production cards (same .hero-card
   classes for the readiness preview; race + numbers previews use
   .ldp-* classes since their live counterparts have evolved past
   what the preview needs).

   No interactivity: the whole strip is read-only. The "Try the full
   live demo →" CTA below routes to /demo/morgan for hands-on. */

.landing-demo-strip {
  display: flex;
  flex-direction: column;
  gap: var(--space-row);
  width: 100%;
  max-width: 1080px;
  margin-top: 28px;
  text-align: left;
  z-index: 1;
}

/* "Live preview: Morgan..." badge above the grid. Small, muted —
   visitors should understand it's sample data without being shouted at. */
.ldp-badge {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  align-self: center;
  font-family: var(--font-ui);
  font-size: var(--fs-xs);
  color: var(--muted2);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.ldp-badge-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--lime);
  box-shadow: 0 0 6px var(--lime);
}

/* Three-column grid above the breakpoint, stacked below. */
.ldp-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-row);
}
@media (min-width: 860px) {
  .ldp-grid {
    grid-template-columns: minmax(0, 1.4fr) minmax(0, 1.4fr) minmax(0, 1fr);
  }
}

.ldp-col {
  min-width: 0;  /* let grid children shrink instead of overflowing */
}

/* The hero-card preview reuses .hero-card and .hero-card-* directly.
   We scale down slightly inside the landing strip so it fits the
   column without dominating the grid. */
.ldp-col-today .hero-card {
  margin: 0;
}

/* Race predictions preview ─────────────────────────────────────────
   Lightweight version of the historical race-predictions-strip
   surface (the live Overview surface is now a grid table). The
   preview keeps the "stat strip" visual because it reads compact in
   a column and the ±CI is the primary differentiator we're selling. */

.ldp-race-eyebrow {
  font-family: var(--font-ui);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.10em;
  color: var(--muted);
  font-weight: 600;
  margin-bottom: 10px;
}

.ldp-race-row {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 8px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r);
  padding: 16px 12px;
}

.ldp-race-cell {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 4px 2px;
}

.ldp-race-distance {
  font-family: var(--font-ui);
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--muted2);
  letter-spacing: 0.06em;
}

.ldp-race-time {
  font-family: var(--font-data);
  font-size: var(--fs-lg);
  font-weight: 600;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}

.ldp-race-ci {
  font-family: var(--font-data);
  font-size: var(--fs-xs);
  color: var(--muted2);
  font-variant-numeric: tabular-nums;
  min-height: 1em;
}

.ldp-race-cell-empty .ldp-race-time {
  color: var(--muted);
}

/* Numbers preview ─────────────────────────────────────────────────
   Compact 2×2 grid of metric tiles. Standalone visual; doesn't reuse
   the sidebar-stats classes because those depend on full SPA state
   and the layout there is mode-dependent (rail / strip / collapsed). */

.ldp-num-eyebrow {
  font-family: var(--font-ui);
  font-size: var(--fs-xs);
  text-transform: uppercase;
  letter-spacing: 0.10em;
  color: var(--muted);
  font-weight: 600;
  margin-bottom: 10px;
}

.ldp-num-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px;
}

.ldp-num-tile {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  padding: 12px 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r);
}

.ldp-num-value {
  font-family: var(--font-data);
  font-size: var(--fs-lg);
  font-weight: 600;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}

.ldp-num-label {
  font-family: var(--font-ui);
  font-size: var(--fs-xs);
  color: var(--muted2);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 600;
}

.ldp-num-unit {
  font-family: var(--font-ui);
  font-size: var(--fs-xs);
  color: var(--muted);
  letter-spacing: 0.02em;
}

/* TSB color tone — mirrors the live sidebar's form color logic. */
.ldp-num-tile-fresh .ldp-num-value     { color: var(--lime); }
.ldp-num-tile-fatigued .ldp-num-value  { color: var(--rose); }

/* Inline mid-page CTA below the strip. Same primary-action visual
   weight as the landing-demo-primary above the fold, but smaller
   and centered. */
.ldp-cta {
  display: inline-block;
  align-self: center;
  margin-top: 8px;
  padding: 10px 22px;
  background: var(--accent);
  color: #ffffff;
  font-family: var(--font-ui);
  font-size: var(--fs-sm);
  font-weight: 600;
  text-decoration: none;
  border-radius: 999px;
  transition: filter 0.15s, transform 0.15s, box-shadow 0.15s;
}
.ldp-cta:hover {
  filter: brightness(1.08);
  transform: translateY(-1px);
  box-shadow: 0 0 24px var(--accent-dim);
}

