/* ==========================================================================
   RydR — design tokens
   Palette: neutral slate surfaces, a single confident blue accent, calm
   amber caution and red alert. Type is Inter throughout for a clean,
   modern app feel; telemetry numbers use JetBrains Mono for precision.
   ========================================================================== */
:root{
  --bg-void:#0a0c11;
  --bg-panel:#12151c;
  --bg-panel-raised:#181c26;
  --bg-panel-hover:#1e2330;
  --border-subtle:#212632;
  --border-strong:#333c4c;

  --neon-green:#4f8cff;
  --neon-green-dim:#2c5fc7;
  --neon-green-glow:rgba(79,140,255,0.28);
  --cyan:#22d3ee;
  --alert-red:#ef4444;
  --alert-red-glow:rgba(239,68,68,0.3);
  --warn-amber:#f59e0b;
  /* Labs category accent — nothing else in the app uses violet, which is
     the point: it reads as "new/experimental" at a glance. Light-toned
     themes override this one token below (search "labs-violet") instead
     of every theme needing its own tuned value, since nothing else keys
     off it. */
  --labs-violet:#a78bfa;
  /* Route editor's trim-range / detour / replay-marker accent — shared with
     the chase-cam glow and Labs route preview (search "ff9d2e" in js/) so
     it reads as one consistent "this is a route/path" color across the app.
     Fixed like --labs-violet, not themed with --neon-green: it marks a
     specific kind of content (an edited or highlighted route segment), not
     the app's general accent. */
  --route-highlight:#ff9d2e;
  --route-highlight-ink:#1a0f05;

  --text-primary:#eef1f5;
  --text-muted:#8b93a3;
  /* Was #565e6c — 2.6-2.8:1 against the panel backgrounds it's actually
     used on (stat labels, panel titles), well under WCAG AA's 4.5:1 for
     normal text. Lightened to clear 4.5:1+ against both --bg-panel and
     --bg-panel-raised while staying visibly dimmer than --text-muted so
     the two still read as a hierarchy, not a coin-flip. */
  --text-dim:#818999;

  /* Menu panel background is its own token (not just re-derived from
     --bg-panel) so it can stay a translucent glass surface — every theme
     sets this to match its own light/dark direction so .menu-link's
     existing --text-muted/--text-primary tokens always land on the
     correct contrast side. */
  --menu-panel-bg:rgba(18,21,28,0.92);

  --font-display:'Barlow Condensed',sans-serif;
  --font-body:'Inter',sans-serif;
  --font-mono:'JetBrains Mono',monospace;

  --radius-sm:8px;
  --radius-md:14px;
  --radius-lg:20px;
  --touch:48px;
}

*{box-sizing:border-box;}
html,body{margin:0;padding:0;}
body{
  background:var(--bg-void);
  background-image:
    radial-gradient(circle at 20% 0%, rgba(79,140,255,0.05), transparent 45%),
    radial-gradient(circle at 85% 100%, rgba(34,211,238,0.03), transparent 45%);
  color:var(--text-primary);
  font-family:var(--font-body);
  min-height:100vh;
  -webkit-font-smoothing:antialiased;
}
a{color:inherit;text-decoration:none;}
button{font-family:inherit;}
::selection{background:var(--neon-green-dim);color:#02110a;}

:focus-visible{
  outline:2px solid var(--neon-green);
  outline-offset:2px;
  border-radius:4px;
}

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

/* Cross-Document View Transitions (Chrome/Edge on Android as of this
   writing) — a soft cross-fade between full page navigations instead of
   a hard flash-to-white, at the cost of zero JS and zero risk: browsers
   without support simply ignore this at-rule and navigate exactly as
   before. Skipped under reduced-motion for the same reason every other
   animation in this file is. */
@media (prefers-reduced-motion: no-preference){
  @view-transition{
    navigation:auto;
  }
}

/* Labs category accent (see --labs-violet near the top of :root) needs a
   darker, light-background-safe value on every light-toned theme — one
   shared rule instead of six separate per-theme overrides, since nothing
   else keys off this token yet. */
body.theme-light, body.theme-silver, body.theme-sand, body.theme-highvis, body.theme-alpine, body.theme-cloud{
  --labs-violet:#7c5cd6;
}

/* ---------- layout shell ---------- */
.app-shell{
  display:flex;
  flex-direction:column;
  min-height:100vh;
}

/* ---------- Track HUD (racing-telemetry inspired full-screen view) ----------
   Landscape-first layout (matches the reference this was built from); the
   media query near the bottom of this block stacks everything vertically
   for portrait/narrow screens instead of squeezing the horizontal layout.
   Fixed dark identity — does NOT respond to the app's separate light/dark
   theme setting (previously had a body.theme-light override; removed —
   Track HUD keeps its own single telemetry-HUD look regardless). A custom
   Track HUD theme (Settings > Track HUD > Themes) works the same way:
   exactly one style per theme, an exact recreation of its reference
   image/template, never a separate light/dark variant of itself. */
.hud-shell{
  background: radial-gradient(circle at 50% 0%, #10151d 0%, #05070a 70%);
  color:#eef3fa;
  padding:clamp(6px, 2.2vh, 28px) clamp(16px, 4vw, 48px);
  overflow-y:auto;
}
/* Landscape is this HUD's primary use case (phone mounted on a bike) — the
   whole shell should fit the viewport with no scrolling there, unlike
   portrait (kept scrollable above as a safety net for the stacked layout
   the orientation:portrait block below switches to). Every size this rule
   and the ones below it touch (gauge block, digital card, bottom-row
   columns) was a fixed px value tuned for a tall/scrolling layout; the
   .hud-gauge-block bezel/carbon/glass pseudo-elements are already sized in
   %, so shrinking the block's own box via clamp() cascades correctly
   through them with no separate changes needed there. */
@media (orientation: landscape){
  .hud-shell{ overflow-y:hidden; }
}
.hud-topbar{
  display:grid;
  grid-template-columns:1fr auto 1fr;
  align-items:start;
  gap:16px;
  margin-bottom:clamp(6px, 2vh, 28px);
  padding-left:44px;
  /* .hud-shell is a flex column with a fixed height:100vh (see
     .full-screen-view). Without flex-shrink:0, a flex column squeezes
     any item that doesn't fit rather than letting the column overflow
     and scroll — and it does this disproportionately, so a row holding
     large fixed-size children (the 220-280px gauge cards below) can get
     squashed down to a few px while those children refuse to shrink,
     spilling out over whatever comes right after them in the layout.
     Locking every row to its natural height turns "silently overlap
     the next section" into "the view scrolls," which is what
     .hud-shell's own overflow-y:auto was already trying to provide. */
  flex-shrink:0;
}
.hud-timer{ display:flex; flex-direction:column; gap:4px; }
.hud-timer-main{ font-family:var(--font-mono); font-size:28px; font-weight:700; letter-spacing:0.02em; }
.hud-timer-sub{ display:flex; gap:14px; font-size:12px; color:#7d8ba0; font-family:var(--font-mono); }
.hud-timer-sub b{ color:#c7d3e6; font-weight:600; }
.hud-brand{ display:flex; flex-direction:column; align-items:center; gap:4px; text-align:center; position:relative; }
.hud-back-btn{
  position:absolute; left:-52px; top:-4px;
  width:36px; height:36px; border-radius:10px;
  display:grid; place-items:center;
  background:rgba(255,255,255,0.06); border:1px solid rgba(255,255,255,0.14); color:#c7d3e6; cursor:pointer;
  font-size:16px;
}
.hud-brand-title{ font-family:var(--font-display); font-weight:700; font-size:16px; letter-spacing:0.03em; color:#fff; text-transform:uppercase; }
.hud-brand-sub{ font-size:11px; color:#7d8ba0; }
.hud-style-toggle{ display:flex; gap:4px; margin-top:8px; background:rgba(255,255,255,0.05); border-radius:999px; padding:3px; }
.hud-style-btn{
  border:none; background:transparent; color:#7d8ba0;
  font-family:var(--font-display); font-weight:600; font-size:10px; letter-spacing:0.04em; text-transform:uppercase;
  padding:5px 12px; border-radius:999px; cursor:pointer;
}
.hud-style-btn.active{ background:rgba(255,255,255,0.14); color:#fff; }
.hud-weather{ display:flex; flex-direction:column; align-items:flex-end; gap:2px; font-size:12px; color:#9aa8bd; text-align:right; }
.hud-weather div:first-child{ color:#c7d3e6; }

/* Horizontal row on landscape/wide screens (this HUD's primary use case —
   phone mounted landscape on a bike) — if the gauges + mini-stats block
   don't all fit, the row scrolls horizontally instead of squeezing.
   Portrait screens get a stacked layout instead (see the orientation
   media query below); the old trigger for that was `max-width:900px`
   *or* portrait, which also caught plenty of phones in landscape
   (routinely under 900px wide) and stacked them too — narrowed to
   portrait-only so landscape never stacks. */
.hud-gauges-row{
  display:flex;
  align-items:center;
  justify-content:center;
  gap:24px;
  margin-bottom:clamp(6px, 2vh, 24px);
  /* See .hud-topbar's comment — this row is the one that actually held
     the 220-280px gauge cards, so it's the one that was visibly
     collapsing to a few px and letting them overlap the topbar/back
     button. */
  flex-shrink:0;
  overflow-x:auto;
  overflow-y:hidden;
  scroll-snap-type:x proximity;
  -webkit-overflow-scrolling:touch;
  padding-bottom:6px; /* keeps the scrollbar from crowding the gauges' drop shadows */
}
.hud-gauges-row::-webkit-scrollbar{ height:6px; }
.hud-gauges-row::-webkit-scrollbar-track{ background:rgba(255,255,255,0.05); border-radius:3px; }
.hud-gauges-row::-webkit-scrollbar-thumb{ background:rgba(79,140,255,0.4); border-radius:3px; }
.hud-gauge-slot{ display:flex; justify-content:center; flex:0 0 auto; scroll-snap-align:center; }
/* Speedo + lean gauge overlap, matching the reference instrument cluster
   this HUD is styled after: the lean gauge sits partially behind and
   below the speedometer rather than side by side. The lean slot's own
   negative margins pull it under the speedo slot, whose higher z-index
   lets its opaque bezel/face naturally occlude the overlapped crescent —
   no clip-path needed, just paint order + position. Margins scale with
   the same clamp() ratio as .hud-gauge-block's own max-width below so the
   overlap stays proportional at any viewport size. Landscape only (this
   HUD's primary use case); portrait resets to a plain stacked column. */
.hud-gauge-cluster{ position:relative; display:flex; flex:0 0 auto; }
.hud-gauge-cluster .hud-gauge-slot-speedo{ position:relative; z-index:5; }
.hud-gauge-cluster .hud-gauge-slot-lean{ position:relative; z-index:2; margin-left:clamp(-96px, -11vh, -48px); margin-top:clamp(21px, 4.8vh, 42px); }
.hud-gauge-block-lean{ max-width:clamp(118px, 27vh, 236px); }
.hud-gauge-block-lean svg{ max-width:clamp(118px, 27vh, 236px); }
@media (orientation: portrait){
  .hud-gauge-cluster{ flex-direction:column; align-items:center; }
  .hud-gauge-cluster .hud-gauge-slot-lean{ margin-left:0; margin-top:16px; }
}
/* Digital style's cards weren't designed with this overlap in mind — keep
   that variant laid out side by side as before. */
.hud-shell.style-digital .hud-gauge-slot-lean{ margin-left:0; margin-top:0; }
/* Analog/Digital toggle — Analog (.hud-gauge-block) is the default; the
   toggle in the topbar adds .style-digital to .hud-shell to swap which
   variant shows in each slot. */
.hud-shell.style-digital .hud-gauge-block{ display:none; }
.hud-digital-card{ display:none; }
.hud-shell.style-digital .hud-digital-card{ display:flex; }

/* Digital gauge — modern EV-dashboard style: flat card, thin progress
   ring, big flat digits, no bezel/ticks/glass. */
.hud-digital-card{
  position:relative;
  width:clamp(120px, 30vh, 220px); height:clamp(120px, 30vh, 220px);
  border-radius:28px;
  background:linear-gradient(160deg, #131a26 0%, #070a10 100%);
  border:1px solid rgba(255,255,255,0.08);
  box-shadow:0 20px 50px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.06);
  flex-direction:column; align-items:center; justify-content:center;
  overflow:hidden;
}
.hud-digital-card::before{
  content:"";
  position:absolute; inset:0; border-radius:inherit;
  background:linear-gradient(180deg, rgba(255,255,255,0.06), transparent 45%);
  pointer-events:none;
}
.hud-digital-card svg{ position:absolute; inset:10px; width:calc(100% - 20px); height:calc(100% - 20px); }
.hud-digital-value{ position:relative; z-index:1; font-family:var(--font-mono); font-weight:800; font-size:52px; color:#fff; line-height:1; }
.hud-digital-unit{ position:relative; z-index:1; font-family:var(--font-display); font-size:12px; letter-spacing:0.16em; text-transform:uppercase; color:#7d8ba0; margin-top:6px; }

.hud-gauge-block{ position:relative; display:flex; justify-content:center; max-width:clamp(140px, 32vh, 280px); margin:0 auto; }
/* Metallic bezel ring — brushed-metal conic gradient with a lit top edge
   and shadowed underside, like a machined gauge surround. */
.hud-gauge-block::before{
  content:"";
  position:absolute;
  top:50%; left:50%; transform:translate(-50%,-50%);
  width:94%; height:94%;
  border-radius:50%;
  z-index:0;
  background:
    radial-gradient(circle at 50% 50%, transparent 78%, rgba(0,0,0,0.5) 79%, transparent 82%),
    conic-gradient(from 200deg, #4a5262, #8b95a6, #3d4452, #1a1e25, #6b7688, #93a0b3, #262b33, #4a5262);
  box-shadow:
    0 10px 26px rgba(0,0,0,0.55),
    inset 0 2px 3px rgba(255,255,255,0.25),
    inset 0 -3px 8px rgba(0,0,0,0.7);
}
/* Carbon-fiber face, inset within the bezel — must stay BEHIND the SVG
   (ticks/needle) even though ::after normally paints above real children;
   explicit z-index below the svg's fixes that. */
.hud-gauge-block::after{
  content:"";
  position:absolute;
  top:50%; left:50%; transform:translate(-50%,-50%);
  width:82%; height:82%;
  border-radius:50%;
  z-index:1;
  background-color:#0b0e13;
  background-image:
    repeating-linear-gradient(45deg, rgba(255,255,255,0.05) 0, rgba(255,255,255,0.05) 1px, transparent 1px, transparent 6px),
    repeating-linear-gradient(-45deg, rgba(255,255,255,0.05) 0, rgba(255,255,255,0.05) 1px, transparent 1px, transparent 6px),
    repeating-linear-gradient(45deg, rgba(0,0,0,0.45) 0, rgba(0,0,0,0.45) 3px, transparent 3px, transparent 6px),
    repeating-linear-gradient(-45deg, rgba(0,0,0,0.45) 0, rgba(0,0,0,0.45) 3px, transparent 3px, transparent 6px);
  background-size:6px 6px;
  box-shadow:inset 0 0 26px rgba(0,0,0,0.75), inset 0 3px 4px rgba(0,0,0,0.8);
}
.hud-gauge-block svg{ position:relative; z-index:2; width:100%; max-width:clamp(140px, 32vh, 280px); height:auto; }
/* Glass dome reflection — sits above the needle/ticks, like real cover glass. */
.hud-gauge-glass{
  position:absolute;
  z-index:3;
  top:7%; left:16%;
  width:68%; height:36%;
  border-radius:50%;
  background:linear-gradient(180deg, rgba(255,255,255,0.22), rgba(255,255,255,0.03) 65%, transparent);
  transform:rotate(-10deg);
  pointer-events:none;
}
.hud-gauge-overlay{
  position:absolute; z-index:4; top:50%; left:50%; transform:translate(-50%,-50%);
  text-align:center; pointer-events:none;
}
.hud-gauge-value{ font-family:var(--font-mono); font-size:clamp(34px, 6vw, 54px); font-weight:800; color:#fff; line-height:1; }
.hud-gauge-unit{ font-family:var(--font-display); font-size:12px; letter-spacing:0.14em; color:#7d8ba0; margin-top:4px; text-transform:uppercase; }
/* Digital readout relocated to 6 o'clock, tucked under the needle hub
   instead of dead-centered over it (matches the reference cluster). */
.hud-gauge-overlay-bottom{ top:75%; }
.hud-gauge-overlay-bottom .hud-gauge-value{ font-size:clamp(18px, 4vh, 26px); }
.hud-gauge-overlay-bottom .hud-gauge-unit{ font-size:9px; margin-top:1px; }

.hud-mini-stats{ display:flex; flex-direction:column; gap:16px; flex:0 0 auto; }
.hud-mini-label{ font-family:var(--font-display); font-size:10px; letter-spacing:0.1em; text-transform:uppercase; color:#7d8ba0; }
.hud-mini-value{ font-family:var(--font-mono); font-size:20px; font-weight:700; color:#fff; margin-top:4px; }

.hud-bottom-row{
  display:grid;
  grid-template-columns:clamp(140px, 22vw, 220px) 1fr clamp(140px, 22vw, 220px);
  gap:16px;
  /* See .hud-topbar's comment. */
  flex-shrink:0;
}
.hud-panel{
  position:relative;
  background-color:#0d1117;
  background-image:
    linear-gradient(rgba(255,255,255,0.03), rgba(255,255,255,0.03)),
    repeating-linear-gradient(45deg, rgba(255,255,255,0.035) 0, rgba(255,255,255,0.035) 1px, transparent 1px, transparent 7px),
    repeating-linear-gradient(-45deg, rgba(255,255,255,0.035) 0, rgba(255,255,255,0.035) 1px, transparent 1px, transparent 7px),
    repeating-linear-gradient(45deg, rgba(0,0,0,0.35) 0, rgba(0,0,0,0.35) 3px, transparent 3px, transparent 7px),
    repeating-linear-gradient(-45deg, rgba(0,0,0,0.35) 0, rgba(0,0,0,0.35) 3px, transparent 3px, transparent 7px);
  background-size:auto, 7px 7px, 7px 7px, 7px 7px, 7px 7px;
  border:1px solid rgba(255,255,255,0.1);
  border-radius:16px;
  padding:14px;
}
.hud-mini-stat{
  position:relative;
  background-color:#0d1117;
  background-image:
    linear-gradient(rgba(255,255,255,0.04), rgba(255,255,255,0.04)),
    repeating-linear-gradient(45deg, rgba(255,255,255,0.035) 0, rgba(255,255,255,0.035) 1px, transparent 1px, transparent 7px),
    repeating-linear-gradient(-45deg, rgba(255,255,255,0.035) 0, rgba(255,255,255,0.035) 1px, transparent 1px, transparent 7px),
    repeating-linear-gradient(45deg, rgba(0,0,0,0.35) 0, rgba(0,0,0,0.35) 3px, transparent 3px, transparent 7px),
    repeating-linear-gradient(-45deg, rgba(0,0,0,0.35) 0, rgba(0,0,0,0.35) 3px, transparent 3px, transparent 7px);
  background-size:auto, 7px 7px, 7px 7px, 7px 7px, 7px 7px;
  border:1px solid rgba(255,255,255,0.1);
  border-radius:14px; padding:12px 18px; min-width:110px; text-align:center;
}
.hud-heading-panel{ display:flex; flex-direction:column; align-items:center; justify-content:center; gap:8px; }
.hud-mini-gauge-frame{
  position:relative;
  width:clamp(72px, 18vh, 120px); height:clamp(72px, 18vh, 120px);
  border-radius:50%;
  background:
    radial-gradient(circle at 50% 50%, transparent 82%, rgba(0,0,0,0.5) 83%, transparent 86%),
    conic-gradient(from 200deg, #4a5262, #8b95a6, #3d4452, #1a1e25, #6b7688, #93a0b3, #262b33, #4a5262);
  box-shadow:0 4px 12px rgba(0,0,0,0.5), inset 0 2px 3px rgba(255,255,255,0.25), inset 0 -2px 5px rgba(0,0,0,0.7);
  display:flex; align-items:center; justify-content:center;
}
.hud-mini-gauge-frame svg{ position:relative; width:88%; height:88%; }
.hud-gauge-glass-sm{ top:10%; left:20%; width:60%; height:32%; }
.hud-panel-label{ font-family:var(--font-display); font-size:11px; letter-spacing:0.1em; text-transform:uppercase; color:#7d8ba0; }
.hud-map-panel{ position:relative; padding:0; overflow:hidden; min-height:clamp(120px, 26vh, 220px); background:#0b1220; }
.hud-map-panel #hudMap{ width:100%; height:100%; min-height:clamp(120px, 26vh, 220px); }
.hud-map-panel #hudMapLeaflet{ width:100%; height:100%; min-height:clamp(120px, 26vh, 220px); }
.hud-themed-slot .hud-map-panel #hudMapLeaflet{ min-height:0; }
/* Track HUD's own live-position marker (js/dashboard.js's
   initTrackHudLeafletMap/updateHudLeafletMarker) — an actual image icon
   (a theme's map-style.json markerIcon, e.g. Rally Raid's hand-drawn
   motorcycle glyph) rather than the emoji glyph the "Where to Ride"
   full-screen map's own marker utility (.motorcycle-marker-pin in
   js/maps-nav.js) uses. Kept as a distinct class so the two never share
   styling by accident. */
.hud-leaflet-marker-pin{ position:relative; width:36px; height:36px; }
.hud-leaflet-marker-pin img{ width:100%; height:100%; display:block; filter:drop-shadow(0 2px 5px rgba(0,0,0,0.6)); }
.hud-leaflet-marker-pin .hud-marker-pulse{
  position:absolute; inset:-8px; border-radius:50%;
  background:radial-gradient(circle, rgba(56,195,232,0.35) 0%, rgba(56,195,232,0) 70%);
  animation:hudMarkerPulse 1.8s ease-out infinite;
}
@keyframes hudMarkerPulse{
  0%{ transform:scale(0.7); opacity:0.9; }
  100%{ transform:scale(1.9); opacity:0; }
}
.hud-map-caption{
  position:absolute; left:12px; bottom:10px;
  font-family:var(--font-display); font-size:12px; font-weight:600; color:#fff;
  text-shadow:0 1px 4px rgba(0,0,0,0.6);
}
.hud-stats-panel{ display:flex; flex-direction:column; gap:12px; justify-content:center; }
.hud-stat-box{ text-align:center; }
.hud-stat-box .v{ font-family:var(--font-mono); font-size:22px; font-weight:700; color:#fff; }
.hud-stat-box .k{ font-family:var(--font-display); font-size:10px; letter-spacing:0.1em; text-transform:uppercase; color:#7d8ba0; margin-top:2px; }

/* All-time odometer badge — lifetime miles across every saved ride,
   styled as a classic rolling-digit odometer window. */
.hud-odo-row{ display:flex; align-items:center; justify-content:center; gap:8px; margin-top:2px; }
.hud-odo-label{ font-family:var(--font-display); font-size:10px; font-weight:700; letter-spacing:0.1em; text-transform:uppercase; color:#7d8ba0; }
.hud-odo-badge{
  display:flex; align-items:center; gap:6px;
  background:linear-gradient(180deg, #2a2e36, #14161b);
  border:1px solid rgba(255,255,255,0.1); border-radius:8px;
  padding:4px 6px 4px 8px;
  box-shadow:0 4px 12px rgba(0,0,0,0.4), inset 0 1px 0 rgba(255,255,255,0.06);
}
.hud-odo-digits{
  display:flex; background:#16140f;
  border-radius:4px; padding:2px 3px;
  box-shadow:inset 0 2px 4px rgba(0,0,0,0.7), inset 0 -1px 0 rgba(255,255,255,0.04);
}
.hud-odo-digit{
  width:13px; text-align:center;
  font-family:var(--font-mono); font-weight:700; font-size:15px;
  color:#e9e2c8; background:linear-gradient(180deg, #2b2a22, #1c1b15);
  border-right:1px solid rgba(0,0,0,0.6);
  text-shadow:0 1px 0 rgba(0,0,0,0.6);
}
.hud-odo-digit:last-child{ border-right:none; color:#d99b3a; } /* tenths wheel, classic amber */
.hud-odo-unit{ font-family:var(--font-display); font-size:10px; font-weight:700; letter-spacing:0.06em; color:#8b93a3; text-transform:uppercase; }

/* Portrait/narrow: topbar and the bottom panel row stack for readability.
   This used to trigger on max-width:900px alone — deliberately, per an
   earlier note here, so a landscape phone (routinely under 900px wide)
   still got it. That's exactly what broke "the whole HUD fits the
   landscape viewport, no scrolling": stacking the topbar into a tall
   single column (back button above title above the analog/digital
   toggle above weather text) and the bottom row into 3 stacked panels
   burns most of a short landscape viewport's height on its own, before
   the gauges even get a turn at the remaining space. Narrowed to
   portrait-only (matching .hud-gauges-row's own already-correct
   orientation:portrait scoping below) so landscape — narrow or wide —
   never stacks either of these, same as the gauges row already doesn't. */
@media (max-width: 900px) and (orientation: portrait){
  .hud-topbar{ grid-template-columns:1fr; text-align:center; gap:10px; }
  .hud-back-btn{ position:static; margin:0 auto 6px; }
  .hud-weather{ align-items:center; text-align:center; }
  .hud-bottom-row{ grid-template-columns:1fr; }
}

/* Portrait only: the gauges row stacks vertically instead of the
   horizontal/scrollable strip it uses everywhere else — landscape
   (this HUD's primary use case) must never trigger this, only a
   genuinely portrait screen should. */
@media (orientation: portrait){
  .hud-gauges-row{
    flex-direction:column;
    overflow-x:visible;
    overflow-y:visible;
  }
  .hud-mini-stats{ flex-direction:row; justify-content:center; }
}

/* ---------- Track HUD: image-template themes (Settings > Track HUD) ----------
   Hidden unless a custom theme is active (js/dashboard.js's
   applyTrackHudTheme() toggles .hud-gauges-row/.hud-bottom-row vs. this
   canvas and sets every inline style below from the theme's theme.json —
   position/size values here are placeholders overwritten per-theme). */
/* A theme is a pixel-accurate recreation of its own reference, not RydR's
   chrome layered over one — applyTrackHudTheme() hides .hud-topbar
   entirely in this state (see its comment) and this drops .hud-shell's
   own padding so the recreation can run edge-to-edge, exactly as the
   template, instead of inset within the same margins the default
   (non-image) skin uses. */
.hud-shell.theme-active{ padding:0; }
.hud-shell.theme-active .hud-themed-frame{ border-radius:0; }
.hud-themed-canvas{
  flex:1 1 auto;
  min-height:0;
  display:flex;
  align-items:center;
  justify-content:center;
}
.hud-themed-frame{
  position:relative;
  width:100%;
  max-width:100%;
  max-height:100%;
  /* aspect-ratio set inline per-theme (theme.json's aspectRatio) */
  background-size:100% 100%;
  background-repeat:no-repeat;
  border-radius:12px;
  overflow:hidden;
}
.hud-themed-slot{
  position:absolute;
  /* left/top/width/height set inline per-slot from theme.json's position fractions */
  display:flex;
  align-items:center;
  justify-content:center;
}
/* A themed gauge slot swaps the default metallic-bezel/carbon-face CSS
   chrome (::before/::after on .hud-gauge-block, see the gauge realism
   pass above) for the theme's own bezel image, set inline via
   background-image — the live SVG ticks/needle from RydRGauges keep
   rendering on top exactly as they do unthemed, just over different
   chrome. Digital mode is forced off here (see .hud-digital-card below)
   since a photorealistic/illustrated analog template has no equivalent
   "flat EV card" look to swap to. */
/* The moved .hud-gauge-slot itself has no defined width/height of its own
   (flex:0 0 auto, sized to content in the default layout) — inside a
   themed slot, that leaves .hud-gauge-block--themed's own width/height:100%
   below with nothing determinate to resolve against, so percentage sizing
   silently falls back to the gauge SVG's native 280x280/236x236 attributes
   instead of shrinking to the theme's (often much smaller) slot box. Giving
   the slot itself an explicit 100% here is what makes the rest of this
   chain actually take effect. */
.hud-themed-slot .hud-gauge-slot{ width:100%; height:100%; max-width:100%; }
.hud-gauge-block.hud-gauge-block--themed{ max-width:100%; width:100%; height:100%; margin:0; }
.hud-gauge-block.hud-gauge-block--themed::before,
.hud-gauge-block.hud-gauge-block--themed::after{ display:none; }
.hud-gauge-block.hud-gauge-block--themed{ background-size:100% 100%; background-repeat:no-repeat; background-position:center; }
.hud-gauge-block.hud-gauge-block--themed svg{ max-width:100%; width:100%; height:100%; }
/* .hud-gauge-block-lean's own clamp()-based floor (its default, non-themed
   sizing) is more specific than the shared --themed rule above when both
   classes are combined with it on the same element — pin it back to a
   plain 100% so a theme with a deliberately small lean-gauge slot (smaller
   than that floor) isn't forced back up to it. */
.hud-gauge-block-lean.hud-gauge-block--themed,
.hud-gauge-block-lean.hud-gauge-block--themed svg{ max-width:100%; }
.hud-themed-slot .hud-digital-card{ display:none !important; }
.hud-themed-slot.hud-themed-text-slot{
  font-family:var(--font-mono); font-weight:700;
  color:#eef3fa;
  text-shadow:0 0 8px rgba(0,0,0,0.6);
  font-size:clamp(11px, 1.6vh, 15px);
}
.hud-themed-slot .hud-map-panel{ position:absolute; inset:0; width:100%; height:100%; min-height:0; }
.hud-themed-slot .hud-map-panel #hudMap{ min-height:0; }
/* Weather-alert telltale (see track-hud-themes/neon-cluster/theme.json's
   weather-alert-icon slot): no image asset of its own — the baked
   warning-triangle art in the theme's background/decorative layer stays
   at its normal dim look, this slot only adds a bright pulsing glow on
   top while a real alert is active (js/dashboard.js's updateTrackHud()
   toggles .is-active based on activeAlerts.length). Off/hidden by
   default so an inactive telltale never reads as "something's on". */
.hud-themed-alert-slot{
  border-radius:50%;
  opacity:0;
  transition:opacity 0.3s ease;
  background:radial-gradient(circle, rgba(239,68,68,0.85) 0%, rgba(239,68,68,0) 70%);
  pointer-events:none;
}
.hud-themed-alert-slot.is-active{
  opacity:1;
  animation:hudThemedAlertPulse 1.2s ease-in-out infinite;
}
@keyframes hudThemedAlertPulse{
  0%, 100%{ opacity:0.75; transform:scale(0.92); }
  50%{ opacity:1; transform:scale(1.08); }
}
/* Weather-condition icon (weather-condition-icon slot): the live emoji
   RydR already uses on the main dashboard weather card, sized to sit in
   the hole punched at the reference's sun-icon position. */
.hud-themed-icon-slot{ font-size:clamp(14px, 2.4vh, 22px); line-height:1; }
/* Heading badge (heading-badge slot, substituted for the reference's ECO
   badge) — a short compass label in the same small-frame footprint. */
.hud-themed-heading-slot{ font-size:clamp(9px, 1.3vh, 12px); letter-spacing:0.05em; }
/* Now-playing album art (spotify-thumbnail slot, substituted for the
   reference's fuel-pump icon) — hidden by default (no art element at
   all) rather than showing a broken-image icon when nothing's playing;
   js/dashboard.js sets the src and toggles visibility together. */
.hud-themed-art-slot{ overflow:hidden; border-radius:3px; }
.hud-themed-art-slot img{ width:100%; height:100%; object-fit:cover; display:none; }
.hud-themed-art-slot img.is-visible{ display:block; }
/* Now-playing progress bar (spotify-progress-bar slot, substituted for
   the reference's E-F fuel gauge) — the baked E/F hash-mark frame from
   the theme's own asset stays as chrome (see the slot's own `asset` in
   theme.json); this is purely the live fill layered on top, width set
   inline as a 0-100% track-progress fraction each tick. */
.hud-themed-progress-slot{ overflow:hidden; }
.hud-themed-progress-fill{
  position:absolute; left:0; top:0; bottom:0; width:0%;
  background:linear-gradient(90deg, rgba(79,140,255,0.15), rgba(95,138,235,0.55));
  box-shadow:0 0 10px rgba(95,138,235,0.6);
  transition:width 0.4s linear;
  pointer-events:none;
}
/* Opt-in vertical orientation (theme.json's spotifyProgress slot sets
   "fillDirection": "vertical") — a bottom-to-top fill for themes whose
   reference substitutes a real E-F/vertical fuel-style gauge instead of
   Neon Cluster's horizontal one. js/dashboard.js toggles this class per
   theme and writes height (not width) while it's active. */
.hud-themed-progress-vertical .hud-themed-progress-fill{
  left:0; right:0; top:auto; bottom:0; width:auto; height:0%;
  background:linear-gradient(0deg, rgba(95,138,235,0.65), rgba(79,140,255,0.15));
  transition:height 0.4s linear;
}
/* Spotify play/pause control (spotify-control slot) — a real tappable
   button (js/dashboard.js wires its click to RydRSpotify.togglePlay()),
   not just a display widget like the other themed slots. No background
   asset by design (see Rally Raid's theme.json note) so it always lines
   up with the live button regardless of any background regeneration. */
.hud-themed-spotify-control-slot{ pointer-events:auto; }
.hud-themed-spotify-control-slot button{
  width:100%; height:100%;
  display:flex; align-items:center; justify-content:center;
  background:rgba(255,255,255,0.04);
  border:2px solid var(--cyan, #22d3ee);
  border-radius:14px;
  color:#eef3fa;
  font-size:clamp(22px, 5vh, 40px);
  line-height:1;
  cursor:pointer;
  transition:background 0.15s ease, opacity 0.2s ease;
}
.hud-themed-spotify-control-slot button:active{ background:rgba(34,211,238,0.18); }
.hud-themed-spotify-control-slot button.is-inactive{ opacity:0.4; cursor:default; }

/* Themed gauge digital readout (the big MPH/lean number, .hud-gauge-value)
   scales with its own gauge box instead of the viewport — lets a theme
   whose speedometer slot is much larger than RydR's own default (e.g.
   Rally Raid) show a proportionally bigger number, without changing the
   default (non-themed) HUD's own fixed clamp() sizing above. Containment
   is scoped to themed blocks only for the same reason. */
.hud-gauge-block.hud-gauge-block--themed{ container-type:inline-size; }
.hud-gauge-block--themed .hud-gauge-overlay-bottom .hud-gauge-value{ font-size:clamp(18px, 20cqw, 160px); }
.hud-gauge-block--themed .hud-gauge-overlay-bottom .hud-gauge-unit{ font-size:clamp(9px, 4.5cqw, 22px); margin-top:clamp(1px, 1cqw, 6px); }

/* ---------- Settings > Track HUD: theme gallery card ---------- */
.track-hud-theme-gallery{
  display:grid;
  grid-template-columns:repeat(auto-fill, minmax(140px, 1fr));
  gap:10px;
}
.track-hud-theme-card{
  position:relative;
  border:2px solid var(--border-subtle);
  border-radius:12px;
  overflow:hidden;
  cursor:pointer;
  background:var(--bg-void);
  text-align:left;
  padding:0;
}
.track-hud-theme-card.selected{ border-color:var(--neon-green); }
.track-hud-theme-card .thumb{
  width:100%; aspect-ratio:2/1;
  background-size:cover; background-position:center;
  background-color:#05070a;
  display:flex; align-items:center; justify-content:center;
  color:var(--text-muted); font-size:11px;
}
.track-hud-theme-card .label{
  padding:6px 8px;
  font-family:var(--font-display); font-weight:600; font-size:12px;
  color:var(--text-primary);
  display:flex; align-items:center; justify-content:space-between; gap:6px;
}
.track-hud-theme-card .label .check{ color:var(--neon-green); display:none; }
.track-hud-theme-card.selected .label .check{ display:inline; }

/* ---------- bottom tab bar v2 (alternative nav, Settings > Appearance) ----------
   5 large primary destinations + a "More" tab that opens the same
   categorized drawer as the hamburger menu, instead of a second,
   separately-maintained nav structure — the iOS/Android "tab bar + More"
   pattern. Sized for a gloved thumb glancing down at a handlebar mount:
   64px-tall targets (up from ~52px), bigger icons and labels. */
.bottom-tab-bar{
  display:none;
  position:fixed; left:0; right:0; bottom:0; z-index:520;
  align-items:stretch;
  background:var(--menu-panel-bg);
  border-top:1px solid var(--border-subtle);
  padding:4px 4px calc(4px + env(safe-area-inset-bottom, 0px));
  box-shadow:0 -6px 20px rgba(0,0,0,0.12);
  backdrop-filter:blur(16px);
  -webkit-backdrop-filter:blur(16px);
}
body.nav-tabbar .bottom-tab-bar{ display:flex; }
body.nav-tabbar.view-fullscreen .bottom-tab-bar{ display:none; }
body.nav-tabbar .menu-btn{ display:none; }
body.nav-tabbar .main{ padding-bottom:128px; }
.tab-bar-item{
  flex:1 1 0; min-width:0;
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:4px;
  min-height:64px;
  padding:6px 2px;
  border-radius:14px;
  background:none; border:none;
  color:var(--text-dim);
  text-decoration:none;
  font-family:var(--font-display); font-weight:700; font-size:11px; letter-spacing:0.02em;
  cursor:pointer;
  transition:color 0.15s ease, background 0.15s ease, transform 0.1s ease;
}
.tab-bar-item svg{ width:26px; height:26px; }
.tab-bar-item:active{ transform:scale(0.94); background:var(--bg-panel-hover); }
.tab-bar-item.active{ color:var(--neon-green); }

/* ---------- floating menu trigger + slide-in drawer ---------- */
.menu-btn{
  position:fixed; top:12px; left:12px; z-index:600;
  width:48px; height:48px;
  display:grid; place-items:center;
  background:var(--bg-panel);
  border:1px solid var(--border-subtle);
  border-radius:var(--radius-sm);
  color:var(--text-muted);
  font-size:20px;
  cursor:pointer;
  box-shadow:0 2px 10px rgba(0,0,0,0.3);
  transition:color 0.15s ease, border-color 0.15s ease, background 0.15s ease, transform 0.1s ease;
}
.menu-btn:hover{ color:var(--neon-green); border-color:var(--neon-green-dim); background:var(--bg-panel-hover); }
.menu-btn:active{ transform:scale(0.93); }

/* Scrim behind the drawer — dims the app so the (now much larger) touch
   targets are the only thing competing for attention, and gives the
   drawer a tap-anywhere-to-close target. @starting-style lets this fade
   in/out smoothly without a display:none flash-of-nothing on the very
   first frame; browsers without support for it just get an instant show/
   hide instead of the fade, so this only ever adds polish, never breaks. */
.nav-scrim{
  position:fixed; inset:0; z-index:585;
  background:rgba(0,0,0,0.45);
  opacity:0;
  display:none;
  transition:opacity 0.28s ease;
}
.nav-scrim.open{ display:block; opacity:1; }
@starting-style{
  .nav-scrim.open{ opacity:0; }
}

/* ---------- App top stack ----------
   The menu button anchors the top-left corner on its own (position:fixed,
   unchanged below — it's shared markup across every page, not just the
   dashboard). This wrapper is dashboard-only: it pins the icon nav bar
   (when any card is minimized) and the header status bar (time/location/
   recording) to that same top edge, stacked in normal flow so the status
   bar always sits directly under the toolbar's real rendered height —
   nothing here is a hardcoded offset that the two have to be kept in sync
   by hand (see the ResizeObserver in js/icon-toolbar.js that keeps
   `.main`'s top padding in sync with this stack's actual height too). */
.app-top-stack{
  position:fixed; top:12px; left:64px; right:20px; z-index:200;
  display:flex; flex-direction:column; gap:8px;
}
@media (min-width:1440px){
  .app-top-stack{ right:calc(50vw - 680px); }
}

/* ---------- Header Status Bar ---------- */
.header-status-bar{
  position:relative;
  height:44px;
  display:flex; align-items:center; justify-content:center;
  padding:2px 14px;
  background:transparent;
  border:none;
  box-shadow:none;
  font-family:var(--font-display);
  font-size:13px;
  color:var(--text-primary);
  overflow:hidden;
  user-select:none;
  opacity:1;
  transition:opacity 0.25s ease;
}
.header-status-bar.is-faded{
  opacity:0;
  pointer-events:none;
}

.header-status-center{
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  text-align:center; min-width:0; max-width:100%;
}

.header-status-time{
  font-family:'Barlow Condensed', var(--font-display);
  font-weight:300;
  font-size:22px;
  color:var(--neon-green);
  letter-spacing:0.05em;
  line-height:1;
  margin-bottom:0px;
}

.header-status-location{
  font-size:10px;
  font-weight:600;
  color:var(--text-muted);
  line-height:1.1;
  overflow:hidden;
  text-overflow:ellipsis;
  white-space:nowrap;
  max-width:320px;
}

.header-status-right{
  position:absolute; right:14px; top:50%; transform:translateY(-50%);
  display:flex; align-items:center;
}

.header-status-rec{
  display:none; align-items:center; gap:6px;
  padding:4px 10px;
  border-radius:999px;
  background:rgba(239,68,68,0.12);
  border:1px solid rgba(239,68,68,0.32);
}
.header-status-rec.recording,
.header-status-rec.paused{ display:flex; }
.header-status-rec .rec-dot{
  width:8px; height:8px; border-radius:50%;
  background:var(--alert-red);
  box-shadow:0 0 8px var(--alert-red-glow);
  animation:recPulse 1.2s infinite ease-in-out;
  flex-shrink:0;
}
.header-status-rec-label{
  font-family:var(--font-display);
  font-weight:700; font-size:11px; letter-spacing:0.06em;
  text-transform:uppercase;
  color:var(--alert-red);
}
.header-status-rec.paused{
  background:rgba(245,158,11,0.12);
  border-color:rgba(245,158,11,0.32);
}
.header-status-rec.paused .rec-dot{
  background:var(--warn-amber);
  box-shadow:0 0 8px rgba(245,158,11,0.35);
  animation:none;
}
.header-status-rec.paused .header-status-rec-label{ color:var(--warn-amber); }

@keyframes recPulse{
  0%{ transform:scale(0.85); opacity:0.7; }
  50%{ transform:scale(1.25); opacity:1.0; }
  100%{ transform:scale(0.85); opacity:0.7; }
}

@media (max-width:480px){
  .app-top-stack{ left:60px; right:12px; }
  .header-status-bar{ padding:2px 8px; }
  .header-status-location{ max-width:150px; font-size:9.5px; }
  .header-status-time{ font-size:18px; }
}
/* ---------- primary navigation drawer v2 ----------
   Redesigned around one job: a rider glancing at a phone mounted on
   handlebars can find and hit the right row without close attention or
   fine motor precision. Rebuilt from scratch rather than restyled:
   - Rows are 64px tall (was 50px) with 18px type (was 16px) — comfortably
     inside typical glove-tap guidance, well past the 44-48px baseline.
   - Items are grouped into color-coded categories (Ride/Navigate/Log/
     Labs/Settings) instead of one flat list, so the eye can jump straight
     to a section instead of reading every row. Category colors reuse the
     app's own already theme-tuned tokens (--neon-green/--cyan/--warn-
     amber/--text-dim) so every existing theme "just works"; only Labs
     needed a new token (--labs-violet, defined near :root) since nothing
     else in the app used violet.
   - Panel width is min(88vw, 380px) with its own scroll — Material's
     "leave a sliver of context visible" sizing — instead of a fixed
     288px that would've had to either crowd five category headers plus
     ~11 rows or get its own inner overflow bug on a short phone.
   - @view-transition (see near :root) gives this a soft cross-fade
     between pages on browsers that support the Cross-Document View
     Transitions API (Chrome/Edge on Android as of this writing) — a
     pure-CSS progressive enhancement, inert and harmless elsewhere. */
.menu-panel{
  position:fixed; top:0; left:0; z-index:590;
  width:min(88vw, 380px); height:100vh; height:100dvh;
  display:flex;
  flex-direction:column;
  background:var(--menu-panel-bg);
  border-right:1px solid var(--border-subtle);
  box-shadow:16px 0 40px rgba(0,0,0,0.45);
  backdrop-filter:blur(16px);
  -webkit-backdrop-filter:blur(16px);
  transform:translateX(-100%);
  transition:transform 0.3s cubic-bezier(0.16,1,0.3,1);
  overflow-y:auto;
  overscroll-behavior-y:contain;
  padding-bottom:calc(16px + env(safe-area-inset-bottom, 0px));
}
.menu-panel.open{ transform:translateX(0); }
.menu-brand{
  position:sticky; top:0; z-index:1;
  display:flex; align-items:center; gap:12px;
  padding:18px 18px 16px;
  font-family:var(--font-display);
  font-weight:700; font-size:18px;
  background:var(--menu-panel-bg);
  border-bottom:1px solid var(--border-subtle);
}
.menu-brand .mark{
  width:36px;height:36px;
  display:grid;place-items:center;
  color:#fff;
  background:var(--neon-green);
  border-radius:var(--radius-sm);
  font-family:var(--font-display);
  font-weight:700; font-size:17px;
}
.menu-brand small{
  display:block;
  font-family:var(--font-mono);
  font-size:10px;
  letter-spacing:0.14em;
  color:var(--text-dim);
  font-weight:500;
  margin-top:2px;
}

.nav-home{
  display:block; margin:14px 12px 6px;
}
.nav-category{ margin:18px 12px 6px; }
.nav-category-header{
  display:flex; align-items:center; gap:8px;
  padding:0 10px 8px;
  font-family:var(--font-display); font-weight:800; font-size:12px;
  letter-spacing:0.14em; text-transform:uppercase;
  color:var(--text-dim);
}
.nav-category-dot{
  width:9px; height:9px; border-radius:50%; flex:0 0 auto;
  background:var(--cat-color, var(--neon-green));
  box-shadow:0 0 8px color-mix(in srgb, var(--cat-color, var(--neon-green)) 60%, transparent);
}
.nav-category[data-cat="ride"]{ --cat-color:var(--neon-green); }
.nav-category[data-cat="navigate"]{ --cat-color:var(--cyan); }
.nav-category[data-cat="log"]{ --cat-color:var(--warn-amber); }
.nav-category[data-cat="labs"]{ --cat-color:var(--labs-violet); }
.nav-category[data-cat="settings"]{ --cat-color:var(--text-dim); }

.nav-divider{ height:1px; background:var(--border-subtle); margin:16px 18px; }

.menu-link{
  display:flex; align-items:center; gap:14px;
  width:100%; min-height:64px;
  padding:0 12px;
  border:none; background:transparent;
  border-radius:14px;
  color:var(--text-primary);
  font-family:var(--font-display); font-weight:600; font-size:18px;
  text-align:left; cursor:pointer;
  transition:background 0.15s ease, color 0.15s ease, transform 0.1s ease;
}
.menu-link + .menu-link{ margin-top:2px; }
.menu-link:hover{ background:var(--bg-panel-hover); color:var(--text-primary); }
.menu-link:active{ transform:scale(0.98); background:var(--bg-panel-hover); }
.menu-link.active{
  color:var(--cat-color, var(--neon-green));
  background:color-mix(in srgb, var(--cat-color, var(--neon-green)) 12%, transparent);
  font-weight:700;
}
.nav-home > .menu-link{ font-size:19px; }
.menu-link-icon{
  width:40px; height:40px; flex:0 0 auto;
  display:grid; place-items:center;
  border-radius:11px;
  font-size:20px;
  background:color-mix(in srgb, var(--cat-color, var(--text-dim)) 14%, transparent);
}
.nav-home .menu-link-icon{
  background:color-mix(in srgb, var(--neon-green) 16%, transparent);
}
.icon-btn{
  min-width:var(--touch); min-height:var(--touch);
  display:grid; place-items:center;
  background:var(--bg-panel);
  border:1px solid var(--border-subtle);
  border-radius:10px;
  color:var(--text-muted);
  cursor:pointer;
}
.icon-btn:hover{color:var(--neon-green); border-color:var(--border-strong);}

.main{
  flex:1;
  width:100%;
  max-width:1400px;
  margin:0 auto;
  padding:76px 20px 90px;
}

/* ---------- panels ---------- */
.panel,
.map-box {
  background: transparent;
  padding: 16px;
  transition: background 0.25s ease, border-color 0.25s ease;
}
.panel-title{
  font-family:var(--card-custom-font, var(--font-display));
  font-weight:700;
  font-size:var(--card-custom-title-size, 13px);
  letter-spacing:0.06em;
  text-transform:uppercase;
  color:var(--card-custom-muted-color, var(--text-dim));
  margin:0 0 14px;
  display:flex; align-items:center; justify-content:space-between;
  /* A title paired with a trailing status/button can run out of room on
     narrow cards. Without wrap, flexbox's default shrink behavior squeezes
     both items to fit on one line, which breaks text internally mid-word
     instead of just dropping the second item to its own line. */
  flex-wrap:wrap; row-gap:4px;
}
/* Keeps the title itself intact (no mid-word break) when the row wraps —
   only the trailing status text/button should ever drop to its own line. */
.panel-title-text{ white-space:nowrap; flex-shrink:0; }

/* Card typography & text color overrides */
.rr-card {
  font-family: var(--card-custom-font, var(--font-body));
}
.rr-card .speed-value,
.rr-card .weather-temp,
.rr-card .v,
.rr-card .value,
.rr-card .plugin-value {
  color: var(--card-custom-text-color, var(--text-primary));
}
.rr-card .k,
.rr-card .weather-cond,
.rr-card .plugin-label {
  color: var(--card-custom-muted-color, var(--text-muted));
}

/* ---------- dashboard card layout ---------- */
.rr-card{
  display:none;
  position:relative;
  background:var(--card-custom-bg, var(--bg-panel));
  border:1px solid var(--border-subtle);
  border-radius:var(--radius-lg);
  box-shadow:0 4px var(--shadow-blur, 12px) rgba(0, 0, 0, var(--shadow-opacity, 0.18));
  overflow:hidden;
  transition:background 0.25s ease, border-color 0.25s ease, box-shadow 0.25s ease;
  /* Lets descendants (e.g. .weather-panel below) adapt their own layout to
     how narrow this specific card actually is — the Layout editor can put
     any card into a 1/3-width row, a half row, or full width, so a fixed
     breakpoint on the viewport can't tell a component how much room it
     really has. Only the inline (width) axis is queried. */
  container-type:inline-size;
}

/* Top-Down Glowing Edge Overlay (subtle by default; user-adjustable in Settings > Appearance) */
.rr-card::before,
.clock-preview-box#cardPreviewBox::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1.5px;
  background: linear-gradient(
    180deg,
    var(--neon-green, #4f8cff) 0%,
    var(--neon-green-glow, rgba(79,140,255,0.28)) calc(var(--glow-coverage, 8%) * 0.75),
    transparent var(--glow-coverage, 8%)
  );
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
  z-index: 2;
}

/* Top Sheen Metallic Reflection Overlay (subtle by default; user-adjustable) */
.rr-card::after,
.clock-preview-box#cardPreviewBox::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: linear-gradient(
    180deg,
    rgba(255, 255, 255, var(--sheen-opacity, 0.05)) 0%,
    rgba(255, 255, 255, calc(var(--sheen-opacity, 0.05) * 0.2)) calc(var(--glow-coverage, 8%) * 0.8),
    transparent var(--glow-coverage, 8%)
  );
  pointer-events: none;
  z-index: 1;
}

.layout-row .rr-card{ display:block; min-width:0; }
.layout-root{
  display:flex; flex-direction:column; gap:16px;
  margin-bottom:16px;
}
/* Inner panels inherit transparent background from .rr-card */
.rr-card > .panel,
.rr-card > .speed-panel {
  background: transparent !important;
  border: none !important;
  box-shadow: none !important;
}

/* Disabled Background for Individual Component Card */
.rr-card.no-card-bg {
  background: transparent !important;
  border-color: transparent !important;
  box-shadow: none !important;
}
.rr-card.no-card-bg::before,
.rr-card.no-card-bg::after {
  display: none !important;
}

/* Spotify Card Exemption: Retains its original dark brand background */
.rr-card[data-card-id="spotify"] {
  background: transparent !important;
  border: none !important;
  box-shadow: none !important;
  overflow: visible;
}
.rr-card[data-card-id="spotify"]::before,
.rr-card[data-card-id="spotify"]::after {
  display: none !important;
}
/* The minimize button for this card is slotted directly into .spotify-head
   (see index.html's [data-minimize-slot] span and the "Minimize Button
   Slot" section in skills/rydr-plugin-development/SKILL.md) rather than
   using the generic floating .card-minimize-cap, so it sits inline with
   Spotify's own header controls instead of adding an extra bar above the
   card. Its look is defined by .card-minimize-btn-slotted below. */

/* Spotify Playlists plugin card: same treatment as the built-in Spotify
   controller above — it paints its own full-bleed dark/brand-green surface
   (see plugins/spotify-playlists/spotify-playlists.js's PLUGIN_STYLE), so
   the generic .rr-card background/border/glow (which follows the active
   app theme, and Settings > Appearance > Card Background & Effects' custom
   per-card color) must stay out of the way rather than showing through at
   the edges or behind a disconnected/loading state. */
.rr-card[data-plugin-id="spotify-playlists"] {
  background: transparent !important;
  border: none !important;
  box-shadow: none !important;
  overflow: visible;
}
.rr-card[data-plugin-id="spotify-playlists"]::before,
.rr-card[data-plugin-id="spotify-playlists"]::after {
  display: none !important;
}
/* Its minimize button is slotted into .sp-header-controls (see the
   [data-minimize-slot] span in plugins/spotify-playlists/spotify-playlists.js)
   rather than using the generic floating .card-minimize-cap. */

/* Route Exchange plugin card: paints its own fully custom card surface
   (see plugins/route-exchange/route-exchange.js's ensureNearbyStyles/.rexc
   styles), so — like the two Spotify cards above — the generic .rr-card
   background/border/glow must stay out of the way instead of showing a
   plain dashboard-card frame around it. */
.rr-card[data-plugin-id="route-exchange"] {
  background: transparent !important;
  border: none !important;
  box-shadow: none !important;
  overflow: visible;
}
.rr-card[data-plugin-id="route-exchange"]::before,
.rr-card[data-plugin-id="route-exchange"]::after {
  display: none !important;
}

/* Hourly Precipitation ("Chance of Rain") plugin card: same treatment —
   it paints its own full-bleed animated background (sun/cloud/rain, see
   plugins/hourly-precip/hourly-precip.js's RydRBgAnimator layers) behind
   the bar chart, so the generic .rr-card frame must stay out of the way
   rather than showing a plain dashboard-card border/glow around it. */
.rr-card[data-plugin-id="hourly-precip"] {
  background: transparent !important;
  border: none !important;
  box-shadow: none !important;
  overflow: visible;
}
.rr-card[data-plugin-id="hourly-precip"]::before,
.rr-card[data-plugin-id="hourly-precip"]::after {
  display: none !important;
}

/* Ride Pulse plugin card: same treatment — it's a deliberately bespoke
   instrument-cluster design with its own fixed dark gauge-face palette
   (see the comment above .rp-cluster below), so the generic themed
   .rr-card background/border/glow — which would fight that fixed palette
   rather than match it — must stay out of the way. */
.rr-card[data-plugin-id="ridepulse"] {
  background: transparent !important;
  border: none !important;
  box-shadow: none !important;
  overflow: visible;
}
.rr-card[data-plugin-id="ridepulse"]::before,
.rr-card[data-plugin-id="ridepulse"]::after {
  display: none !important;
}

.layout-row{
  display:flex; flex-wrap:nowrap; gap:12px; align-items:stretch;
  background:transparent !important;
  border:none !important;
  box-shadow:none !important;
  padding:0 !important;
}
.layout-row.multi-card-row{
  background:transparent !important;
  border:none !important;
  box-shadow:none !important;
  padding:0 !important;
}
.layout-row .rr-card{ flex:1 1 0; }
.layout-row .rr-card[data-card-id="map"],
.layout-row .rr-card[data-card-id="route"]{ flex:1.6 1 0; }
.layout-row .rr-card > .panel,
.layout-row .rr-card > .speed-panel{ height:100%; }

.speed-panel{
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  background:transparent;
  border:none;
  border-radius:var(--radius-lg);
  padding:20px;
  position:relative;
  overflow:hidden;
}
.speed-panel::before{
  content:"";
  position:absolute; inset:0;
  background:linear-gradient(180deg, transparent 60%, rgba(79,140,255,0.05));
  pointer-events:none;
}
.speed-panel svg{ width:100%; height:auto; max-width:240px; }
.speed-panel.style-digital svg{ display:none; }
/* In analog mode .speed-overlay sits absolutely centered over the visible
   SVG gauge. In digital mode there's no SVG to center over — if it stayed
   absolutely positioned, it would contribute no height to the flex column,
   so the panel would collapse to just its padding and overflow:hidden
   would clip the (often much taller) digital number. Making it a normal
   flow child here lets the panel size itself to the actual text. */
.speed-panel.style-digital .speed-overlay{
  position:static;
  transform:none;
}
.speed-overlay{
  position:absolute; top:50%; left:50%;
  transform:translate(-50%, -50%);
  text-align:center;
  pointer-events:none;
}
.speed-value{
  font-family:var(--speedo-font, var(--font-mono));
  font-weight:700;
  font-size:var(--speedo-size, clamp(28px, 9vw, 64px));
  line-height:1;
  color:var(--speedo-fg, var(--neon-green));
  text-shadow:0 0 20px var(--speedo-fg-glow, var(--neon-green-glow));
}
.speed-unit{
  font-family:var(--font-display);
  font-size:13px;
  font-weight:500;
  letter-spacing:0.08em;
  color:var(--text-muted);
  margin-top:4px;
}

.mini-gauge-row{
  display:flex; gap:16px;
}
.mini-gauge{
  flex:1;
  display:flex; flex-direction:column; align-items:center;
  background:var(--bg-panel);
  border:1px solid var(--border-subtle);
  border-radius:var(--radius-md);
  padding:12px;
  min-width:0;
}
.mini-gauge svg{ width:100%; height:auto; max-width:140px; }
.mini-gauge .label{
  font-family:var(--font-display);
  font-size:11px; letter-spacing:0.06em;
  color:var(--text-dim); text-transform:uppercase;
  margin-top:6px;
}
.mini-gauge .value{
  font-family:var(--font-mono);
  font-size:20px; font-weight:700;
  color:var(--text-primary);
}

/* record button */
.record-btn{
  min-height:56px;
  width:100%;
  border-radius:var(--radius-md);
  border:1px solid var(--neon-green-dim);
  background:linear-gradient(180deg, rgba(79,140,255,0.16), rgba(79,140,255,0.04));
  color:var(--neon-green);
  font-family:var(--font-display);
  font-weight:600;
  font-size:16px;
  letter-spacing:0.02em;
  display:flex; align-items:center; justify-content:center; gap:10px;
  cursor:pointer;
  transition:border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
}
.record-btn .dot{
  width:12px;height:12px;border-radius:50%;
  background:var(--neon-green);
  box-shadow:0 0 10px var(--neon-green-glow);
}
.record-btn.recording{
  border-color:var(--alert-red);
  background:linear-gradient(180deg, rgba(255,59,92,0.18), rgba(255,59,92,0.05));
  color:var(--alert-red);
}
.record-btn.recording .dot{
  background:var(--alert-red);
  box-shadow:0 0 10px var(--alert-red-glow);
  animation:pulse 1.4s ease-in-out infinite;
}
.record-btn.paused{
  border-color:var(--warn-amber);
  background:linear-gradient(180deg, rgba(255,176,32,0.18), rgba(255,176,32,0.05));
  color:var(--warn-amber);
}
.record-btn.paused .dot{
  background:var(--warn-amber);
  box-shadow:0 0 10px rgba(255,176,32,0.35);
}
@keyframes pulse{
  0%,100%{opacity:1; transform:scale(1);}
  50%{opacity:0.4; transform:scale(0.8);}
}
.rec-meta{
  display:flex; justify-content:space-between;
  font-family:var(--font-mono);
  font-size:12px; color:var(--text-muted);
  margin-top:8px;
}
.auto-toggle{
  display:flex; align-items:center; justify-content:space-between;
  margin-top:10px;
  font-family:var(--font-body);
  font-size:13px; font-weight:500; letter-spacing:0; color:var(--text-muted);
}
.switch{
  width:42px;height:24px;border-radius:14px;
  background:var(--bg-void); border:1px solid var(--border-strong);
  position:relative; cursor:pointer; flex-shrink:0;
}
.switch::after{
  content:"";
  position:absolute; top:2px; left:2px;
  width:18px;height:18px;border-radius:50%;
  background:var(--text-dim);
  transition:transform .18s ease, background .18s ease;
}
.switch.on{border-color:var(--neon-green-dim);}
.switch.on::after{
  transform:translateX(18px);
  background:var(--neon-green);
  box-shadow:0 0 8px var(--neon-green-glow);
}

/* weather widget */
.weather-panel{
  display:flex; flex-direction:column;
  position:relative;
}
.weather-main-row{
  display:flex; align-items:center; gap:16px;
}
.weather-temp{
  font-family:var(--font-mono); font-size:38px; font-weight:700; color:var(--text-primary);
}
.weather-cond{
  font-family:var(--font-body); font-weight:500; font-size:14px; color:var(--text-muted); text-transform:capitalize;
}
.weather-stats{
  margin-left:auto;
  display:flex; gap:18px;
}
.weather-stat{text-align:right;}
.weather-stat .v{font-family:var(--font-mono); font-size:16px; color:var(--text-primary);}
.weather-stat .k{font-family:var(--font-display); font-size:10px; letter-spacing:0.06em; color:var(--text-dim); text-transform:uppercase;}
.weather-icon{font-size:40px; line-height:1;}

.weather-precip-toggle{
  appearance:none; border:1px solid var(--border-subtle); background:var(--bg-void);
  color:var(--text-muted); border-radius:10px; width:34px; height:34px; flex-shrink:0;
  display:flex; align-items:center; justify-content:center; font-size:16px; cursor:pointer;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.weather-precip-toggle[aria-expanded="true"]{
  background:rgba(79,140,255,0.16); border-color:rgba(79,140,255,0.5); color:var(--text-primary);
}
.weather-precip-drawer{
  max-height:0; overflow:hidden; opacity:0;
  transition: max-height 0.28s ease, opacity 0.2s ease, margin-top 0.28s ease;
}
.weather-precip-drawer.open{ max-height:200px; opacity:1; margin-top:14px; }
.weather-precip-drawer-title{
  font-family:var(--font-display); font-size:11px; letter-spacing:0.06em; text-transform:uppercase;
  color:var(--text-dim); margin-bottom:10px;
}
.weather-precip-chart{
  display:flex; align-items:flex-end; gap:10px; height:120px;
  overflow-x:auto; overflow-y:hidden; -webkit-overflow-scrolling:touch;
  scrollbar-width:none;
}
.weather-precip-chart::-webkit-scrollbar{ display:none; }

/* Weather is a full "icon + temp + condition + wind/clouds + precip toggle"
   panel, but the default dashboard layout groups it into a 1/3-width row
   alongside Heading and Lean Angle — at that width the row's content was
   nearly 3x wider than the card (measured: ~307px of content in a ~107px
   card), clipping the condition text and stat labels mid-word. This
   re-flows the same content into a compact stack instead of shrinking it
   past readability, and applies whenever the card itself is narrow —
   including if a user drags Weather into a different row via the Layout
   editor — rather than assuming one fixed viewport breakpoint. 300px (not
   220px) is deliberate: a Weather+Forecast carousel row measured out to a
   ~242px card on a 390px phone (the carousel narrows each slide to make
   room for the next one to peek in, then this card's own side padding
   eats more still), and the un-stacked row actually needs ~291px of
   content width at that point — comfortably inside the old 220px cutoff's
   "should still be fine" zone, but not actually fine, which is exactly
   why the precip-drawer toggle button was reported as missing: it wasn't
   gone, it was flowed off the right edge and clipped by the carousel
   viewport's overflow:hidden. */
@container (max-width:300px){
  /* A 2-column (icon | text) layout was tried first, but at this width the
     icon alone eats most of the row, squeezing the text column down to
     ~28px — narrow enough that break-word wrapped every single character
     onto its own line. Stacking everything full-width, one row per piece
     of info, gives each row the whole ~100px+ card width instead of a
     sliver of it, so normal word-wrap is all that's needed. */
  .weather-main-row{
    display:flex;
    flex-direction:column;
    align-items:flex-start;
    gap:4px;
    padding-right:32px;
  }
  .weather-icon{ font-size:26px; }
  .weather-temp{ font-size:22px; }
  .weather-cond{ font-size:12px; }
  .weather-stats{
    width:100%;
    margin-left:0;
    flex-wrap:wrap;
    justify-content:flex-start;
    gap:4px 14px;
  }
  .weather-stat{ text-align:left; }
  .weather-precip-toggle{ position:absolute; top:0; right:0; }
}
.wp-bar-col{
  display:flex; flex-direction:column; align-items:center; justify-content:flex-end;
  flex:0 0 32px; height:100%; gap:5px;
}
.wp-bar-pct{
  font-family:var(--font-mono); font-size:10px; font-weight:700; color:var(--text-primary);
  white-space:nowrap;
}
.wp-bar-track{
  width:100%; max-width:26px; flex:1; display:flex; align-items:flex-end;
  background:rgba(127,127,127,0.12); border-radius:5px; overflow:hidden;
}
.wp-bar-fill{
  width:100%; min-height:3px; border-radius:5px 5px 0 0;
  background:linear-gradient(180deg, #7ec4ff, #3b7ce0);
}
.wp-bar-time{
  font-family:var(--font-mono); font-size:9px; color:var(--text-dim); white-space:nowrap;
}

.precip-alert{
  display:flex; align-items:center; gap:10px;
  background:rgba(255,176,32,0.1);
  border:1px solid rgba(255,176,32,0.35);
  color:var(--warn-amber);
  padding:10px 14px;
  border-radius:var(--radius-md);
  font-family:var(--font-display);
  font-size:14px;
  letter-spacing:0.02em;
  margin-bottom:16px;
}

/* two column: map + route */
.content-grid{
  display:grid;
  grid-template-columns:1.6fr 1fr;
  gap:16px;
}
@media (max-width:980px){
  .content-grid{grid-template-columns:1fr;}
}
#map{
  width:100%; height:420px;
  border-radius:var(--radius-md);
  border:1px solid var(--border-subtle);
  background:var(--bg-panel-raised);
}
.map-wrap{ position:relative; }
.map-locate-btn{
  position:absolute; right:12px; bottom:12px; z-index:5;
  width:42px; height:42px; border-radius:50%;
  display:grid; place-items:center;
  background:var(--bg-panel); border:1px solid var(--border-subtle);
  color:var(--text-muted); font-size:16px; cursor:pointer;
  box-shadow:0 2px 10px rgba(0,0,0,0.35);
}
.map-locate-btn:hover{ color:var(--neon-green); border-color:var(--neon-green-dim); }
.map-locate-btn.is-loading{ opacity:0.55; pointer-events:none; animation:pulse 1s ease-in-out infinite; }
.current-location-label{
  font-family:var(--font-body); font-weight:400; text-transform:none; letter-spacing:0;
  font-size:12px; color:var(--text-muted); margin-left:6px;
}
.current-location-label:empty{ display:none; }
.map-toolbar{
  display:flex; gap:8px; margin-top:10px;
}
.radar-controls{
  margin-top:10px;
  background:var(--bg-panel-raised);
  border:1px solid var(--border-subtle);
  border-radius:var(--radius-md);
  padding:10px 12px;
}
.chip-btn{
  min-height:40px;
  padding:0 14px;
  border-radius:20px;
  border:1px solid var(--border-subtle);
  background:var(--bg-panel-raised);
  color:var(--text-muted);
  font-family:var(--font-display);
  font-weight:600; font-size:13px; letter-spacing:0.04em;
  cursor:pointer;
  display:flex; align-items:center; gap:6px;
}
.chip-btn.active{ color:var(--neon-green); border-color:var(--neon-green-dim); background:rgba(79,140,255,0.08);}

/* route planner */
.route-form{display:flex; gap:8px; margin-bottom:12px;}
.route-form input{
  flex:1;
  min-height:var(--touch);
  background:var(--bg-void);
  border:1px solid var(--border-subtle);
  border-radius:var(--radius-sm);
  padding:0 14px;
  color:var(--text-primary);
  font-family:var(--font-body);
  font-size:15px;
}
.route-form input::placeholder{color:var(--text-dim);}
.btn{
  min-height:var(--touch);
  padding:0 20px;
  border-radius:var(--radius-sm);
  border:1px solid var(--btn-primary-border, var(--neon-green-dim));
  background:var(--btn-primary-bg, var(--neon-green));
  color:var(--btn-primary-color, #ffffff);
  font-family:var(--font-display);
  font-weight:600; font-size:14px; letter-spacing:0.01em;
  cursor:pointer;
  transition:filter 0.15s ease, box-shadow 0.15s ease, background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
  box-shadow:0 1px 2px rgba(0,0,0,0.18);
}
.btn:hover{
  filter:brightness(1.08);
  box-shadow:0 3px 10px rgba(0,0,0,0.22);
}
.btn:active{ filter:brightness(0.97); }
.btn.small{
  min-height:36px;
  padding:0 14px;
  font-size:12.5px;
}
.btn.secondary{
  background:var(--btn-secondary-bg, var(--bg-panel-raised));
  color:var(--text-primary);
  border:1px solid var(--btn-secondary-border, var(--border-subtle));
  box-shadow:none;
}
.btn.secondary:hover,
.btn.secondary.active{
  background:var(--bg-panel-hover);
  border-color:var(--neon-green);
  color:var(--neon-green);
}
.btn.danger{
  background:transparent;
  color:var(--alert-red);
  border:1px solid var(--alert-red);
  box-shadow:none;
}
.btn.danger:hover{
  background:var(--alert-red);
  color:#ffffff;
}
.btn.danger:disabled{
  opacity:0.4;
  cursor:default;
  filter:none;
}
.route-summary{
  display:flex; gap:20px;
  font-family:var(--font-mono);
  margin-bottom:10px;
  padding-bottom:10px;
  border-bottom:1px solid var(--border-subtle);
}
.route-summary div span{display:block; font-family:var(--font-display); font-size:10px; color:var(--text-dim); letter-spacing:0.06em; text-transform:uppercase;}
.directions-list{
  list-style:none; margin:0; padding:0;
  max-height:260px; overflow-y:auto;
}
.directions-list li{
  display:flex; gap:10px;
  padding:10px 0;
  border-bottom:1px dashed var(--border-subtle);
  font-size:14px;
}
.directions-list li:last-child{border-bottom:none;}
.step-idx{
  font-family:var(--font-mono);
  color:var(--neon-green);
  min-width:22px;
}
.empty-state{
  color:var(--text-dim);
  font-family:var(--font-body);
  font-size:14px;
  text-align:center;
  padding:30px 10px;
}

/* ---------- history page ---------- */
.ride-log-header{
  display:flex; align-items:center; justify-content:space-between;
  gap:12px; margin-bottom:16px; flex-wrap:wrap;
}
.ride-log-view-toggle{
  display:flex; gap:4px;
  background:var(--bg-panel-raised);
  border:1px solid var(--border-subtle);
  border-radius:999px; padding:3px;
}
.ride-log-view-btn{
  border:none; background:transparent; color:var(--text-muted);
  font-family:var(--font-display); font-weight:600; font-size:12px; letter-spacing:0.03em;
  padding:7px 16px; border-radius:999px; cursor:pointer;
}
.ride-log-view-btn.active{ background:var(--bg-panel); color:var(--neon-green); }
.ride-log-header-actions{ display:flex; align-items:center; gap:10px; flex-wrap:wrap; }
#combineModeBtn.active{ background:rgba(79,140,255,0.18); border-color:rgba(79,140,255,0.55); color:var(--text-primary); }

.ride-log-sort-row{
  display:flex; align-items:center; gap:8px; flex-wrap:wrap;
  margin-bottom:16px;
}
.ride-log-sort-label{
  font-family:var(--font-display); font-size:11px; letter-spacing:0.06em; text-transform:uppercase;
  color:var(--text-dim);
}
.ride-log-sort-select{
  min-height:var(--touch);
  background:var(--bg-panel-raised); border:1px solid var(--border-subtle);
  border-radius:var(--radius-sm); color:var(--text-primary);
  font-family:var(--font-body); font-size:13px;
  padding:0 12px; cursor:pointer;
}
.ride-log-sort-dir-btn{
  min-height:var(--touch);
  background:var(--bg-panel-raised); border:1px solid var(--border-subtle);
  border-radius:var(--radius-sm); color:var(--text-muted);
  font-family:var(--font-display); font-weight:600; font-size:12px;
  padding:0 14px; cursor:pointer;
  white-space:nowrap;
}
.ride-log-sort-dir-btn:hover{ color:var(--neon-green); border-color:var(--neon-green-dim); }

.ride-map-view{ display:flex; flex-direction:column; gap:14px; }
.ride-map-picker select{
  width:100%; min-height:var(--touch);
  background:var(--bg-panel); border:1px solid var(--border-subtle);
  border-radius:var(--radius-sm); color:var(--text-primary);
  padding:0 14px; font-family:var(--font-body); font-size:15px; cursor:pointer;
}
.ride-map-wrap{
  border-radius:var(--radius-lg); overflow:hidden;
  border:1px solid var(--border-subtle);
}
#rideMapCanvas{ width:100%; height:60vh; min-height:360px; background:var(--bg-panel-raised); }
.ride-map-stats{
  display:grid; grid-template-columns:repeat(auto-fit,minmax(110px,1fr));
  gap:10px;
}
.ride-map-stats .box{
  background:var(--bg-panel); border:1px solid var(--border-subtle);
  border-radius:var(--radius-md); padding:12px; text-align:center;
}
.ride-map-stats .box .v{ font-family:var(--font-mono); font-size:18px; color:var(--neon-green); }
.ride-map-stats .box .k{ font-family:var(--font-display); font-size:10px; color:var(--text-dim); letter-spacing:0.1em; text-transform:uppercase; margin-top:4px; }

.ride-grid{
  display:grid;
  grid-template-columns:repeat(auto-fill,minmax(300px,1fr));
  gap:16px;
}
.ride-card{
  background:var(--bg-panel);
  border:1px solid var(--border-subtle);
  border-radius:var(--radius-lg);
  padding:16px;
  cursor:pointer;
  transition:border-color .15s ease, transform .15s ease;
}
.ride-card:hover{border-color:var(--neon-green-dim); transform:translateY(-2px);}
.ride-card canvas{width:100%; height:110px; display:block; border-radius:var(--radius-sm); background:var(--bg-void); margin-bottom:12px;}
.ride-card-title{
  font-family:var(--font-display); font-weight:700; font-size:17px;
  color:var(--text-primary);
  margin-bottom:6px;
  overflow:hidden; text-overflow:ellipsis; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical;
  cursor:text;
  border-radius:6px;
  /* Room for the long-press-to-edit affordance without shifting layout
     on hover/focus. */
  padding:2px 4px; margin-left:-4px; margin-right:-4px;
}
.ride-card-title:hover{ background:var(--bg-panel-raised); }
.ride-card-title-input{
  width:100%;
  font:inherit; font-weight:700; color:var(--text-primary);
  background:var(--bg-panel-raised);
  border:1px solid var(--neon-green-dim);
  border-radius:6px;
  padding:2px 6px;
  margin:-2px -6px;
}
.ride-card .date-row{display:flex; align-items:center; justify-content:space-between; gap:8px;}
.ride-card .date{font-family:var(--font-display); font-weight:600; font-size:15px; color:var(--text-primary);}
.ride-card .weather-chip{
  flex-shrink:0;
  font-family:var(--font-mono); font-size:12px; font-weight:600; color:var(--text-muted);
  background:var(--bg-panel-raised); border:1px solid var(--border-subtle);
  border-radius:20px; padding:3px 10px;
  white-space:nowrap;
}
.ride-card .stats{
  display:grid; grid-template-columns:repeat(3,1fr);
  gap:8px; margin-top:12px;
}
.ride-card .stat .v{font-family:var(--font-mono); font-size:16px; color:var(--neon-green);}
.ride-card .stat .k{font-family:var(--font-display); font-size:10px; color:var(--text-dim); letter-spacing:0.1em; text-transform:uppercase;}

/* ---------- combine trips ---------- */
.ride-card{ position:relative; }
.ride-card.combine-mode{ padding-left:46px; }
.ride-card.combine-mode.selected{ border-color:var(--neon-green); box-shadow:0 0 0 1px var(--neon-green); }
.ride-card-checkbox{
  display:none;
  position:absolute; left:14px; top:16px; z-index:2;
  width:24px; height:24px; border-radius:50%;
  border:2px solid var(--border-strong); background:var(--bg-panel-raised);
  align-items:center; justify-content:center;
  font-size:13px; color:transparent;
}
.ride-card.combine-mode .ride-card-checkbox{ display:flex; }
.ride-card.combine-mode.selected .ride-card-checkbox{
  background:var(--neon-green); border-color:var(--neon-green); color:#06280f;
}
.combine-actionbar{
  position:fixed; left:50%; bottom:22px; transform:translateX(-50%);
  z-index:210;
  display:flex; align-items:center; gap:14px;
  background:var(--bg-panel-raised); border:1px solid var(--border-strong);
  border-radius:999px; padding:10px 12px 10px 20px;
  box-shadow:0 12px 32px rgba(0,0,0,0.35);
  font-family:var(--font-display); font-size:13px; color:var(--text-muted);
}
.combine-order-list{ display:flex; flex-direction:column; gap:8px; max-height:50vh; overflow-y:auto; }
.combine-order-item{
  display:flex; align-items:center; gap:10px;
  background:var(--bg-panel-raised); border:1px solid var(--border-subtle);
  border-radius:var(--radius-md); padding:10px 12px;
}
.combine-order-item .combine-order-num{
  font-family:var(--font-mono); font-weight:700; color:var(--neon-green);
  width:22px; text-align:center; flex-shrink:0;
}
.combine-order-item .combine-order-info{ flex:1; min-width:0; }
.combine-order-item .combine-order-date{ font-family:var(--font-display); font-weight:600; font-size:13px; color:var(--text-primary); }
.combine-order-item .combine-order-meta{ font-family:var(--font-mono); font-size:11px; color:var(--text-muted); }
.combine-order-item .combine-order-btns{ display:flex; gap:4px; flex-shrink:0; }
.combine-order-item .combine-order-btns button{
  appearance:none; border:1px solid var(--border-subtle); background:var(--bg-void);
  color:var(--text-primary); border-radius:6px; width:30px; height:30px; cursor:pointer; font-size:13px;
}
.combine-order-item .combine-order-btns button:disabled{ opacity:0.3; cursor:default; }

/* ---------- route editor ---------- */
/* Higher than .menu-btn's fixed z-index:600 — a true full-screen overlay
   (unlike the other, centered modals, which sit inset enough not to
   collide with the corner hamburger button) needs to actually cover it,
   not just sit visually behind it while still being "in front". Compound
   selector (.modal-overlay.route-editor-overlay, not just
   .route-editor-overlay) deliberately outranks the base .modal-overlay's
   own z-index:100 rule, which is defined later in this file — at equal
   specificity that source-order would otherwise let it win and silently
   put this back behind the hamburger button. Confirmed live: without the
   compound selector, getComputedStyle reported z-index 100, not 650. */
.modal-overlay.route-editor-overlay{ z-index:650; padding:0; }
.route-editor-box{
  width:100%; height:100%; max-width:none; max-height:none;
  background:var(--bg-void); display:flex; flex-direction:column;
}
.route-editor-header{
  display:flex; align-items:center; justify-content:space-between; gap:12px;
  padding:14px 16px; border-bottom:1px solid var(--border-subtle);
  background:var(--bg-panel);
}
.route-editor-body{
  flex:1; overflow-y:auto; padding:14px 16px 10px;
  display:flex; flex-direction:column; gap:18px;
}
.route-editor-map-wrap{
  position:relative;
  border-radius:var(--radius-lg); overflow:hidden; border:1px solid var(--border-subtle);
  /* min-height is a floor, not just a target: the floating map toolbar
     (#reMapToolbar, ~300px of stacked icon buttons) is an absolutely
     positioned child, so on a very short viewport (a landscape phone)
     height:min(46vh,420px) alone could shrink this below the toolbar's
     own height and clip it against overflow:hidden. */
  height:min(46vh, 420px); min-height:320px; background:var(--bg-panel-raised);
}
#routeEditorMap{ width:100%; height:100%; }
/* Contextual hint for the Reroute/Detour tool — floats over the map (where
   the rider is about to tap) instead of a static paragraph in the body
   that pushes every control below it down whenever the tool is toggled on. */
.route-editor-map-hint{
  position:absolute; left:10px; right:10px; bottom:10px; z-index:10; margin:0;
  background:var(--menu-panel-bg);
  backdrop-filter:blur(10px); -webkit-backdrop-filter:blur(10px);
  border:1px solid var(--border-subtle); border-radius:var(--radius-md);
  padding:10px 12px; font-size:12.5px; line-height:1.45; color:var(--text-muted);
}
/* Floating map toolbar — zoom/pan/layer/point tools that act directly on
   the map, docked top-right so they never collide with the Reroute hint
   banner (bottom) or Leaflet's own attribution area. A vertical icon
   stack rather than app-wide .btn styling: these are map chrome, not
   editor actions, so they read as part of the map surface itself. */
.route-editor-map-toolbar{
  position:absolute; top:10px; right:10px; z-index:10;
  display:flex; flex-direction:column; gap:2px;
  background:var(--menu-panel-bg);
  backdrop-filter:blur(10px); -webkit-backdrop-filter:blur(10px);
  border:1px solid var(--border-subtle); border-radius:var(--radius-md);
  padding:5px;
}
.re-map-btn{
  width:38px; height:38px; padding:0; flex-shrink:0;
  display:flex; align-items:center; justify-content:center;
  border-radius:var(--radius-sm);
  border:1px solid transparent;
  background:transparent; color:var(--text-primary);
  font-family:var(--font-body); font-size:16px; line-height:1;
  cursor:pointer;
  transition:background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.re-map-btn:hover{ background:var(--bg-panel-hover); }
.re-map-btn:active{ filter:brightness(0.92); }
.re-map-btn.active{
  background:rgba(79,140,255,0.22); border-color:rgba(79,140,255,0.6); color:var(--neon-green);
}
.re-map-btn.danger:hover{ background:rgba(239,68,68,0.16); color:var(--alert-red); }
.re-map-btn:disabled{ opacity:0.35; cursor:default; }
.re-map-btn:disabled:hover{ background:transparent; }
.route-editor-map-btn-sep{ height:1px; margin:3px 6px; background:var(--border-subtle); }
.route-editor-scrub-row{ display:flex; align-items:center; gap:10px; }
.route-editor-play-btn{ width:40px; height:40px; flex-shrink:0; border-radius:50%; padding:0; }
.route-editor-scrub{ flex:1; accent-color:var(--neon-green); }
.route-editor-time{ font-family:var(--font-mono); font-size:12px; color:var(--text-muted); width:52px; text-align:right; flex-shrink:0; }
.route-editor-scrub-track{
  position:relative; height:14px; border-radius:7px; background:var(--bg-panel-raised);
  border:1px solid var(--border-subtle); overflow:hidden; margin-top:-10px;
}
.re-track-range{
  position:absolute; top:0; bottom:0;
  background:rgba(79,140,255,0.4); border-left:2px solid var(--neon-green); border-right:2px solid var(--neon-green);
}
.re-track-cursor{
  position:absolute; top:-2px; bottom:-2px; width:2px; background:var(--route-highlight);
  box-shadow:0 0 6px rgba(255,157,46,0.8);
}

/* Each toolbar is a labeled group (Trim & Clip / Tools / Route File) instead
   of one undifferentiated wall of buttons — the label uses the same
   typographic treatment as .route-editor-changelist so every section of
   the editor body reads as part of one consistent rhythm. A grid rather
   than flex-wrap gives predictable, evenly-sized columns at any width
   instead of a ragged last row. */
.route-editor-toolbar-group{ display:flex; flex-direction:column; gap:8px; }
.route-editor-toolbar-group--divider{ padding-top:16px; border-top:1px solid var(--border-subtle); }
.route-editor-group-label{
  font-family:var(--font-display); font-size:11px; letter-spacing:0.06em; text-transform:uppercase;
  color:var(--text-dim);
}
.route-editor-toolbar{
  display:grid; grid-template-columns:repeat(auto-fit, minmax(96px, 1fr)); gap:8px;
}
.route-editor-toolbar .btn{ min-width:0; font-size:12px; padding:9px 10px; }
.route-editor-toolbar .btn.active{ background:rgba(79,140,255,0.22); border-color:rgba(79,140,255,0.6); color:var(--text-primary); }
.route-editor-changelist{
  background:var(--bg-panel); border:1px solid var(--border-subtle);
  border-radius:var(--radius-md); padding:10px 12px;
}
.route-editor-changelist .route-editor-group-label{ margin-bottom:6px; }
.route-editor-changelist ul{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:4px; }
.route-editor-changelist li{
  font-family:var(--font-mono); font-size:12px; color:var(--text-muted);
  padding-left:14px; position:relative;
}
.route-editor-changelist li::before{ content:"–"; position:absolute; left:0; color:var(--neon-green); }
.route-editor-footer{
  display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap;
  padding:12px 16px; border-top:1px solid var(--border-subtle); background:var(--bg-panel);
}
.route-editor-footer-actions{ display:flex; gap:10px; }
.route-editor-live-stats{
  display:flex; align-items:center; gap:8px;
  font-family:var(--font-mono); font-size:12px; color:var(--text-muted);
}
/* Small at-a-glance signal that there are unsaved edits — otherwise the
   only cue is the Undo button no longer being greyed out, easy to miss
   at a glance versus the toolbar full of icons around it. */
.route-editor-dirty-dot{
  width:7px; height:7px; border-radius:50%; flex-shrink:0;
  background:var(--route-highlight); box-shadow:0 0 6px rgba(255,157,46,0.7);
}
.replay-route-marker{
  width:14px; height:14px; border-radius:50%; background:var(--route-highlight);
  border:2px solid var(--route-highlight-ink); box-shadow:0 0 0 4px rgba(255,157,46,0.28);
}

/* ---------- ride detail ---------- */
.detail-header{
  display:flex; align-items:center; justify-content:space-between;
  margin-bottom:16px; flex-wrap:wrap; gap:10px;
}
.detail-stats{
  display:grid; grid-template-columns:repeat(auto-fit, minmax(100px, 1fr));
  gap:12px; margin-bottom:16px;
}
.detail-stats .box{
  background:var(--bg-panel); border:1px solid var(--border-subtle);
  border-radius:var(--radius-md); padding:12px; text-align:center;
}
.detail-stats .box .v{font-family:var(--font-mono); font-size:18px; color:var(--neon-green);}
.detail-stats .box .k{font-family:var(--font-display); font-size:10px; color:var(--text-dim); letter-spacing:0.1em; text-transform:uppercase; margin-top:4px;}
.playback-bar{
  display:flex; align-items:center; gap:12px; margin-top:10px;
}
.playback-bar input[type=range]{flex:1; accent-color:var(--neon-green);}
.chart-panel{margin-top:16px;}
.chart-panel canvas{width:100%; height:180px;}

/* ---------- weather alert detail (weather-alert.html) ----------
   Severity color comes in via the --wxalert-color custom property, set
   inline per-alert from SEVERITY_META in js/weather-alert.js — it always
   points at one of the app's existing --alert-red/--warn-amber/
   --neon-green tokens (or a color-mix of two of them for "Severe"), so
   the band automatically follows whichever of the 12 themes is active
   instead of carrying its own fixed palette. */
.wxalert-topbar{ margin-bottom:14px; }

.wxalert-skeleton{ padding:20px 4px; }
.wxalert-skeleton-bar{
  background:var(--bg-panel-raised); border-radius:var(--radius-sm);
  animation:wxalert-pulse 1.4s ease-in-out infinite;
}
@keyframes wxalert-pulse{ 0%,100%{opacity:0.5;} 50%{opacity:1;} }

.wxalert-test-banner{
  background:color-mix(in srgb, var(--cyan) 18%, var(--bg-panel-raised));
  border:1px solid color-mix(in srgb, var(--cyan) 50%, var(--border-subtle));
  color:var(--text-primary);
  border-radius:var(--radius-md);
  padding:10px 14px;
  margin-bottom:14px;
  font-family:var(--font-display); font-weight:600; font-size:13px; letter-spacing:0.02em;
}

.wxalert-hero{
  position:relative;
  overflow:hidden;
  border-radius:var(--radius-lg);
  border:1px solid color-mix(in srgb, var(--wxalert-color, var(--border-strong)) 45%, var(--border-subtle));
  background:
    linear-gradient(135deg, color-mix(in srgb, var(--wxalert-color, var(--text-muted)) 14%, var(--bg-panel)) 0%, var(--bg-panel) 65%);
  box-shadow:0 6px 28px color-mix(in srgb, var(--wxalert-color, var(--text-muted)) 20%, transparent);
  padding:22px 22px 20px;
  margin-bottom:18px;
}
.wxalert-hero::before{
  content:"";
  position:absolute; top:0; left:0; right:0; height:5px;
  background:var(--wxalert-color, var(--text-muted));
}
.wxalert-hero-top{
  display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:8px;
}
.wxalert-severity-badge{
  display:inline-flex; align-items:center; gap:6px;
  padding:5px 12px; border-radius:999px;
  background:color-mix(in srgb, var(--wxalert-color) 20%, transparent);
  border:1px solid var(--wxalert-color);
  color:var(--wxalert-color);
  font-family:var(--font-display); font-weight:700; font-size:12px; letter-spacing:0.08em; text-transform:uppercase;
}
.wxalert-status-badge{
  display:inline-flex; align-items:center; gap:5px;
  padding:5px 12px; border-radius:999px;
  background:color-mix(in srgb, var(--wxalert-status-color, var(--text-muted)) 16%, transparent);
  color:var(--wxalert-status-color, var(--text-muted));
  font-family:var(--font-display); font-weight:700; font-size:11px; letter-spacing:0.08em; text-transform:uppercase;
}
.wxalert-headline{
  font-family:var(--font-display); font-weight:700;
  font-size:clamp(22px, 3.6vw, 32px); line-height:1.16;
  color:var(--text-primary);
  margin:12px 0 4px;
}
.wxalert-event-sub{ font-size:14px; color:var(--text-muted); }
.wxalert-badges-row{ display:flex; flex-wrap:wrap; gap:8px; margin-top:16px; }
.wxalert-chip{
  background:var(--bg-panel-raised); border:1px solid var(--border-subtle);
  border-radius:8px; padding:6px 10px;
  font-size:12px; color:var(--text-muted);
  white-space:nowrap;
}
.wxalert-chip strong{ color:var(--text-primary); font-weight:600; margin-right:4px; }

.wxalert-timeline{
  display:flex; flex-wrap:wrap; align-items:center; gap:16px;
  margin-top:18px; padding-top:16px;
  border-top:1px solid var(--border-subtle);
}
.wxalert-timeline-item{ flex:1; min-width:150px; }
.wxalert-timeline-sent{ flex-basis:100%; opacity:0.8; }
.wxalert-timeline-arrow{ font-size:20px; color:var(--wxalert-color, var(--text-muted)); }
.wxalert-timeline-label{
  font-family:var(--font-display); font-size:10px; font-weight:700;
  text-transform:uppercase; letter-spacing:0.1em; color:var(--text-dim);
  margin-bottom:4px;
}
.wxalert-timeline-value{ font-family:var(--font-mono); font-size:15px; color:var(--text-primary); }
.wxalert-timeline-rel{ font-size:12px; color:var(--text-muted); margin-top:2px; }

.wxalert-grid{
  display:grid; grid-template-columns:1.6fr 1fr; gap:16px;
}
@media (max-width:900px){ .wxalert-grid{ grid-template-columns:1fr; } }
.wxalert-main-col, .wxalert-side-col{ display:flex; flex-direction:column; gap:16px; min-width:0; }

.wxalert-panel{
  background:var(--bg-panel); border:1px solid var(--border-subtle);
  border-radius:var(--radius-lg);
}
.wxalert-description p{
  margin:0 0 12px; line-height:1.65; font-size:15px; color:var(--text-primary);
}
.wxalert-description p:last-child{ margin-bottom:0; }
.wxalert-muted{ color:var(--text-dim); font-size:14px; margin:0; }

.wxalert-instruction-box{
  background:color-mix(in srgb, var(--warn-amber) 10%, var(--bg-panel));
  border:1px solid color-mix(in srgb, var(--warn-amber) 42%, var(--border-subtle));
  border-radius:var(--radius-lg);
  padding:16px;
}
.wxalert-instruction-box .panel-title{ color:var(--warn-amber); }

.wxalert-param-grid{
  display:grid; grid-template-columns:repeat(auto-fit, minmax(120px, 1fr)); gap:10px;
}
.wxalert-param-box{
  background:var(--bg-panel-raised); border:1px solid var(--border-subtle);
  border-radius:var(--radius-md); padding:12px; text-align:center;
}
.wxalert-param-icon{ font-size:18px; margin-bottom:4px; }
.wxalert-param-value{ font-family:var(--font-mono); font-size:16px; color:var(--text-primary); }
.wxalert-param-label{
  font-family:var(--font-display); font-size:10px; color:var(--text-dim);
  letter-spacing:0.06em; text-transform:uppercase; margin-top:4px;
}

.wxalert-vtec-row{ display:flex; flex-wrap:wrap; gap:8px; }
.wxalert-vtec-row + .wxalert-vtec-row{ margin-top:10px; padding-top:10px; border-top:1px dashed var(--border-subtle); }

.wxalert-area-list{ list-style:none; margin:0; padding:0; }
.wxalert-area-list li{
  padding:8px 0; font-size:14px; color:var(--text-primary);
  border-bottom:1px dashed var(--border-subtle);
}
.wxalert-area-list li:last-child{ border-bottom:none; }

.wxalert-detail-list{ margin:0; }
.wxalert-detail-list dt{
  font-family:var(--font-display); font-size:10px; font-weight:700;
  text-transform:uppercase; letter-spacing:0.08em; color:var(--text-dim);
  margin-top:12px;
}
.wxalert-detail-list dt:first-child{ margin-top:0; }
.wxalert-detail-list dd{ margin:2px 0 0; font-size:14px; color:var(--text-primary); }

.wxalert-footer-note{
  text-align:center; font-size:12px; color:var(--text-dim);
  margin-top:20px; padding-top:16px; border-top:1px solid var(--border-subtle);
}

/* Empty/error states (no url param, bad url, fetch failure, 404,
   malformed response) — deliberately reuses the same visual language as
   the rest of the page rather than a plain unstyled fallback string. */
.wxalert-state{
  text-align:center; max-width:440px; margin:40px auto; padding:20px;
}
.wxalert-state-icon{ font-size:44px; margin-bottom:10px; }
.wxalert-state-title{
  font-family:var(--font-display); font-weight:700; font-size:19px;
  color:var(--text-primary); margin-bottom:8px;
}
.wxalert-state-desc{ font-size:14px; color:var(--text-muted); line-height:1.55; margin-bottom:20px; }
.wxalert-state-actions{ display:flex; gap:10px; justify-content:center; flex-wrap:wrap; }

/* ---------- light theme override ---------- */
body.theme-light{
  --bg-void:#f4f5f7;
  --bg-panel:#ffffff;
  --bg-panel-raised:#f8f9fb;
  --bg-panel-hover:#eef0f4;
  --border-subtle:#e1e4ea;
  --border-strong:#c7ccd6;
  --text-primary:#161a22;
  --text-muted:#4b525f;
  /* Was #70788a — 4.2-4.43:1 against the panel backgrounds it's actually
     used on, just under WCAG AA's 4.5:1 for normal text. Darkened to
     clear 4.5:1+ against both --bg-panel and --bg-panel-raised while
     staying visibly lighter than --text-muted (dim still recedes toward
     the light background more than muted does). */
  --text-dim:#616980;
  --neon-green:#3568e0;
  --neon-green-dim:#c3d6fb;
  --neon-green-glow:rgba(53,104,224,0.16);
  --menu-panel-bg:rgba(255,255,255,0.92);
}
body.theme-light .speed-value{color:#f2f4f2;}
body.theme-light .btn.primary{color:#ffffff;}

.trip-footer{
  display:grid;
  grid-template-columns:repeat(4,1fr);
  gap:8px;
  margin-top:12px;
  padding-top:12px;
  border-top:1px solid var(--border-subtle);
}
.trip-footer .trip-stat{
  background:var(--bg-panel-raised);
  border:1px solid var(--border-subtle);
  border-radius:var(--radius-md);
  padding:10px 6px;
  text-align:center;
}
.trip-footer .trip-stat .v{
  font-family:var(--font-mono);
  font-size:15px;
  color:var(--neon-green);
}
.trip-footer .trip-stat .k{
  font-family:var(--font-display);
  font-size:9px;
  color:var(--text-dim);
  letter-spacing:0.08em;
  text-transform:uppercase;
  margin-top:2px;
}
@media (max-width:520px){
  .modal-overlay{ padding:8px; }
  .modal-box{ padding:12px; border-radius:18px; }
  .settings-tabs{ grid-template-columns:repeat(2, minmax(0, 1fr)); }
  .settings-tab{ min-height:36px; font-size:11px; }
  .trip-footer{grid-template-columns:repeat(2,1fr);} 
}

@media (max-width:480px){
  .trip-footer{grid-template-columns:repeat(2,1fr);} 
}

/* ---------- settings modal ---------- */
.modal-overlay{
  position:fixed; inset:0; z-index:100;
  background:rgba(5,7,9,0.72);
  backdrop-filter:blur(3px);
  display:flex; align-items:center; justify-content:center;
  padding:16px;
}
.modal-box{
  width:min(100%, 540px);
  max-height:min(88vh, 860px);
  overflow:hidden;
  display:flex; flex-direction:column;
  background:var(--card-bg-gradient, var(--bg-panel));
  border:1px solid var(--border-strong);
  border-radius:24px;
  padding:16px;
  box-shadow:0 18px 44px rgba(0,0,0,0.45);
}
.modal-header{
  display:flex; align-items:center; justify-content:space-between;
  gap:10px;
  margin-bottom:10px;
  flex-shrink:0;
}
.modal-header h3{
  margin:0;
  font-family:var(--font-display);
  font-size:20px;
  color:var(--text-primary);
}
.modal-desc{
  color:var(--text-muted);
  font-size:13px;
  line-height:1.6;
  margin:0 0 14px;
}
.current-default-row{
  display:flex; align-items:center; justify-content:space-between;
  gap:10px;
  background:var(--bg-panel-raised);
  border:1px solid var(--border-subtle);
  border-radius:var(--radius-md);
  padding:10px 12px;
  font-size:14px;
}
.settings-hero{
  display:flex; align-items:flex-start; justify-content:space-between;
  gap:10px;
  background:linear-gradient(90deg, rgba(79,140,255,0.12), rgba(34,211,238,0.08));
  border:1px solid var(--border-subtle);
  border-radius:16px;
  padding:12px 14px;
  margin-bottom:12px;
}
.settings-pill{
  flex-shrink:0;
  border:1px solid var(--neon-green-dim);
  color:var(--neon-green);
  background:rgba(79,140,255,0.1);
  border-radius:999px;
  padding:6px 10px;
  font-family:var(--font-display);
  font-weight:600;
  font-size:11px;
  letter-spacing:0.05em;
  text-transform:uppercase;
}

/* ---------- collapsible settings groups (native <details>, no JS needed) ---------- */
.settings-group{
  background:var(--bg-panel-raised);
  border:1px solid var(--border-subtle);
  border-radius:var(--radius-md);
  margin-bottom:10px;
  overflow:hidden;
}
.settings-group summary{
  list-style:none;
  cursor:pointer;
  display:flex; align-items:center; justify-content:space-between; gap:10px;
  padding:14px 16px;
  font-family:var(--font-display);
  font-weight:600; font-size:14px;
  color:var(--text-primary);
  user-select:none;
}
.settings-group summary::-webkit-details-marker{ display:none; }
.settings-group summary .sg-meta{
  font-family:var(--font-body);
  font-weight:400; font-size:12px;
  color:var(--text-muted);
  margin-left:auto; margin-right:4px;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; max-width:40%;
}
.settings-group summary::after{
  content:"";
  flex-shrink:0;
  width:9px; height:9px;
  border-right:2px solid var(--text-dim);
  border-bottom:2px solid var(--text-dim);
  transform:rotate(-45deg);
  transition:transform 0.2s ease;
  margin-top:-3px;
}
.settings-group[open] summary::after{ transform:rotate(45deg); margin-top:0; }
.settings-group[open] summary{ border-bottom:1px solid var(--border-subtle); }
.settings-group-body{ padding:4px 16px 16px; }
.settings-group summary:hover{ color:var(--neon-green); }
.plugin-settings-card{
  background:var(--bg-panel-raised);
  border:1px solid var(--border-subtle);
  border-radius:16px;
  padding:12px;
  margin-bottom:10px;
}
.settings-row{
  display:flex; align-items:center; justify-content:space-between; gap:12px;
  padding:9px 0; border-bottom:1px solid var(--border-subtle);
  font-family:var(--font-body); font-size:14px; color:var(--text-primary);
}
.settings-row:last-child{ border-bottom:none; }
.settings-row input[type="checkbox"]{ width:20px; height:20px; flex-shrink:0; accent-color:var(--neon-green); }
.plugin-list{ display:flex; flex-direction:column; gap:8px; }
.plugin-list .route-form{ flex-wrap:wrap; }
.plugin-list .route-form input{ flex:1 1 180px; }
.plugin-list-item{
  display:flex; align-items:center; justify-content:space-between;
  gap:10px;
  background:var(--bg-panel);
  border:1px solid var(--border-subtle);
  border-radius:12px;
  padding:10px 12px;
}
.plugin-list-item strong{ display:block; color:var(--text-primary); }
.plugin-list-item span{ display:block; color:var(--text-muted); font-size:12px; margin-top:2px; }
.plugin-actions{ display:flex; align-items:center; gap:8px; }
.plugin-actions .btn{ min-height:36px; padding:0 12px; }
.settings-tabs{
  display:grid;
  grid-template-columns:repeat(4, minmax(0, 1fr));
  gap:4px;
  background:var(--bg-panel-raised);
  padding:4px; border-radius:14px;
  margin-bottom:12px;
  flex-shrink:0;
}
.settings-tab{
  min-height:40px;
  border:none; background:transparent;
  color:var(--text-muted);
  font-family:var(--font-display); font-weight:600; font-size:11px; letter-spacing:0.04em;
  border-radius:10px; cursor:pointer;
  white-space:nowrap;
  overflow:hidden; text-overflow:ellipsis;
  padding:0 6px;
}
.settings-tab.active{ background:var(--bg-panel); color:var(--neon-green); }
.settings-panel{
  flex:1; min-height:0; overflow-y:auto; padding-right:2px;
}

/* ---------- forecast card ---------- */
.forecast-days{
  display:flex; gap:10px; overflow-x:auto; padding-bottom:4px;
}
.forecast-day{
  position:relative;
  flex:0 0 96px;
  background:var(--bg-panel-raised);
  border:1px solid var(--border-subtle);
  border-radius:var(--radius-md);
  padding:10px 8px;
  text-align:center;
  cursor:pointer;
}
.forecast-day:hover, .forecast-day:focus-visible{ border-color:var(--border-strong); outline:none; }
.forecast-day.active{ border-color:var(--neon-green); }
.forecast-day.has-alert{ padding-top:26px; }
.forecast-day .fd-label{
  font-family:var(--font-display); font-size:11px; letter-spacing:0.08em;
  text-transform:uppercase; color:var(--text-dim);
}
.forecast-day .fd-icon{ font-size:26px; margin:6px 0; }
.forecast-day .fd-temps{ font-family:var(--font-mono); font-size:13px; }
.forecast-day .fd-hi{ color:var(--text-primary); font-weight:700; }
.forecast-day .fd-lo{ color:var(--text-dim); margin-left:4px; }
.forecast-day .fd-meta{ font-size:10px; color:var(--text-muted); margin-top:4px; }

/* Small alert strip pinned across the top of a forecast-day card, shown
   only when checkWeatherAlerts() found an NWS watch/warning overlapping
   that calendar day. Its own click is caught before the card's, so
   tapping it opens the alert's detail instead of the day's forecast. */
.forecast-day-alert-banner{
  position:absolute; top:0; left:0; right:0; z-index:2;
  display:block; width:100%;
  background:linear-gradient(180deg, rgba(255,59,92,0.95), rgba(190,28,58,0.95));
  color:#fff; border:none; border-radius:var(--radius-md) var(--radius-md) 0 0;
  font-family:var(--font-display); font-size:9px; font-weight:700; letter-spacing:0.02em;
  padding:4px 6px; white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
  cursor:pointer;
}

/* ---------- forecast day detail modal ---------- */
.fdm-alert-banner{
  display:flex; align-items:center; gap:8px; width:100%;
  background:linear-gradient(180deg, rgba(255,59,92,0.95), rgba(190,28,58,0.95));
  color:#fff; border:none; border-radius:var(--radius-md);
  font-family:var(--font-display); font-size:13px; font-weight:600;
  padding:10px 12px; margin-bottom:14px; cursor:pointer; text-align:left;
}
.fdm-alert-banner .fdm-alert-icon{ font-size:16px; flex-shrink:0; }
.fdm-alert-banner .fdm-alert-text{ flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.fdm-alert-banner .fdm-alert-chevron{ font-size:18px; opacity:0.8; flex-shrink:0; }

.fdm-summary{ display:flex; align-items:center; gap:14px; margin-bottom:14px; }
.fdm-summary-icon{ font-size:44px; flex-shrink:0; }
.fdm-summary-main{ min-width:0; }
.fdm-summary-temps{ font-family:var(--font-mono); font-size:22px; }
.fdm-hi{ color:var(--text-primary); font-weight:700; }
.fdm-lo{ color:var(--text-dim); margin-left:6px; }
.fdm-summary-desc{ font-size:13px; color:var(--text-muted); text-transform:capitalize; margin-top:2px; }

.fdm-stat-grid{
  display:grid; grid-template-columns:1fr 1fr; gap:8px;
  margin-bottom:16px;
}
.fdm-stat{
  background:var(--bg-panel-raised); border:1px solid var(--border-subtle); border-radius:var(--radius-sm);
  padding:8px 10px; display:flex; flex-direction:column; gap:2px;
}
.fdm-stat-label{ font-family:var(--font-display); font-size:10px; letter-spacing:0.06em; text-transform:uppercase; color:var(--text-dim); }
.fdm-stat-value{ font-family:var(--font-mono); font-size:15px; color:var(--text-primary); }

.fdm-segments{ display:flex; gap:8px; overflow-x:auto; padding-bottom:4px; margin-bottom:6px; }
.fdm-segment{
  flex:0 0 68px; text-align:center;
  background:var(--bg-panel-raised); border:1px solid var(--border-subtle); border-radius:var(--radius-sm);
  padding:8px 4px;
}
.fdm-segment-time{ font-family:var(--font-display); font-size:9px; letter-spacing:0.04em; text-transform:uppercase; color:var(--text-dim); }
.fdm-segment-icon{ font-size:18px; margin:4px 0; }
.fdm-segment-temp{ font-family:var(--font-mono); font-size:13px; color:var(--text-primary); font-weight:700; }
.fdm-segment-pop{ font-size:9px; color:var(--text-muted); margin-top:2px; }

.fdm-carousel-title{
  font-family:var(--font-display); font-size:11px; letter-spacing:0.08em; text-transform:uppercase;
  color:var(--text-dim); margin:8px 0 8px; padding-top:10px; border-top:1px solid var(--border-subtle);
}
.fdm-carousel{ margin-bottom:2px; }

/* ---------- layout editor (Settings > Layout) ---------- */
.layout-editor-rows{ display:flex; flex-direction:column; gap:10px; }
.layout-editor-row{
  background:var(--bg-panel-raised);
  border:1px solid var(--border-subtle);
  border-radius:var(--radius-md);
  padding:8px;
}
.row-grip{
  font-family:var(--font-mono); font-size:11px; color:var(--text-dim);
  letter-spacing:0.1em; text-transform:uppercase;
  padding:2px 4px 8px; cursor:grab; user-select:none;
}
.layout-editor-chips{
  display:flex; flex-wrap:wrap; gap:8px; min-height:40px;
}
.layout-editor-chip{
  display:flex; align-items:center; gap:6px;
  background:var(--bg-panel);
  border:1px solid var(--border-subtle);
  border-radius:20px;
  padding:6px 10px;
  font-size:13px;
  cursor:grab;
  user-select:none;
}
.layout-editor-chip.is-hidden{ opacity:0.45; }
.layout-editor-chip.is-selected{
  border-color:var(--neon-green);
  box-shadow:0 0 0 2px var(--neon-green-dim);
  background:rgba(79,140,255,0.1);
}
.layout-editor-chip{ touch-action:manipulation; }
.layout-drop-slot{
  width:14px;
  min-height:34px;
  flex-shrink:0;
  padding:0;
  border-radius:6px;
  border:1px dashed var(--neon-green-dim);
  background:rgba(79,140,255,0.08);
  cursor:pointer;
}
.layout-drop-slot:hover, .layout-drop-slot:active{ background:rgba(79,140,255,0.22); }
.layout-drop-slot-newrow{
  display:block;
  width:100%;
  min-height:40px;
  margin-top:8px;
  border-radius:var(--radius-md);
  border:1px dashed var(--neon-green-dim);
  background:rgba(79,140,255,0.06);
  color:var(--neon-green);
  font-family:var(--font-display);
  font-size:12px;
  letter-spacing:0.06em;
  text-transform:uppercase;
  cursor:pointer;
}
.layout-editor-chip .grip{ color:var(--text-dim); }
.layout-editor-chip .chip-eye{
  background:none; border:none; cursor:pointer; font-size:14px; padding:0; line-height:1;
}
.layout-add-row-btns{ display:flex; gap:8px; margin-top:12px; }
.layout-add-row-btn{ width:100%; }
.sortable-ghost{ opacity:0.3; }
.sortable-chosen{ box-shadow:0 0 0 2px var(--neon-green-dim); }

/* Carousel row in the editor: same chip strip as a normal row, but boxed
   with a dashed accent border so it reads as a distinct row type, and its
   "+" column-add control lives inline at the end of the chip row (the
   "| chip | chip | + |" layout this was requested as). */
.layout-editor-row.is-carousel{
  border:1px dashed var(--neon-green-dim);
  background:rgba(79,140,255,0.05);
}
.layout-add-column-btn{
  flex-shrink:0;
  width:34px; height:34px;
  border-radius:50%;
  border:1px dashed var(--neon-green-dim);
  background:rgba(79,140,255,0.1);
  color:var(--neon-green);
  font-family:var(--font-display);
  font-size:16px; font-weight:700;
  cursor:pointer;
}
.layout-add-column-btn:hover{ background:rgba(79,140,255,0.22); }
.layout-editor-placeholder select{
  min-height:34px;
  border-radius:20px;
  border:1px dashed var(--border-strong);
  background:var(--bg-panel);
  color:var(--text-muted);
  font-size:12px;
  padding:0 10px;
  cursor:pointer;
}

/* ---------- icon nav bar (js/icon-toolbar.js) ---------- */
/* Slim, transparent strip added as the first child of every .rr-card
   (built-in and plugin alike) by attachMinimizeButtons() — living in normal
   flow above a card's own content means it can never overlap a card's own
   controls (radar chip, Spotify playlist toggle, etc.), unlike an absolutely
   positioned corner badge would on several cards. */
.card-minimize-cap{
  display:flex; justify-content:flex-end;
  padding:6px 6px 0;
  position:relative; z-index:3;
}
.card-minimize-btn{
  width:28px; height:28px; padding:0;
  display:grid; place-items:center;
  background:var(--bg-panel-raised);
  border:1px solid var(--border-subtle);
  border-radius:8px;
  color:var(--text-muted);
  cursor:pointer;
  transition:background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease, transform 0.1s ease;
}
/* Off/idle, mouse-over, and pressed each get a distinct, always-visible
   look — no opacity dimming, since that washed out the whole chip (not
   just the icon) and made the button nearly invisible at rest. */
.card-minimize-btn:hover, .card-minimize-btn:focus-visible{
  color:var(--neon-green); border-color:var(--neon-green); background:var(--bg-panel);
}
.card-minimize-btn:active{
  background:var(--neon-green); border-color:var(--neon-green); color:#04140a; transform:scale(0.92);
}

/* Slotted variant: joins a card's own existing header row (Spotify's
   playback header, Spotify Playlists' sort/view/refresh row) via
   [data-minimize-slot] instead of floating in the standalone .card-minimize-cap
   above the card, so it's sized/styled to sit naturally beside those
   cards' own dark, circular icon buttons rather than the default chip. */
.card-minimize-btn-slotted{
  width:26px; height:26px;
  border-radius:50%;
  margin-left:6px;
  background:rgba(255,255,255,0.08);
  border:1px solid rgba(255,255,255,0.16);
  color:#b3b3b3;
}
.card-minimize-btn-slotted:hover, .card-minimize-btn-slotted:focus-visible{
  background:rgba(29,185,84,0.25);
  border-color:#1db954;
  color:#1db954;
}
.card-minimize-btn-slotted:active{
  background:#1db954;
  border-color:#1db954;
  color:#0b1f10;
  transform:scale(0.92);
}
.spotify-minimize-slot, .sp-minimize-slot{
  display:inline-flex; align-items:center;
}

@keyframes card-restore-pulse{
  0%{ box-shadow:0 0 0 0 var(--neon-green-glow, rgba(79,140,255,0.4)); }
  100%{ box-shadow:0 0 0 14px rgba(79,140,255,0); }
}
.card-restore-flash{ animation:card-restore-pulse 0.9s ease-out; }

/* The toolbar itself: hidden entirely (via inline display:none, toggled by
   js/icon-toolbar.js) whenever nothing is minimized, so it never sits
   around as dead chrome. Lives inside the fixed #appTopStack (see
   index.html and the "App top stack" rule above) rather than in the
   dashboard's own scrolling flow, so it — and the menu button it shares
   a row with — stay reachable while scrolling a long dashboard, matching
   the "always there" feel of an OS quick-access tray. position:relative
   only to anchor the ::after sheen overlay below. */
.icon-toolbar{
  position:relative;
  margin:0;
  padding:0;
  background:linear-gradient(180deg, #1c1c1c 0%, #0a0a0a 100%);
  border:1px solid rgba(255,255,255,0.08);
  border-radius:var(--radius-lg);
  /* Drop shadow (lifts it off the page), inner top highlight (the "sheen"),
     and a soft accent-colored inner glow that tints with the active theme
     (var(--neon-green-glow) is the theme's own accent-glow token, already
     used for e.g. the settings toggle's on-state) — deliberately fixed,
     not tied to Settings > Appearance > Card Background's user-adjustable
     glow/sheen sliders, since this bar is a distinct black dock chrome
     rather than a themable card surface. */
  box-shadow:
    0 10px 28px rgba(0, 0, 0, 0.55),
    inset 0 1px 0 rgba(255, 255, 255, 0.1),
    inset 0 0 32px var(--neon-green-glow, rgba(79, 140, 255, 0.22));
  overflow:hidden;
}
.icon-toolbar::after{
  content:"";
  position:absolute; inset:0;
  border-radius:inherit;
  background:linear-gradient(180deg, rgba(255,255,255,0.14) 0%, rgba(255,255,255,0.03) 35%, transparent 65%);
  pointer-events:none;
}
.icon-toolbar-grid{
  display:grid;
  grid-template-columns:repeat(var(--toolbar-cols, 6), var(--toolbar-icon-size, 48px));
  gap:10px;
}
.icon-toolbar-item{
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:3px;
  width:var(--toolbar-icon-size, 48px); height:var(--toolbar-icon-size, 48px);
  padding:4px 2px;
  /* Fixed (not theme-token) tile styling — the bar itself is always black
     regardless of the active theme, so a tile using var(--bg-panel) could
     land anywhere from white to near-black and disappear into it. A pale
     translucent glass tile reads clearly against black in every theme. */
  background:rgba(255, 255, 255, 0.07);
  border:1px solid rgba(255, 255, 255, 0.14);
  border-radius:12px;
  color:#fff;
  cursor:pointer;
  touch-action:none;
  transition:background 0.15s ease, border-color 0.15s ease, transform 0.1s ease;
}
.icon-toolbar-item:hover{ background:rgba(255, 255, 255, 0.12); border-color:rgba(255, 255, 255, 0.22); }
.icon-toolbar-item:active{ transform:scale(0.94); }
.icon-toolbar-item-glyph{
  font-size:calc(var(--toolbar-icon-size, 48px) * 0.4);
  line-height:1;
}
.icon-toolbar-item-label{
  font-family:var(--font-display); font-size:9px; letter-spacing:0.02em; text-transform:uppercase;
  color:rgba(255, 255, 255, 0.6);
  max-width:100%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.icon-toolbar-grid.no-label .icon-toolbar-item-label{ display:none; }

/* ---------- dashboard carousel row (js/carousel.js) ---------- */
.layout-row.carousel-row{ display:block; }
.rr-carousel{ display:flex; flex-direction:column; gap:6px; }
/* position:relative anchors the arrow overlays below — .rr-carousel-viewport
   is main's only flex child (the arrows are position:absolute, out of flow),
   so it naturally fills the row's true full width, same as a lone card. */
.rr-carousel-main{
  display:flex; align-items:stretch;
  position:relative;
}
/* The only part that clips/scrolls. Arrows are NOT inside it (they're
   siblings, absolutely positioned over its left/right edges) — keeping
   them out of the overflow:hidden subtree means they're never clipped by
   it, and keeping them small + centered (below) keeps them clear of a
   card's own corner-anchored content (e.g. Weather's precip-toggle). */
.rr-carousel-viewport{
  flex:1; min-width:0; overflow:hidden;
  border-radius:var(--radius-lg);
}
.rr-carousel-track{
  display:flex; align-items:stretch;
  touch-action:pan-y;
}
.rr-carousel-slide{
  /* Actual width/flex-basis is set inline by js/carousel.js off the
     viewport's real width — this is just a same-render fallback before
     that JS runs. */
  flex:0 0 100%;
  width:100%;
  min-width:0;
  box-sizing:border-box;
}
/* .rr-carousel-track's align-items:stretch already gives every slide the
   SAME height (the tallest card in the group) — but that only stretches
   the slide box itself, not the card inside it. Without height:100% here,
   a shorter card (e.g. Weather next to a taller Forecast) rendered at its
   own natural, shorter height, leaving a visible blank gap below it
   inside the equally-tall slide. height:100% makes every card's own box
   (background/border included, since .rr-card has overflow:hidden) fill
   that full slide height instead, so every slide in the group reads as
   the same size with no dead space, regardless of which one's showing. */
.rr-carousel-slide > .rr-card{ width:100%; height:100%; }
/* The button itself is the full glove-friendly hit target (--touch,
   48px) — transparent and borderless, so it doesn't visually read as a
   40px+ bar cutting into the card. What you actually SEE is the smaller
   .rr-carousel-arrow-dot centered inside it: a semi-transparent circle
   with a blurred backdrop, inset a few px from the viewport edge and
   vertically centered on the row so it stays clear of top/bottom
   corner-anchored card content while the tappable area stays full-size. */
.rr-carousel-arrow{
  position:absolute; top:50%; transform:translateY(-50%);
  z-index:2;
  width:var(--touch); height:var(--touch);
  display:flex; align-items:center; justify-content:center;
  background:transparent; border:none; padding:0; margin:0;
  cursor:pointer;
  -webkit-tap-highlight-color:transparent;
}
.rr-carousel-prev{ left:2px; }
.rr-carousel-next{ right:2px; }
.rr-carousel-arrow-dot{
  width:30px; height:30px;
  border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  background:var(--bg-panel-raised);
  border:1px solid var(--border-subtle);
  color:var(--text-primary);
  font-size:17px; line-height:1;
  opacity:0.75;
  backdrop-filter:blur(6px);
  -webkit-backdrop-filter:blur(6px);
  box-shadow:0 1px 6px rgba(0, 0, 0, 0.25);
  transition:opacity 0.2s ease, background 0.2s ease;
}
.rr-carousel-arrow:hover .rr-carousel-arrow-dot,
.rr-carousel-arrow:active .rr-carousel-arrow-dot{
  opacity:1; background:var(--bg-panel);
}
.rr-carousel-dots{
  display:flex; justify-content:center; align-items:center; gap:6px;
}
.rr-carousel-dot{
  width:6px; height:6px; border-radius:50%; border:none; padding:0;
  background:var(--border-strong);
  cursor:pointer;
  transition:width 0.2s ease, border-radius 0.2s ease, background 0.2s ease;
}
.rr-carousel-dot.active{ width:16px; border-radius:3px; background:var(--neon-green); }

/* ---------- screensaver ---------- */
/* True black (not the app's usual near-black void) so an OLED panel can
   turn those pixels fully off, plus a near-static DOM updated on a slow
   1s interval instead of every GPS tick — this is deliberately the
   cheapest screen a phone can show while still lit for GPS recording. No
   gradients, shadows, or animation here on purpose. */
.screensaver-modal{
  position:fixed; inset:0; z-index:200;
  background:#000;
  display:flex; align-items:center; justify-content:center;
  cursor:pointer;
  -webkit-tap-highlight-color:transparent;
}
.screensaver-topbar{
  position:absolute; top:0; left:0; right:0;
  display:flex; align-items:center; justify-content:center;
  gap:14px;
  padding:22px 16px 0;
}
.screensaver-clock{
  font-family:var(--font-mono);
  font-weight:700;
  font-size:22px;
  color:#fff;
  letter-spacing:0.04em;
}
.screensaver-rec{
  display:none; align-items:center; gap:6px;
  position:absolute; right:20px; top:22px;
}
.screensaver-rec.recording,
.screensaver-rec.paused{ display:flex; }
.screensaver-rec .rec-dot{
  width:9px; height:9px; border-radius:50%;
  background:var(--alert-red);
  animation:recPulse 1.2s infinite ease-in-out;
}
.screensaver-rec.paused .rec-dot{ background:var(--warn-amber); animation:none; }
.screensaver-rec-label{
  font-family:var(--font-display);
  font-weight:700; font-size:11px; letter-spacing:0.08em;
  text-transform:uppercase;
  color:var(--alert-red);
}
.screensaver-rec.paused .screensaver-rec-label{ color:var(--warn-amber); }

.screensaver-alert{
  display:none; align-items:center; gap:8px;
  position:absolute; top:64px; left:50%; transform:translateX(-50%);
  max-width:min(86vw, 480px);
  background:rgba(239,68,68,0.18);
  border:1px solid rgba(239,68,68,0.5);
  color:#fff;
  padding:8px 16px;
  border-radius:999px;
  font-family:var(--font-body);
  font-weight:500;
  font-size:12.5px;
  cursor:pointer;
  -webkit-tap-highlight-color:transparent;
}
.screensaver-alert.show{ display:flex; }
.screensaver-alert-icon{ font-size:15px; flex-shrink:0; }
.screensaver-alert-text{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

.screensaver-widgets{
  display:flex; flex-direction:column; align-items:center; gap:10px;
}
.screensaver-widget{ text-align:center; }
.screensaver-value{
  font-family:var(--font-mono);
  font-weight:700;
  font-size:min(30vw, 220px);
  line-height:1;
  color:#fff;
}
.screensaver-value-sm{ font-size:min(14vw, 90px); }
.screensaver-label{
  font-family:var(--font-display);
  font-size:16px;
  letter-spacing:0.3em;
  color:#7a7a7a;
  margin-top:8px;
  text-transform:uppercase;
  overflow:hidden;
  white-space:nowrap;
  max-width:min(80vw, 320px);
  margin-inline:auto;
}
.screensaver-label.scrolling-label,
.screensaver-value.scrolling-label,
.screensaver-spotify-time.scrolling-label{
  display:block;
  overflow:hidden;
  white-space:nowrap;
}
.scrolling-label-track{
  display:inline-block;
  padding-right:12px;
  white-space:nowrap;
  will-change:transform;
}
.scrolling-label .scrolling-label-track{
  animation: screensaver-scroll 7.5s ease-in-out infinite;
  animation-delay: 1.2s;
}
@keyframes screensaver-scroll {
  0%, 18% { transform: translateX(0); }
  82%, 100% { transform: translateX(calc(-50% - 6px)); }
}
.screensaver-hint{
  position:absolute; bottom:26px; left:0; right:0;
  text-align:center;
  font-family:var(--font-body);
  font-size:12px;
  color:#3a3a3a;
}
.screensaver-widget-check{
  display:flex; align-items:center; gap:10px;
  font-family:var(--font-body); font-size:14px; color:var(--text-primary);
  padding:8px 0;
  cursor:pointer;
}
.screensaver-widget-check input{ width:18px; height:18px; accent-color:var(--neon-green); cursor:pointer; }

/* ---------- top alert banner (precip along route / weather watches) ---------- */
.top-banner{
  position:fixed; top:0; left:0; right:0; z-index:150;
  display:flex; align-items:center; gap:10px;
  padding:12px 16px;
  background:linear-gradient(180deg, rgba(255,59,92,0.95), rgba(190,28,58,0.95));
  color:#fff;
  font-family:var(--font-display);
  font-size:14px;
  cursor:pointer;
  box-shadow:0 4px 16px rgba(0,0,0,0.35);
}
.top-banner-icon{ font-size:18px; flex-shrink:0; }
.top-banner-text{ flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.top-banner-more{ font-family:var(--font-mono); font-size:11px; opacity:0.85; flex-shrink:0; }
.top-banner-close{ font-family:var(--font-body); font-size:11px; opacity:0.75; flex-shrink:0; }

/* ---------- Settings > Speedometer ---------- */
.speedo-style-toggle{ display:flex; gap:8px; }
.speedo-style-btn{
  flex:1; min-height:44px;
  border:1px solid var(--border-subtle);
  background:var(--bg-panel-raised);
  border-radius:var(--radius-md);
  color:var(--text-muted);
  font-family:var(--font-display); font-weight:600; font-size:14px;
  cursor:pointer;
}
.speedo-style-btn.active{
  border-color:var(--neon-green-dim);
  color:var(--neon-green);
  background:rgba(79,140,255,0.08);
}
/* Same visual pattern as .speedo-style-btn (theme-aware via CSS custom
   properties, so no separate light/dark override rules are needed) but a
   dedicated class — deliberately NOT sharing .speedo-style-btn, which
   would also pull in that button's own unrelated click handler (dashboard
   speedometer style, not Track HUD) since that's wired to every element
   matching the class globally, not scoped to its own tab. */
.trackhud-settings-style-toggle{ display:flex; gap:8px; }
.trackhud-settings-style-btn{
  flex:1; min-height:44px;
  border:1px solid var(--border-subtle);
  background:var(--bg-panel-raised);
  border-radius:var(--radius-md);
  color:var(--text-muted);
  font-family:var(--font-display); font-weight:600; font-size:14px;
  cursor:pointer;
}
.trackhud-settings-style-btn.active{
  border-color:var(--neon-green-dim);
  color:var(--neon-green);
  background:rgba(79,140,255,0.08);
}
.spotify-art-style-btn{
  flex:1; min-height:44px;
  border:1px solid var(--border-subtle);
  background:var(--bg-panel-raised);
  border-radius:var(--radius-md);
  color:var(--text-muted);
  font-family:var(--font-display); font-weight:600; font-size:14px;
  cursor:pointer;
}
.spotify-art-style-btn.active{
  border-color:var(--neon-green-dim);
  color:var(--neon-green);
  background:rgba(79,140,255,0.08);
}
.speedo-preview{
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  position:relative;
  background:var(--speedo-bg, radial-gradient(circle at 50% 30%, var(--bg-panel-raised), var(--bg-panel) 70%));
  border:1px solid var(--border-subtle);
  border-radius:var(--radius-lg);
  padding:20px;
  margin-bottom:6px;
}
.speedo-preview svg{ width:160px; height:160px; }
.speedo-preview.style-digital svg{ display:none; }
.speedo-preview.style-digital .speedo-preview-overlay{
  position:static;
  transform:none;
}
.speedo-preview-overlay{
  position:absolute; top:50%; left:50%;
  transform:translate(-50%,-50%);
  text-align:center; pointer-events:none;
}
.speedo-preview-value{
  font-family:var(--speedo-font, var(--font-mono));
  font-weight:700;
  font-size:var(--speedo-size-preview, 40px);
  color:var(--speedo-fg, var(--neon-green));
  text-shadow:0 0 24px var(--speedo-fg-glow, var(--neon-green-glow));
  line-height:1;
}
.color-picker-row{ display:flex; gap:16px; }
.color-picker-row label{
  flex:1; display:flex; flex-direction:column; gap:6px;
  font-family:var(--font-body); font-size:13px; color:var(--text-muted);
}
.color-picker-row input[type=color]{
  width:100%; height:44px;
  border:1px solid var(--border-subtle); border-radius:8px;
  background:var(--bg-void); cursor:pointer; padding:2px;
}
.color-picker-row input[type=color]:disabled{ opacity:0.35; cursor:not-allowed; }
.transparent-check{
  display:flex; align-items:center; gap:8px;
  font-family:var(--font-body); font-size:13px; color:var(--text-muted);
  margin-top:8px; cursor:pointer;
}
.transparent-check input{ width:16px; height:16px; accent-color:var(--neon-green); cursor:pointer; }

/* ---------- speedometer session stats (max/avg speed while recording) ---------- */
.speedo-session-stats{
  display:grid; grid-template-columns:1fr 1fr; gap:8px;
  margin-top:10px;
}
.speedo-stat{
  background:var(--bg-panel-raised);
  border:1px solid var(--border-subtle);
  border-radius:var(--radius-md);
  padding:10px 6px;
  text-align:center;
}
.speedo-stat .v{ font-family:var(--font-mono); font-size:20px; font-weight:700; color:var(--neon-green); }
.speedo-stat .k{
  font-family:var(--font-display); font-size:10px; letter-spacing:0.1em;
  color:var(--text-dim); text-transform:uppercase; margin-top:2px;
}

/* ---------- plugin cards ---------- */
.plugin-card{ position:relative; display:block; }
.plugin-shell{
  position:relative;
  background:var(--card-custom-bg, var(--bg-panel));
  border:1px solid var(--border-subtle);
  border-radius:20px;
  padding:12px;
  min-height:170px;
  display:flex; flex-direction:column;
  gap:10px;
}
.plugin-head{ display:flex; align-items:center; justify-content:space-between; gap:10px; }
.plugin-head-copy{ display:flex; align-items:center; gap:8px; min-width:0; }
.plugin-icon{ width:34px; height:34px; border-radius:10px; display:grid; place-items:center; background:rgba(79,140,255,0.14); color:var(--neon-green); font-size:18px; flex-shrink:0; }
.plugin-title{ font-family:var(--font-display); font-weight:700; font-size:14px; color:var(--text-primary); }
.plugin-meta{ font-family:var(--font-body); font-size:11px; color:var(--text-muted); }
.plugin-resize-grip{
  position:absolute; right:8px; bottom:8px;
  width:24px; height:24px; flex-shrink:0; cursor:ns-resize; touch-action:none;
  background:linear-gradient(135deg, transparent 50%, var(--text-dim) 50%, var(--text-dim) 60%, transparent 60%, transparent 68%, var(--text-dim) 68%, var(--text-dim) 78%, transparent 78%);
  border-radius:6px;
}
.plugin-body{ flex:1; }

/* ---------- standalone plugins (own full-bleed card look, like the
   first-party Spotify controller — no generic icon/title bar, no generic
   panel background) — opted into via `standalone: true` on the plugin's
   runtime object; see js/plugins.js renderPluginCard(). The plugin's own
   render() fills .plugin-body-standalone edge to edge with its own
   header/background/everything. ---------- */
.plugin-shell.plugin-shell-standalone{
  padding:0;
  border:none;
  background:transparent;
  border-radius:20px;
  overflow:hidden;
}
/* isolation:isolate is load-bearing, not decorative: position:relative
   alone does NOT create a new stacking context, so a standalone plugin's
   own internal z-index (layering its content above its own decorative
   background, say) would otherwise escape .plugin-body-standalone
   entirely and could out-rank .plugin-resize-grip's sibling z-index:auto
   — found for real building the Hourly Precipitation plugin, where its
   z-index:2 content layer silently sat on top of the grip and ate its
   clicks. Isolating here contains any z-index a plugin uses internally,
   so every future standalone plugin gets this for free. */
.plugin-body.plugin-body-standalone{ flex:1; height:100%; position:relative; isolation:isolate; }
/* The default grip's stripe pattern has no base fill, so it only reads
   against .plugin-shell's own light/dark panel background — a standalone
   plugin can paint anything behind it (dark gradients, photos, video), so
   the grip needs its own guaranteed-visible backing rather than trusting
   whatever's underneath. */
.plugin-standalone .plugin-resize-grip{
  background-color:rgba(0,0,0,0.35);
  background-image:linear-gradient(135deg, transparent 50%, rgba(255,255,255,0.55) 50%, rgba(255,255,255,0.55) 60%, transparent 60%, transparent 68%, rgba(255,255,255,0.55) 68%, rgba(255,255,255,0.55) 78%, transparent 78%);
}

/* ---------- Hourly Precipitation plugin (standalone) ----------
   Background shows CURRENT conditions at the rider's location — exactly
   one of sun-or-moon/cloud/rain "on" at a time — mounted by
   js/bg-animator.js's RydRBgAnimator (see that file, and the "Reusable
   Decorative Backgrounds" section of skills/rydr-plugin-development/
   SKILL.md) as this root's first child: .hp-bg (z-index:0, via the
   library's own injected .rydr-bga-root rule) sits behind .hp-head/
   .hp-chart (z-index:2, the actual data — always the focal point, per
   design brief). Layer order bottom to top is sky -> sun/moon -> clouds
   -> rain -> scrim.

   Sun and moon share one layer (see hourly-precip.js's sunLayer/
   sunMoonState()): --amt (0 or 1, from classifyCondition() === "sun")
   gates the whole system on/off exactly like the other two layers used
   to alone; --fade (0..1, per wrap, from sunMoonState()'s dawn/dusk
   cross-fade math) is what actually cross-fades sun into moon and back.
   left/top on each wrap is the REAL time-of-day position (JS-computed,
   set once per 15-minute refresh from sys.sunrise/sunset) — the
   continuous "moving across the screen" drift on top of that anchor is
   100% CSS `@keyframes`, and by design only ever animates `transform`,
   never `left`/`top`/`opacity`: those stay purely JS-computed statics
   (left/top) or purely calc()-derived (opacity), so nothing here can
   collide the way hp-rainfall's opacity keyframe once did (see that
   rule's own comment below for the exact failure mode this avoids).

   Clouds get a real right-to-left cross-screen drift (hp-cloud-cross,
   transform-only, staggered per blob) replacing the old in-place sway.

   --rydr-bga-opacity (the library's one intensity knob, wired to this
   plugin's own settings tab) is what every layer below multiplies its
   opacity by, so "soft and semi-transparent" is still one knob, not
   four. */
.hp-root{
  position:relative;
  height:100%; width:100%;
  min-height:calc(200px * var(--plugin-scale, 1));
  border-radius:inherit;
  overflow:hidden;
  background:var(--bg-panel-raised);
  display:flex; flex-direction:column;
  padding:calc(14px * var(--plugin-scale, 1));
}

/* Every multiplier below was tuned by actually computing WCAG contrast
   ratios (text color vs. the composited result of panel + this layer +
   the bottom scrim, at max realistic intensity) for both themes, not
   just eyeballed — see the light-theme note further down for the
   visibility-side problem this app already hit once. Dark theme turned
   out to have the OPPOSITE problem: a bright warm sun/cloud wash over
   the dark panel *reduces* contrast for the light --text-muted bar-time
   labels (it lightens the local background, closing the gap to light
   text), measured as low as ~4.0:1 against the sunny stretch at default
   intensity — under the 4.5:1 AA text threshold. Dark theme's own
   (unprefixed / default) multipliers below are deliberately lower than a
   naive "1x" to keep that comfortably above 4.5:1; light theme's
   multipliers stay boosted for the pale-panel visibility problem, which
   these same colors don't have (measured comfortably >5:1 there). */
.hp-bg-sky{ position:absolute; inset:0; opacity:calc(var(--rydr-bga-opacity) * 0.6); transition:background 0.3s linear; }
:root[data-theme="light"] .hp-bg-sky, .theme-light .hp-bg-sky{ opacity:calc(var(--rydr-bga-opacity) * 1.7); }

/* z-index:1, explicitly below the cloud layer's z-index:2 — on a
   "partly cloudy" day both the sun/moon AND the clouds are visible at
   once (see sunLayer/cloudLayer's layout() in hourly-precip.js), the
   first time these two layers have ever needed to actually stack
   correctly against each other rather than being drawn mutually
   exclusive — the clouds must pass IN FRONT OF the sun/moon, not behind
   it, so this can't be left to implicit DOM-order stacking alone. */
.hp-bg-sun-layer{ position:absolute; inset:0; z-index:1; }

/* .hp-sun-wrap/.hp-moon-wrap are pure position anchors — left/top are set
   directly by sunLayer.layout() (the real time-of-day position) and
   NEVER touched by a keyframe; their `transform` carries centering (this
   element's own size) PLUS the continuous decorative drift/pulse, both
   combined in one @keyframes rule (hp-sun-drift/hp-moon-drift) so the two
   never fight over the same property. --fade (0..1, set per wrap by
   layout()) is the dawn/dusk cross-fade amount; --amt (0 or 1, inherited
   from .hp-bg-sun-layer) is the whole-system on/off gate. Both multiply
   into a plain opacity:calc(...) on the CHILD (.hp-sun-core/.hp-moon-core
   etc, not the wrap) so the wrap's own transform-only animation can never
   collide with opacity gating either. */
.hp-sun-wrap, .hp-moon-wrap{
  position:absolute;
  width:min(calc(60px * var(--plugin-scale, 1)), 30%); height:min(calc(60px * var(--plugin-scale, 1)), 30%);
  transition:left 0.4s linear, top 0.4s linear;
}
.hp-sun-wrap{ animation:hp-sun-drift 150s linear infinite; }
.hp-moon-wrap{ animation:hp-moon-drift 170s ease-in-out infinite; }
/* The sun arcs specifically RIGHTWARD (opposite hp-cloud-cross's
   leftward sweep, so the two never read as the same generic side-to-side
   wobble) — a long, linear rightward drift for nearly the entire cycle
   (0%->97%), then a brief eased reset back to the start over the last
   3% rather than an instant snap. The reset window is deliberately short
   (a few seconds out of 150) so the sun reads as continuously moving
   right for practically the whole time it's watched, rather than an
   earlier version's 85%->100% reset, which spent long enough moving
   backward to read as ambiguous or even leftward over a short viewing
   window. This is layered on top of the wrap's real left/top
   anchor (the actual time-of-day position, set by sunLayer.layout() and
   never touched here), not a replacement for it — a real ~12-hour solar
   transit is imperceptible frame-to-frame, so this is a deliberately
   stylized, much-slower-than-realtime loop, not literal solar speed. The
   moon keeps the older side-to-side sweep (not given the same rightward
   treatment — the two are never visible at once, so there's nothing to
   visually reconcile between them, and the user's ask was sun-specific).
   Percent-of-own-size transforms would make this drift trivially small
   on a small blob, so the horizontal sweep uses `vw` (a real, container-
   size-independent distance) instead. */
@keyframes hp-sun-drift{
  0%{ transform:translate(-50%,-50%) translateX(-4.5vw) scale(1); animation-timing-function:linear; }
  97%{ transform:translate(-50%,-50%) translateX(4.5vw) scale(1.06); animation-timing-function:ease-in-out; }
  100%{ transform:translate(-50%,-50%) translateX(-4.5vw) scale(1); }
}
@keyframes hp-moon-drift{
  0%{ transform:translate(-50%,-50%) translateX(-3.2vw) scale(1); }
  50%{ transform:translate(-50%,-50%) translateX(3.2vw) scale(1.04); }
  100%{ transform:translate(-50%,-50%) translateX(-3.2vw) scale(1); }
}

/* ---- sun "image": layered radial gradients for a warm core + a soft
   corona, plus a faint conic-gradient ray flare behind it — reads as an
   actual sun disc, not a blurred glow, while staying 100% CSS (this
   plugin's background system has never used an image asset, and a
   crafted gradient stack scales/recolors per-theme for free where a
   bitmap wouldn't). .hp-sun-rays sits behind .hp-sun-core (DOM order) at
   a larger size so its spokes peek out past the disc's edge. */
.hp-sun-core{
  position:absolute; inset:0;
  border-radius:50%;
  background:
    radial-gradient(circle at 38% 32%, rgba(255,255,255,0.95) 0%, rgba(255,241,199,0.9) 14%, transparent 26%),
    radial-gradient(circle at 50% 50%, #fff3cf 0%, #ffdb8a 30%, #ffb347 58%, rgba(255,140,60,0.55) 78%, transparent 92%);
  /* Layered glow — a tight bright rim plus two progressively softer,
     wider halos — reads as actual light radiating off the disc rather
     than a single flat blur. animation only ever touches box-shadow
     here, never opacity/left/top (see this file's header comment on why
     that split matters), so it layers cleanly on top of the --amt/--fade
     opacity gating and the wrap's own position/drift transform without
     either fighting it. */
  box-shadow:
    0 0 calc(10px * var(--plugin-scale, 1)) rgba(255,244,214,0.85),
    0 0 calc(24px * var(--plugin-scale, 1)) rgba(255,178,80,0.6),
    0 0 calc(46px * var(--plugin-scale, 1)) rgba(255,140,60,0.3);
  opacity:calc(var(--rydr-bga-opacity) * var(--amt, 0) * var(--fade, 1) * 1);
  transition:opacity 0.6s linear;
  animation:hp-sun-shine 4s ease-in-out infinite;
}
@keyframes hp-sun-shine{
  0%, 100%{
    box-shadow:
      0 0 calc(10px * var(--plugin-scale, 1)) rgba(255,244,214,0.85),
      0 0 calc(24px * var(--plugin-scale, 1)) rgba(255,178,80,0.6),
      0 0 calc(46px * var(--plugin-scale, 1)) rgba(255,140,60,0.3);
  }
  50%{
    box-shadow:
      0 0 calc(14px * var(--plugin-scale, 1)) rgba(255,244,214,0.95),
      0 0 calc(32px * var(--plugin-scale, 1)) rgba(255,178,80,0.75),
      0 0 calc(60px * var(--plugin-scale, 1)) rgba(255,140,60,0.4);
  }
}
.hp-sun-rays{
  position:absolute;
  inset:calc(-42% * var(--plugin-scale, 1));
  border-radius:50%;
  background:repeating-conic-gradient(from 0deg, rgba(255,205,120,0.35) 0deg 5deg, transparent 5deg 30deg);
  opacity:calc(var(--rydr-bga-opacity) * var(--amt, 0) * var(--fade, 1) * 0.7);
  transition:opacity 0.6s linear;
  filter:blur(0.5px);
}
:root[data-theme="light"] .hp-sun-core, .theme-light .hp-sun-core{ opacity:calc(var(--rydr-bga-opacity) * var(--amt, 0) * var(--fade, 1) * 2.4); }
:root[data-theme="light"] .hp-sun-rays, .theme-light .hp-sun-rays{ opacity:calc(var(--rydr-bga-opacity) * var(--amt, 0) * var(--fade, 1) * 1.6); }

/* ---- moon "image": a cooler, desaturated disc with a bright limb, plus
   inset box-shadows standing in for crater shading (soft dark hollows on
   the lower-right, a bright highlight upper-left, as if lit from one
   side) and a faint pale glow behind it, mirroring the sun's
   wrap/glow/core structure so the two read as a matched pair. */
.hp-moon-core{
  position:absolute; inset:0;
  border-radius:50%;
  background:
    radial-gradient(circle at 32% 28%, rgba(255,255,255,0.55), transparent 22%),
    radial-gradient(circle at 62% 66%, rgba(140,150,172,0.45), transparent 30%),
    radial-gradient(circle at 50% 50%, #f4f6fb 0%, #dfe4ee 42%, #c3cadb 74%, #aab2c6 92%);
  /* Crater shading (inset shadows) stays as-is; the outer, non-inset
     layers are the moon's own soft "shining" rim — cooler and dimmer
     than the sun's, on top of (not instead of) .hp-moon-glow's wider
     ambient halo below. */
  box-shadow:
    inset calc(-8px * var(--plugin-scale, 1)) calc(-6px * var(--plugin-scale, 1)) calc(12px * var(--plugin-scale, 1)) rgba(85,95,120,0.4),
    inset calc(5px * var(--plugin-scale, 1)) calc(6px * var(--plugin-scale, 1)) calc(9px * var(--plugin-scale, 1)) rgba(255,255,255,0.35),
    0 0 calc(8px * var(--plugin-scale, 1)) rgba(225,232,248,0.65),
    0 0 calc(20px * var(--plugin-scale, 1)) rgba(190,202,232,0.4);
  opacity:calc(var(--rydr-bga-opacity) * var(--amt, 0) * var(--fade, 1) * 1);
  transition:opacity 0.6s linear;
  animation:hp-moon-shine 6s ease-in-out infinite;
}
@keyframes hp-moon-shine{
  0%, 100%{
    box-shadow:
      inset calc(-8px * var(--plugin-scale, 1)) calc(-6px * var(--plugin-scale, 1)) calc(12px * var(--plugin-scale, 1)) rgba(85,95,120,0.4),
      inset calc(5px * var(--plugin-scale, 1)) calc(6px * var(--plugin-scale, 1)) calc(9px * var(--plugin-scale, 1)) rgba(255,255,255,0.35),
      0 0 calc(8px * var(--plugin-scale, 1)) rgba(225,232,248,0.65),
      0 0 calc(20px * var(--plugin-scale, 1)) rgba(190,202,232,0.4);
  }
  50%{
    box-shadow:
      inset calc(-8px * var(--plugin-scale, 1)) calc(-6px * var(--plugin-scale, 1)) calc(12px * var(--plugin-scale, 1)) rgba(85,95,120,0.4),
      inset calc(5px * var(--plugin-scale, 1)) calc(6px * var(--plugin-scale, 1)) calc(9px * var(--plugin-scale, 1)) rgba(255,255,255,0.35),
      0 0 calc(11px * var(--plugin-scale, 1)) rgba(225,232,248,0.8),
      0 0 calc(28px * var(--plugin-scale, 1)) rgba(190,202,232,0.5);
  }
}
.hp-moon-glow{
  position:absolute;
  inset:calc(-24% * var(--plugin-scale, 1));
  border-radius:50%;
  background:radial-gradient(circle, rgba(210,220,240,0.4) 0%, transparent 70%);
  opacity:calc(var(--rydr-bga-opacity) * var(--amt, 0) * var(--fade, 1) * 0.8);
  transition:opacity 0.6s linear;
  filter:blur(1px);
}
:root[data-theme="light"] .hp-moon-core, .theme-light .hp-moon-core{ opacity:calc(var(--rydr-bga-opacity) * var(--amt, 0) * var(--fade, 1) * 2.2); }
:root[data-theme="light"] .hp-moon-glow, .theme-light .hp-moon-glow{ opacity:calc(var(--rydr-bga-opacity) * var(--amt, 0) * var(--fade, 1) * 1.5); }

/* Ambient cloud cover — real cloud ICONS (a ☁️ glyph via ::before, same
   convention the bar chart's own per-hour icons already use — see
   renderChart()'s icon variable) drifting at their own rest `top`/`left`
   anchor (per class, below), not the earlier blurred-ellipse "glow blob"
   look. Only their shared --amt (set on .hp-bg-cloud-layer itself by
   cloudLayer.layout(), inherited down to every .hp-cloud-blob child
   since custom properties inherit) turns them on/off — a plain 0 or 1,
   not a continuous amount. The user explicitly asked for right-to-left
   motion, so hp-cloud-cross sweeps `transform: translateX` from a
   positive to a negative offset (never the reverse, so no `alternate` —
   that would put half of every cycle moving left-to-right) — `vw`, not a
   percentage of the blob's own small size, so the sweep is a real,
   visible, container-size-independent distance. Each blob uses a
   different `animation-delay` (not just a different duration, like the
   old sway) so the five don't move in lockstep — a staggered "conveyor"
   of puffs re-entering from the right as others exit left, reading
   better than one big synchronized clump. Durations are deliberately
   slow (a full crossing takes well over a minute) — ambient background
   motion, not something that should visibly race across the card.
   z-index:2 (see .hp-bg-sun-layer's z-index:1) puts every cloud in front
   of the sun/moon on purpose: on a "partly cloudy" day both layers are
   visible together (cloudLayer.layout()/sunLayer.layout() in
   hourly-precip.js both turn on for condition "cloud" now), and the
   clouds need to actually pass IN FRONT of the sun as they cross it, not
   behind it. The five rest `top%` bands below are deliberately weighted
   toward the lower half of the card (44-64%, vs. the sun's own real-time
   arc range of roughly 20-62% — see arcPosition() in hourly-precip.js) —
   at a high midday sun (y~20-35%) the sun sits clearly above every
   cloud's band, and only as the sun's real position descends toward the
   horizon near sunrise/sunset does it start to reach into/pass behind
   the cloud band, which is the "sun's top third stays above the clouds
   as they cross" effect that was asked for: whenever they do overlap,
   the sun's disc (its own size, not just its center point) still has
   room to have its upper portion clear the top of a cloud passing near
   the bottom of the sun's own footprint, rather than being fully behind
   it. */
.hp-bg-cloud-layer{ position:absolute; inset:0; z-index:2; }
.hp-cloud-blob{
  position:absolute;
  width:min(calc(58px * var(--plugin-scale, 1)), 32%); height:min(calc(38px * var(--plugin-scale, 1)), 20%);
  display:flex; align-items:center; justify-content:center;
  font-size:min(calc(46px * var(--plugin-scale, 1)), 24cqw);
  line-height:1;
  filter:drop-shadow(0 1px 3px rgba(0,0,0,0.25));
  opacity:calc(var(--rydr-bga-opacity) * var(--amt, 0) * 0.8);
  transition:opacity 0.3s linear;
  animation-name:hp-cloud-cross;
  animation-timing-function:linear;
  animation-iteration-count:infinite;
}
.hp-cloud-blob::before{ content:"☁️"; }
:root[data-theme="light"] .hp-cloud-blob, .theme-light .hp-cloud-blob{ opacity:calc(var(--rydr-bga-opacity) * var(--amt, 0) * 2.1); }
.hp-cloud-blob-0{ top:46%; left:14%; animation-duration:78s; animation-delay:0s; }
.hp-cloud-blob-1{ top:58%; left:32%; animation-duration:92s; animation-delay:-18s; }
.hp-cloud-blob-2{ top:44%; left:52%; animation-duration:70s; animation-delay:-38s; }
.hp-cloud-blob-3{ top:64%; left:68%; animation-duration:86s; animation-delay:-55s; }
.hp-cloud-blob-4{ top:50%; left:86%; animation-duration:75s; animation-delay:-66s; }
/* transform only — NOT opacity, NOT left/top — same reasoning as
   hp-rainfall below: a running keyframe's own per-frame value for a
   property always wins over any separately-computed value for that same
   property, so opacity gating (opacity:calc(...) above) has to stay off
   this animation's property list entirely. */
@keyframes hp-cloud-cross{
  from{ transform:translate(-50%,-50%) translateX(60vw); }
  to{ transform:translate(-50%,-50%) translateX(-60vw); }
}

/* Ambient rain — every .hp-drop inherits --amt from .hp-bg-rain-layer
   (set once by rainLayer.layout(), not per-drop): all 48 drops share the
   same on/off state, which is what makes rain read as a single condition
   covering the whole card, not a patchwork of independently-lit drops. */
.hp-bg-rain-layer{ position:absolute; inset:0; }
.hp-drop{
  position:absolute;
  top:-8%;
  width:calc(2px * var(--plugin-scale, 1)); height:9%;
  border-radius:2px;
  background:linear-gradient(180deg, transparent, rgba(146,190,255,0.9));
  opacity:calc(var(--rydr-bga-opacity) * var(--amt, 0) * 1.6);
  transition:opacity 0.3s linear;
  animation-name:hp-rainfall;
  animation-timing-function:linear;
  animation-iteration-count:infinite;
}
:root[data-theme="light"] .hp-drop, .theme-light .hp-drop{ opacity:calc(var(--rydr-bga-opacity) * var(--amt, 0) * 2.4); }
/* transform only — NOT opacity. A CSS animation's own keyframe values
   always win over the element's separately-computed `opacity:calc(...)`
   for the same property while the animation is running, which is exactly
   why an earlier version of this rule (animating opacity 0.9->0.4 here
   too, for a per-drop "fading as it falls" touch) made every drop stay
   faintly-to-fully visible forever regardless of --amt — rain looked
   "on" no matter which condition was actually active, since this
   animation-iteration-count:infinite animation never stops running. */
@keyframes hp-rainfall{
  from{ transform:translateY(0); }
  to{ transform:translateY(1150%); }
}

.hp-bg-scrim{
  position:absolute; inset:0;
  background:linear-gradient(180deg, transparent 0%, transparent 55%, rgba(0,0,0,0.16) 100%);
}
:root[data-theme="light"] .hp-bg-scrim, .theme-light .hp-bg-scrim{
  background:linear-gradient(180deg, transparent 0%, transparent 55%, rgba(255,255,255,0.35) 100%);
}

.hp-head{ position:relative; z-index:2; display:flex; align-items:center; justify-content:space-between; gap:calc(10px * var(--plugin-scale, 1)); margin-bottom:calc(10px * var(--plugin-scale, 1)); }
.hp-head-left{ display:flex; align-items:center; gap:calc(8px * var(--plugin-scale, 1)); min-width:0; }
.hp-head-icon{ font-size:calc(18px * var(--plugin-scale, 1)); line-height:1; }
.hp-head-title{ font-family:var(--font-display); font-weight:700; font-size:calc(14px * var(--plugin-scale, 1)); color:var(--text-primary); }
/* Current conditions AT the rider's own location (.hp-cur-cond) sits
   stacked above "Next N Hours" (.hp-head-sub), both right-aligned — top
   right of the card, entirely inside .hp-head's own fixed-height row.
   This is deliberate: .hp-head is NOT part of the flexible .hp-chart{
   flex:1 } area below it (see that rule's own comment for the squished-
   bars regression that came from getting that flex level wrong once
   already), so growing .hp-head by one small extra line here only ever
   costs .hp-chart a few fixed pixels of the card's total height, never
   competes with it for a share of flexible space the way a new
   full-width sibling row would. */
.hp-head-right{ display:flex; flex-direction:column; align-items:flex-end; gap:2px; flex-shrink:0; }
.hp-cur-cond{ font-family:var(--font-mono); font-weight:600; font-size:calc(12px * var(--plugin-scale, 1)); color:var(--text-primary); white-space:nowrap; }
.hp-head-sub{ font-family:var(--font-mono); font-size:calc(11px * var(--plugin-scale, 1)); color:var(--text-muted); flex-shrink:0; }

/* align-items:stretch (not flex-end) is load-bearing: .hp-bar-col relies
   on height:100% to size itself against .hp-chart, but a flex item's
   percentage height only reliably resolves against a *stretched* cross
   size — with flex-end (or any non-stretch value) that 100% collapsed to
   the column's own min-content height (~49px: icon+pct+time+gaps) in
   real Chromium measurements, not .hp-chart's actual ~144px, squeezing
   .hp-bar-track down to near 0px regardless of .hp-bar-fill's height%
   — bars looked "squished" because the track they were a percentage OF
   had almost no height to be a percentage of. .hp-bar-col's own
   justify-content:flex-end still keeps its content bottom-aligned inside
   its now-correctly-sized box, so bars still visually grow from the
   bottom exactly as before. min-height:0 on both nested flex columns is
   the standard belt-and-suspenders fix alongside it — a flex item's
   default min-height:auto can otherwise refuse to shrink below content
   size in other browsers/edge cases even once the sizing above is right. */
.hp-chart{ position:relative; z-index:2; flex:1; display:flex; align-items:stretch; gap:min(calc(8px * var(--plugin-scale, 1)), 1.6cqw); min-height:calc(110px * var(--plugin-scale, 1)); cursor:pointer; }
/* No explicit height here on purpose — align-items:stretch on .hp-chart
   is what fills each column to the container's real cross-size; an
   explicit height:100% here (tried first) opts the item OUT of stretch
   sizing entirely per spec (stretch only overrides an *auto* cross-size),
   which is exactly why that version still measured .hp-bar-col at its
   ~49px min-content height instead of .hp-chart's real ~144px. */
.hp-bar-col{ flex:1; min-width:0; min-height:0; display:flex; flex-direction:column; align-items:center; justify-content:flex-end; gap:calc(4px * var(--plugin-scale, 1)); }
.hp-bar-icon{ font-size:min(calc(13px * var(--plugin-scale, 1)), 4.6cqw); opacity:0.9; line-height:1; flex-shrink:0; }
.hp-bar-pct{ font-family:var(--font-mono); font-weight:700; font-size:min(calc(11px * var(--plugin-scale, 1)), 4cqw); color:var(--text-primary); flex-shrink:0; }
.hp-bar-track{
  width:100%; max-width:calc(28px * var(--plugin-scale, 1)); flex:1; min-height:0;
  display:flex; align-items:flex-end;
  background:rgba(127,140,160,0.18);
  border-radius:6px;
  overflow:hidden;
}
.hp-bar-fill{
  width:100%;
  background:linear-gradient(180deg, var(--neon-green), var(--neon-green-dim));
  border-radius:6px;
  transition:height 0.9s cubic-bezier(0.16,1,0.3,1);
  transition-delay:calc(var(--i, 0) * 70ms);
}
.hp-bar-time{ font-family:var(--font-mono); font-size:min(calc(10px * var(--plugin-scale, 1)), 3.6cqw); color:var(--text-muted); white-space:nowrap; }

.hp-status{
  position:relative; z-index:2; flex:1;
  display:flex; align-items:center; justify-content:center; text-align:center;
  color:var(--text-muted); font-size:13px; padding:20px;
}
/* .hp-chart/.hp-status both set their own `display` above, which — same
   specificity as the browser's built-in `[hidden]{display:none}` rule,
   author origin wins — silently defeated `.hidden = true/false` in JS:
   both stayed visibly rendered (each still claiming flex:1) regardless
   of the hidden attribute, splitting the card's height between an empty
   status box and a squeezed, near-invisible bar chart. This is what
   actually made the chart read as "not loading" — the bars were there,
   just fighting a phantom sibling for half their space. The extra
   [hidden] in the selector raises specificity enough to win for real. */
.hp-chart[hidden], .hp-status[hidden]{ display:none; }

/* No plugin-specific prefers-reduced-motion block needed here any more —
   js/bg-animator.js's RydRBgAnimator generically pauses every animation
   under its mounted root (and under [data-motion="reduced"]) for any
   consumer, this plugin included. See that file's injected CSS. */

.plugin-surface{
  height:100%; border-radius:16px; padding:12px; display:flex; flex-direction:column; gap:10px;
  background:radial-gradient(circle at top left, var(--neon-green-glow), transparent 40%), var(--bg-panel-raised);
  border:1px solid var(--border-subtle);
}
.plugin-surface.light{ background:linear-gradient(135deg, rgba(31,156,86,0.16), rgba(255,255,255,0.95)); color:#0f1a12; }
.plugin-stat-row{ display:flex; align-items:center; justify-content:space-between; gap:10px; }
/* Two label/value pairs each. space-between reads fine on a narrow card
   (where the two items already sit close to the edges anyway), but this
   same card can also end up full-width via the Layout editor — there,
   space-between flings the pairs to opposite corners of the row with a
   huge dead gap between them, breaking the "these four stats belong
   together" grouping. Clustering with a fixed gap and letting it wrap
   keeps the pairs legible as one group at any card width. */
.plugin-meta-row,
.plugin-footer{ display:flex; align-items:center; flex-wrap:wrap; gap:10px 28px; }
.plugin-big{ font-family:var(--font-mono); font-size:calc(28px * var(--plugin-scale, 1)); font-weight:700; color:var(--neon-green); display:flex; align-items:flex-end; gap:4px; }
.plugin-big span{ font-family:var(--font-display); font-size:10px; letter-spacing:0.06em; text-transform:uppercase; color:var(--text-muted); margin-bottom:4px; }
.plugin-chip{
  border:1px solid var(--neon-green-dim); color:var(--neon-green); background:rgba(79,140,255,0.12); padding:6px 8px; border-radius:999px;
  font-family:var(--font-display); font-size:10px; letter-spacing:0.12em; text-transform:uppercase;
}
.plugin-label{ font-family:var(--font-display); font-size:10px; letter-spacing:0.06em; text-transform:uppercase; color:var(--text-dim); }

/* ---------- G-Force Vector Bubble (plugin: gforce-vector) ----------
   A standalone friction-circle plot: concentric rings + crosshair axes as
   plain SVG, a live dot for the current combined lateral/longitudinal g
   vector, and a fixed pool of pre-built trail dots (see gforce-vector.js's
   TAIL_POOL_SIZE) whose position/radius/opacity are rewritten each frame
   rather than creating/destroying DOM nodes. Deliberately plain/technical
   (no glass panels, no gradients beyond the app's existing token set) —
   the plot itself is the visual interest, not chrome around it. */
.gfv-root{
  position:relative;
  height:100%; width:100%;
  min-height:calc(220px * var(--plugin-scale, 1));
  border-radius:inherit;
  background:var(--bg-panel-raised);
  display:flex; flex-direction:column;
  padding:calc(14px * var(--plugin-scale, 1));
  gap:calc(8px * var(--plugin-scale, 1));
}
.gfv-head{ display:flex; align-items:center; justify-content:space-between; gap:10px; flex-shrink:0; }
.gfv-head-left{ display:flex; align-items:center; gap:8px; min-width:0; }
.gfv-head-icon{ font-size:calc(16px * var(--plugin-scale, 1)); }
.gfv-head-title{
  font-family:var(--font-display); font-weight:700; letter-spacing:0.04em; text-transform:uppercase;
  font-size:calc(13px * var(--plugin-scale, 1)); color:var(--text-primary);
}
.gfv-g-badge{
  font-family:var(--font-mono); font-weight:700; font-size:calc(15px * var(--plugin-scale, 1));
  color:var(--neon-green); transition:color 160ms ease;
}
.gfv-plot-wrap{ flex:1; min-height:0; display:flex; align-items:center; justify-content:center; }
.gfv-plot{ width:100%; height:100%; max-width:min(100%, 320px); aspect-ratio:1/1; }
.gfv-ring{ fill:none; stroke:var(--border-subtle); stroke-width:1; }
.gfv-ring-outer{ stroke:var(--border-strong); }
.gfv-axis{ stroke:var(--border-subtle); stroke-width:1; }
.gfv-axis-label{
  font-family:var(--font-display); font-size:8px; letter-spacing:0.08em;
  fill:var(--text-dim); text-transform:uppercase;
}
.gfv-ring-label{ font-family:var(--font-mono); font-size:6px; fill:var(--text-dim); }
.gfv-tail-dot{ fill:var(--cyan); }
.gfv-dot{
  fill:var(--neon-green);
  filter:drop-shadow(0 0 5px var(--neon-green-glow));
  transition:fill 160ms ease;
}
.gfv-readout{ display:flex; justify-content:space-between; gap:12px; flex-shrink:0; }
.gfv-stat{ display:flex; flex-direction:column; gap:2px; min-width:0; }
.gfv-stat-label{
  font-family:var(--font-display); font-size:9px; letter-spacing:0.08em; text-transform:uppercase;
  color:var(--text-dim);
}
.gfv-stat-value{ font-family:var(--font-mono); font-size:calc(13px * var(--plugin-scale, 1)); color:var(--text-primary); }

/* ---------- Apex Launch Control (plugin: apex-drag) ----------
   Ground-up redesign — a segmented target/mode selector, a single
   readiness ring instead of a literal drag-tree, and (in the full view)
   a full-screen launch overlay with three live phases: a digital
   light-bar countdown, a motion-armed radar pulse, and a canvas warp-
   tunnel run whose speed is driven by real telemetry. Every color comes
   from the app's existing tokens (--neon-green renders as this app's
   blue accent; --cyan is the secondary telemetry tone) so it reads as
   native RydR chrome in every theme, not a fixed racing palette. */

/* Shared segmented control — widget card and full view both use it */
.alc-seg{
  display:flex; gap:4px; padding:4px;
  background:var(--bg-void); border:1px solid var(--border-subtle);
  border-radius:var(--radius-md);
}
.alc-seg-item{
  flex:1; min-width:0; text-align:center;
  padding:9px 6px; border-radius:9px; border:none; background:none;
  color:var(--text-muted);
  font-family:var(--font-display); font-weight:600; font-size:12px; letter-spacing:0.02em;
  cursor:pointer; white-space:nowrap;
  transition:background 0.15s ease, color 0.15s ease;
}
.alc-seg-item:hover{ color:var(--text-primary); }
.alc-seg-item.selected{ background:var(--neon-green); color:#02111f; }

/* ---------- Widget card ---------- */
.alc-card{ display:flex; flex-direction:column; gap:12px; }
.alc-card-head{ display:flex; align-items:center; gap:8px; }
.alc-card-dot{
  width:8px; height:8px; border-radius:50%; background:var(--cyan);
  box-shadow:0 0 8px 1px rgba(34,211,238,0.7);
  flex-shrink:0;
}
.alc-card-title{
  font-family:var(--font-display); font-weight:700; font-size:12px; letter-spacing:0.08em;
  color:var(--text-primary); text-transform:uppercase;
}
.alc-card-status{
  margin-left:auto;
  font-family:var(--font-display); font-weight:700; font-size:10px; letter-spacing:0.08em; text-transform:uppercase;
  color:var(--text-dim); background:var(--bg-panel-raised); border:1px solid var(--border-subtle);
  padding:4px 10px; border-radius:999px;
  transition:color 0.2s ease, border-color 0.2s ease;
}
.alc-card-hero{ display:flex; align-items:center; gap:14px; }
.alc-card-ring{ position:relative; width:64px; height:64px; flex-shrink:0; }
.alc-card-ring svg{ width:100%; height:100%; transform:rotate(-90deg); }
.alc-card-ring circle{ fill:none; }
.alc-card-ring .track{ stroke:var(--border-subtle); stroke-width:6; }
.alc-card-ring .fill{
  stroke:var(--neon-green); stroke-width:6; stroke-linecap:round;
  transition:stroke-dashoffset 0.2s linear, stroke 0.2s ease;
  filter:drop-shadow(0 0 5px var(--neon-green-glow));
}
.alc-card-ring-label{
  position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
  font-family:var(--font-display); font-weight:700; font-size:10px; letter-spacing:0.05em; color:var(--text-muted);
}
.alc-card-digits{ display:flex; flex-direction:column; gap:2px; min-width:0; }
.alc-card-time{
  font-family:var(--font-mono); font-weight:700; font-variant-numeric:tabular-nums;
  font-size:calc(26px * var(--plugin-scale, 1)); color:var(--text-primary); line-height:1.1;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.alc-card-sub{ font-family:var(--font-mono); font-size:12px; color:var(--text-muted); font-variant-numeric:tabular-nums; }

.alc-card-arm-btn{
  min-height:var(--touch); width:100%; border:none; border-radius:var(--radius-sm);
  background:var(--neon-green); color:#02111f;
  font-family:var(--font-display); font-weight:700; font-size:14px; letter-spacing:0.03em;
  cursor:pointer; transition:filter 0.15s ease, background 0.2s ease, color 0.2s ease;
}
.alc-card-arm-btn:hover{ filter:brightness(1.08); }
.alc-card-arm-btn:active{ filter:brightness(0.92); }
.alc-card-full-btn{
  border:1px solid var(--border-subtle); background:transparent; color:var(--text-muted);
  font-family:var(--font-display); font-weight:600; font-size:11px; letter-spacing:0.03em;
  padding:8px; border-radius:var(--radius-sm); cursor:pointer; transition:color 0.15s ease, border-color 0.15s ease;
}
.alc-card-full-btn:hover{ color:var(--neon-green); border-color:var(--neon-green-dim); }

.alc-card[data-phase="armed"] .alc-card-status{ color:var(--warn-amber); border-color:rgba(245,158,11,0.4); }
.alc-card[data-phase="armed"] .alc-card-ring .fill{ stroke:var(--warn-amber); animation:alc-pulse 1s ease-in-out infinite; }
.alc-card[data-phase="running"] .alc-card-status{ color:var(--cyan); border-color:rgba(34,211,238,0.4); }
.alc-card[data-phase="running"] .alc-card-ring .fill{ stroke:var(--cyan); }
.alc-card[data-phase="finished"] .alc-card-status{ color:var(--neon-green); border-color:var(--neon-green-dim); }
.alc-card[data-phase="armed"] .alc-card-arm-btn,
.alc-card[data-phase="running"] .alc-card-arm-btn{ background:var(--alert-red); color:#fff; }

@keyframes alc-pulse{ 0%,100%{ opacity:1; } 50%{ opacity:0.45; } }

/* ---------- Apex Pro dashboard card (alternate layout) ----------
   The full view's setup panel (target + launch mode) folded into the
   dashboard card, with the readiness dial tucked into a collapsible
   drawer — same collapse/expand mechanics as .weather-precip-drawer —
   that opens automatically once a launch sequence starts. */
.alc-card-pro-settings{ display:flex; flex-direction:column; gap:8px; }
.alc-card-pro-label{
  font-family:var(--font-display); font-weight:700; font-size:10px; letter-spacing:0.1em; text-transform:uppercase; color:var(--text-dim);
}
.alc-card-pro-toggle{
  display:flex; align-items:center; justify-content:space-between; width:100%;
  border:1px solid var(--border-subtle); background:var(--bg-void); color:var(--text-muted);
  border-radius:var(--radius-sm); padding:8px 12px; cursor:pointer;
  font-family:var(--font-display); font-weight:600; font-size:11px; letter-spacing:0.05em; text-transform:uppercase;
  transition:color 0.15s ease, border-color 0.15s ease;
}
.alc-card-pro-toggle:hover{ color:var(--text-primary); }
.alc-card-pro-toggle .chevron{ transition:transform 0.2s ease; display:inline-block; }
.alc-card-pro-toggle[aria-expanded="true"] .chevron{ transform:rotate(180deg); }
.alc-card-pro-drawer{
  max-height:0; overflow:hidden; opacity:0;
  transition:max-height 0.28s ease, opacity 0.2s ease, margin-top 0.28s ease;
}
.alc-card-pro-drawer.open{ max-height:160px; opacity:1; margin-top:2px; }

/* ---------- Full view: Apex Launch Control ---------- */
.alc-view{ overflow-y:auto; }
.alc-topbar{
  display:flex; align-items:center; gap:14px;
  /* 64px clears the fixed hamburger .menu-btn — same left gutter every
     other full-screen header bar in the app reserves for it. */
  padding:14px 20px 14px 64px;
  background:rgba(10,12,17,0.9); border-bottom:1px solid var(--border-subtle); backdrop-filter:blur(12px);
  position:sticky; top:0; z-index:2;
}
.alc-topbar h2{ margin:0; font-family:var(--font-display); font-weight:700; font-size:20px; letter-spacing:0.02em; color:var(--text-primary); }
.alc-topbar-spacer{ flex:1; }

.alc-main-grid{
  display:grid; grid-template-columns:1.15fr 1fr; gap:18px;
  padding:20px; max-width:1200px; margin:0 auto; width:100%; box-sizing:border-box;
}
@media (max-width:860px){ .alc-main-grid{ grid-template-columns:1fr; } }

.alc-panel{
  background:var(--bg-panel); border:1px solid var(--border-subtle); border-radius:var(--radius-lg);
  padding:22px; display:flex; flex-direction:column; gap:16px;
}
.alc-panel-label{
  font-family:var(--font-display); font-weight:700; font-size:11px; letter-spacing:0.14em; text-transform:uppercase; color:var(--text-dim);
}
.alc-help-text{ font-size:12.5px; line-height:1.5; color:var(--text-muted); margin:0; }
.alc-setup-panel > .alc-arm-btn{ margin-top:auto; }

.alc-arm-btn{
  min-height:52px; border:none; border-radius:var(--radius-md);
  background:var(--neon-green); color:#02111f;
  font-family:var(--font-display); font-weight:700; font-size:15px; letter-spacing:0.04em;
  cursor:pointer; display:flex; align-items:center; justify-content:center; gap:8px;
  box-shadow:0 8px 24px var(--neon-green-glow);
  transition:filter 0.15s ease, background 0.2s ease, transform 0.1s ease;
}
.alc-arm-btn:hover{ filter:brightness(1.06); }
.alc-arm-btn:active{ transform:translateY(1px); }
.alc-arm-btn.arming{ background:var(--alert-red); box-shadow:0 8px 24px var(--alert-red-glow); }

/* Readiness dial panel */
.alc-dial-panel{ align-items:center; text-align:center; }
.alc-dial{ position:relative; width:220px; height:220px; margin:6px auto; }
.alc-dial svg{ width:100%; height:100%; transform:rotate(-90deg); }
.alc-dial circle{ fill:none; }
.alc-dial .track{ stroke:var(--border-subtle); stroke-width:12; }
.alc-dial .fill{
  stroke:var(--cyan); stroke-width:12; stroke-linecap:round;
  transition:stroke-dashoffset 0.2s linear, stroke 0.3s ease;
  filter:drop-shadow(0 0 10px rgba(34,211,238,0.5));
}
.alc-dial-center{ position:absolute; inset:0; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:2px; }
.alc-dial-time{ font-family:var(--font-mono); font-weight:700; font-size:40px; color:var(--text-primary); font-variant-numeric:tabular-nums; }
.alc-dial-unit{ font-family:var(--font-display); font-weight:700; font-size:10px; letter-spacing:0.16em; color:var(--text-dim); }

.alc-stat-row{ display:flex; gap:28px; justify-content:center; flex-wrap:wrap; }
.alc-stat{ display:flex; flex-direction:column; align-items:center; gap:2px; }
.alc-stat .v{ font-family:var(--font-mono); font-weight:700; font-size:19px; color:var(--text-primary); font-variant-numeric:tabular-nums; }
.alc-stat .k{ font-family:var(--font-display); font-weight:700; font-size:9px; letter-spacing:0.1em; color:var(--text-dim); }

/* History / proximity panel */
.alc-history{ max-width:1200px; margin:0 auto 24px; padding:0 20px 20px; width:100%; box-sizing:border-box; }
.alc-history-inner{
  background:var(--bg-panel); border:1px solid var(--border-subtle); border-radius:var(--radius-lg); padding:20px;
}
.alc-badge{
  display:inline-flex; align-items:center; gap:6px; padding:6px 12px; border-radius:999px;
  background:rgba(34,211,238,0.12); border:1px solid rgba(34,211,238,0.35); color:var(--cyan);
  font-family:var(--font-display); font-weight:700; font-size:11px; letter-spacing:0.04em; margin-bottom:12px;
}
.alc-badge.alt{ background:rgba(245,158,11,0.12); border-color:rgba(245,158,11,0.35); color:var(--warn-amber); }
.alc-share-img{ width:100%; border-radius:12px; border:1px solid var(--border-subtle); margin-bottom:12px; display:block; }
.alc-run-list{ display:flex; flex-direction:column; gap:8px; max-height:360px; overflow-y:auto; padding-right:2px; }
.alc-run-item{
  display:flex; align-items:center; justify-content:space-between; gap:10px;
  background:var(--bg-panel-raised); border:1px solid var(--border-subtle); border-radius:10px; padding:10px 14px;
  transition:border-color 0.15s ease, background 0.15s ease;
}
.alc-run-item:hover{ border-color:var(--neon-green-dim); background:var(--bg-panel-hover); }
.alc-run-item strong{ font-family:var(--font-mono); color:var(--cyan); font-size:14px; }
.alc-run-item small{ display:block; color:var(--text-dim); font-size:11px; margin-top:2px; }

/* ---------- Launch overlay ---------- */
.alc-overlay{
  position:fixed; inset:0; z-index:700; display:flex; align-items:center; justify-content:center;
  background:var(--bg-void);
  opacity:0; pointer-events:none; transition:opacity 0.25s ease;
}
.alc-overlay.open{ opacity:1; pointer-events:auto; }
.alc-tunnel-canvas{ position:absolute; inset:0; width:100%; height:100%; display:block; }
.alc-overlay-content{
  position:relative; z-index:1; display:flex; flex-direction:column; align-items:center; gap:26px;
  padding:32px; text-align:center; max-width:640px; width:100%;
}
.alc-eyebrow{ font-family:var(--font-display); font-weight:700; font-size:13px; letter-spacing:0.3em; color:var(--text-dim); }
.alc-eyebrow.cyan{ color:var(--cyan); }
.alc-hint{ font-family:var(--font-body); font-size:13px; color:var(--text-muted); max-width:420px; }

.alc-phase{ display:flex; flex-direction:column; align-items:center; gap:22px; }
.alc-phase[hidden]{ display:none; }

/* countdown phase: digital light bar */
.alc-light-bar{ display:flex; gap:12px; }
.alc-light-cell{
  width:56px; height:78px; border-radius:12px; background:var(--bg-panel);
  border:1px solid var(--border-subtle); position:relative; overflow:hidden;
  display:flex; align-items:center; justify-content:center;
}
.alc-light-cell .lamp{ width:28px; height:28px; border-radius:50%; background:var(--bg-panel-raised); transition:background 0.15s ease, box-shadow 0.15s ease; }
.alc-light-cell.amber.lit .lamp{ background:var(--warn-amber); box-shadow:0 0 20px 3px rgba(245,158,11,0.8); }
.alc-light-cell.green.lit .lamp{ background:var(--neon-green); box-shadow:0 0 24px 4px var(--neon-green-glow); }
.alc-big-count{
  font-family:var(--font-mono); font-weight:800; font-size:84px; color:var(--text-primary);
  font-variant-numeric:tabular-nums; line-height:1;
  text-shadow:0 0 30px rgba(238,241,245,0.15);
}
.alc-big-count.go{ color:var(--neon-green); text-shadow:0 0 40px var(--neon-green-glow); }

/* armed phase: pulsing radar ring */
.alc-armed-ring{ position:relative; width:220px; height:220px; display:flex; align-items:center; justify-content:center; }
.alc-armed-ring .ring{
  position:absolute; inset:0; border-radius:50%; border:2px solid rgba(34,211,238,0.35);
  animation:alc-radar 2.2s ease-out infinite;
}
.alc-armed-ring .ring:nth-child(2){ animation-delay:0.7s; }
.alc-armed-ring .ring:nth-child(3){ animation-delay:1.4s; }
.alc-armed-core{
  width:88px; height:88px; border-radius:50%; background:radial-gradient(circle, rgba(34,211,238,0.35), transparent 70%);
  border:2px solid var(--cyan); display:flex; align-items:center; justify-content:center;
  font-family:var(--font-display); font-weight:700; font-size:14px; letter-spacing:0.1em; color:var(--text-primary);
  box-shadow:0 0 30px rgba(34,211,238,0.4);
}
@keyframes alc-radar{
  0%{ transform:scale(0.4); opacity:0.9; }
  100%{ transform:scale(1); opacity:0; }
}

/* running phase: big climbing speed */
.alc-run-speed{
  font-family:var(--font-mono); font-weight:800; font-variant-numeric:tabular-nums;
  font-size:120px; line-height:1; color:var(--text-primary); text-shadow:0 0 40px rgba(34,211,238,0.35);
}
.alc-run-unit{ font-family:var(--font-display); font-weight:700; font-size:14px; letter-spacing:0.2em; color:var(--text-dim); margin-top:-10px; }

/* shared bottom stat bar during armed/running */
.alc-overlay-stats{ display:flex; gap:44px; }
.alc-overlay-stats .v{ font-family:var(--font-mono); font-weight:700; font-size:24px; color:var(--text-primary); font-variant-numeric:tabular-nums; }
.alc-overlay-stats .k{ font-family:var(--font-display); font-weight:700; font-size:10px; letter-spacing:0.12em; color:var(--text-dim); }

/* finish phase */
.alc-result-row{ display:flex; gap:48px; flex-wrap:wrap; justify-content:center; }
.alc-result .v{ font-family:var(--font-mono); font-weight:800; font-size:48px; color:var(--text-primary); font-variant-numeric:tabular-nums; }
.alc-result .k{ font-family:var(--font-display); font-weight:700; font-size:10px; letter-spacing:0.14em; color:var(--text-dim); }
.alc-result-meta{ font-size:12.5px; color:var(--text-muted); }
.alc-actions-row{ display:flex; gap:12px; }
.alc-action-btn{
  padding:11px 22px; border-radius:var(--radius-sm); font-family:var(--font-display); font-weight:700; font-size:12px; letter-spacing:0.03em;
  border:1px solid var(--border-subtle); background:transparent; color:var(--text-muted); cursor:pointer;
  transition:border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}
.alc-action-btn.primary{ background:var(--neon-green); color:#02111f; border-color:transparent; }
.alc-action-btn:hover{ filter:brightness(1.08); }

.alc-cancel-btn{
  border:1px solid var(--border-subtle); background:transparent; color:var(--text-dim);
  font-family:var(--font-display); font-weight:700; font-size:11px; letter-spacing:0.08em;
  padding:11px 24px; border-radius:999px; cursor:pointer; margin-top:4px;
  transition:color 0.15s ease, border-color 0.15s ease;
}
.alc-cancel-btn:hover{ color:var(--alert-red); border-color:var(--alert-red); }

@media (prefers-reduced-motion: reduce){
  .alc-armed-ring .ring{ animation:none; opacity:0.5; }
}
.plugin-value{ font-family:var(--font-mono); font-size:calc(12px * var(--plugin-scale, 1)); color:var(--text-primary); margin-top:2px; }
.plugin-surface.light .plugin-value{ color:#1a231d; }
.plugin-surface.light .plugin-label{ color:#4f6654; }

/* ---------- Ride Pulse: instrument-cluster redesign ----------
   A completely bespoke card, styled like a physical motorcycle dash
   rather than a grid of generic stat tiles: a halogen-amber speedometer
   arc, a lean-angle "horizon" dial that actually tilts a bike glyph left/
   right, a heading compass, an idiot-light REC indicator, and an all-time
   "logbook" with a mechanical rolling-digit odometer. Deliberately uses
   its own fixed dark gauge-face palette (not the app's per-theme tokens)
   the same way plugins/chase-cam-3d and spotify-playlists already do for
   their own bespoke cards — a backlit dash face reads the same whether
   the rest of the app is in light or dark theme, same as a real
   motorcycle's cluster does. For that reason it also opts out of the
   Settings > Appearance "custom card text color" knob (.rr-card
   .plugin-value/.plugin-label), same as those two cards already do —
   arbitrary user text colors would break the gauge-face contrast this
   was tuned against. */
.rp-cluster{
  --rp-face:#14161a; --rp-face-raised:#1c1f24; --rp-rim:#2a2e35;
  --rp-amber:#ffb020; --rp-amber-dim:#c98f3a; --rp-red:#ff3b3b;
  --rp-ice:#eaf6ff; --rp-text-dim:#8790a0; --rp-track:#2c313a;
  background:
    radial-gradient(circle at 30% 0%, rgba(255,176,32,0.07), transparent 55%),
    var(--rp-face);
  /* inherit (not a fixed radius token) so this matches whatever corner
     radius the outer .plugin-shell-standalone card actually has — two
     independently-rounded nested boxes read as a visual mismatch/double
     border, the same fix .hp-root uses for the same reason. */
  border-radius:inherit;
  padding:calc(14px * var(--plugin-scale, 1));
  display:flex; flex-direction:column; gap:calc(10px * var(--plugin-scale, 1));
  font-family:var(--font-body);
  position:relative;
  width:100%; height:100%; max-width:100%; box-sizing:border-box;
  overflow:hidden;
}
/* Faint brushed-metal grain — a repeating diagonal hairline pattern, not
   an image, so it stays crisp at any card scale and costs nothing to load. */
.rp-cluster::before{
  content:""; position:absolute; inset:0; pointer-events:none; z-index:0;
  background-image:repeating-linear-gradient(115deg, rgba(255,255,255,0.025) 0px, rgba(255,255,255,0.025) 1px, transparent 1px, transparent 5px);
}
/* A flex column's cross-axis (width) default is align-items:stretch, but
   that still yields to a child's own min-content width if it's wider —
   min-width:0 here is the blanket fix so no direct child (hero row,
   placards row, logbook) can push the card wider than its container. */
.rp-cluster > *{ position:relative; z-index:1; min-width:0; max-width:100%; }

.rp-hero{ display:flex; align-items:center; gap:calc(10px * var(--plugin-scale, 1)); min-width:0; }
.rp-speed-gauge{
  position:relative;
  /* flex-basis:0 with hero-side sharing the same basis+grow means the
     two split available space evenly instead of one taking a fixed
     share and dumping the entire squeeze onto the other — that's what
     was actually forcing hero-side's content past its own box before.
     --plugin-scale is a vertical/density knob elsewhere in this app (the
     resize grip only drags on Y), so it never grows the *available*
     width — max-width is capped in cqw (relative to .rr-card, which is
     already a container) instead of multiplying by scale, so the gauge
     can't demand more horizontal room than the card actually has. */
  flex:1 1 0;
  min-width:56px;
  max-width:min(150px, 42cqw);
  aspect-ratio:200 / 116;
}
.rp-speed-svg{ width:100%; height:100%; display:block; overflow:visible; }
.rp-speed-track{ fill:none; stroke:var(--rp-track); stroke-width:11; stroke-linecap:round; }
.rp-speed-fill{
  fill:none; stroke:var(--rp-amber); stroke-width:11; stroke-linecap:round;
  filter:drop-shadow(0 0 5px rgba(255,176,32,0.55));
  transition:stroke-dashoffset 0.25s cubic-bezier(0.22,1,0.36,1), stroke 0.25s ease;
}
.rp-speed-fill.is-redline{ stroke:var(--rp-red); filter:drop-shadow(0 0 6px rgba(255,59,59,0.6)); }
.rp-speed-tick{ stroke:var(--rp-rim); stroke-width:2.5; stroke-linecap:round; }
.rp-speed-readout{
  position:absolute; left:0; right:0; bottom:calc(-2px * var(--plugin-scale, 1));
  display:flex; flex-direction:column; align-items:center; line-height:1;
}
.rp-speed-num{
  font-family:'Orbitron', var(--font-display); font-weight:800;
  /* .rr-card is a container (container-type:inline-size) — capping with
     a cqw-relative value means the numeral automatically scales itself
     down on a genuinely narrow/1-third-width card instead of overflowing
     the gauge it sits on once that gauge has shrunk below its usual size. */
  font-size:min(calc(34px * var(--plugin-scale, 1)), 15cqw);
  color:var(--rp-ice);
  letter-spacing:0.01em; text-shadow:0 0 14px rgba(234,246,255,0.25);
}
.rp-speed-unit{
  font-family:var(--font-display); font-weight:600; font-size:min(calc(9px * var(--plugin-scale, 1)), 4cqw);
  letter-spacing:0.14em; color:var(--rp-text-dim); margin-top:1px;
}

.rp-hero-side{
  display:flex; flex-direction:column; gap:calc(6px * var(--plugin-scale, 1));
  flex:1 1 0; min-width:70px;
}

.rp-rec-light{
  display:flex; align-items:center; gap:6px;
  padding:4px min(calc(8px * var(--plugin-scale, 1)), 3cqw);
  background:var(--rp-face-raised); border:1px solid var(--rp-rim); border-radius:999px;
  /* Sized to its own content but docked to the column's right edge — same
     reasoning as the mini-gauge rows below: rp-hero-side is a flex-grow
     column that's often much wider than any single row's content, so
     without an explicit anchor everything here just hugs the left edge
     and leaves a dead gap on the right, right where a rider glancing over
     from the speedometer would expect HDG/LEAN to actually be. */
  width:fit-content; max-width:100%; align-self:flex-end;
}
.rp-rec-dot{
  width:8px; height:8px; border-radius:50%; flex:0 0 auto;
  background:var(--rp-text-dim);
}
.rp-rec-light.is-recording .rp-rec-dot{
  background:var(--rp-red); box-shadow:0 0 8px 1px rgba(255,59,59,0.75);
  animation:rp-rec-pulse 1.4s ease-in-out infinite;
}
@keyframes rp-rec-pulse{ 0%,100%{ opacity:1; } 50%{ opacity:0.35; } }
.rp-rec-text{
  font-family:var(--font-display); font-weight:700; font-size:min(calc(9.5px * var(--plugin-scale, 1)), 4cqw);
  letter-spacing:0.1em; color:var(--rp-text-dim); white-space:nowrap;
}
.rp-rec-light.is-recording .rp-rec-text{ color:var(--rp-ice); }

.rp-mini-gauge{
  display:flex; align-items:center; justify-content:flex-end;
  gap:min(calc(7px * var(--plugin-scale, 1)), 3cqw); min-width:0; max-width:100%;
}
.rp-mini-gauge svg{
  width:min(calc(32px * var(--plugin-scale, 1)), 10cqw);
  height:min(calc(32px * var(--plugin-scale, 1)), 10cqw);
  flex:0 0 auto;
}
.rp-mini-face{ fill:var(--rp-face-raised); stroke:var(--rp-rim); stroke-width:2; }
.rp-mini-tick{ stroke:var(--rp-rim); stroke-width:2.5; stroke-linecap:round; }
.rp-mini-horizon{ stroke:var(--rp-rim); stroke-width:1.5; stroke-dasharray:2 2; }
.rp-bike-glyph{ transform-box:fill-box; transform-origin:50% 50%; transition:transform 0.2s ease-out; }
.rp-bike-wheel{ fill:none; stroke:var(--rp-ice); stroke-width:3; }
.rp-bike-frame{ fill:none; stroke:var(--rp-amber); stroke-width:2.6; stroke-linecap:round; stroke-linejoin:round; }
.rp-bike-glyph.is-flat .rp-bike-frame{ stroke:var(--rp-text-dim); }
.rp-compass-n{
  font-family:var(--font-display); font-weight:700; font-size:8px; fill:var(--rp-text-dim);
  text-anchor:middle;
}
.rp-compass-needle{ transform-box:fill-box; transform-origin:50% 50%; transition:transform 0.25s ease-out; }
.rp-needle-shape{ fill:var(--rp-amber); }
.rp-mini-label{
  font-family:var(--font-display); font-weight:600; font-size:min(calc(9.5px * var(--plugin-scale, 1)), 4cqw);
  letter-spacing:0.06em; color:var(--rp-text-dim); white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
  min-width:0;
}
.rp-mini-label b{ color:var(--rp-ice); font-weight:700; margin-left:3px; }

.rp-placards{ display:flex; gap:calc(8px * var(--plugin-scale, 1)); }
.rp-placard{
  flex:1; min-width:0; display:flex; align-items:center; gap:8px;
  background:var(--rp-face-raised); border:1px solid var(--rp-rim);
  border-radius:var(--radius-sm); padding:calc(7px * var(--plugin-scale, 1)) calc(9px * var(--plugin-scale, 1));
}
.rp-placard > div{ min-width:0; }
.rp-placard-icon{ font-size:min(calc(14px * var(--plugin-scale, 1)), 6cqw); flex:0 0 auto; filter:grayscale(0.15); }
.rp-placard-label{
  font-family:var(--font-display); font-weight:600; font-size:min(calc(8.5px * var(--plugin-scale, 1)), 3.6cqw);
  letter-spacing:0.1em; text-transform:uppercase; color:var(--rp-text-dim); white-space:nowrap;
}
.rp-placard-value{
  font-family:var(--font-mono); font-size:min(calc(11.5px * var(--plugin-scale, 1)), 4.8cqw); color:var(--rp-ice);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; margin-top:1px;
}

/* A literal stretch of road as the section break — a dashed lane line
   down an asphalt strip, exactly what separates "right now" from "the
   road behind you" on this dash. */
.rp-road-divider{
  height:10px; border-radius:3px; position:relative; overflow:hidden;
  background:linear-gradient(180deg, #22262d, #1a1d23);
  border:1px solid var(--rp-rim);
}
.rp-lane-line{
  position:absolute; top:50%; left:0; right:0; height:2px; transform:translateY(-50%);
  background-image:repeating-linear-gradient(90deg, var(--rp-amber-dim) 0 10px, transparent 10px 20px);
  opacity:0.8;
}

.rp-logbook{ display:flex; flex-direction:column; gap:calc(8px * var(--plugin-scale, 1)); }
.rp-logbook-title{
  font-family:var(--font-display); font-weight:700; font-size:min(calc(10.5px * var(--plugin-scale, 1)), 4.4cqw);
  letter-spacing:0.1em; text-transform:uppercase; color:var(--rp-text-dim);
}
.rp-logbook-title span{ font-weight:500; text-transform:none; letter-spacing:0; color:var(--rp-text-dim); opacity:0.75; }
.rp-logbook-empty{ font-family:var(--font-body); font-size:12px; color:var(--rp-text-dim); }

.rp-odometer{
  display:flex; align-items:center; gap:3px;
  background:var(--rp-face-raised); border:1px solid var(--rp-rim); border-radius:var(--radius-sm);
  padding:calc(8px * var(--plugin-scale, 1)) calc(10px * var(--plugin-scale, 1));
  /* Was width:fit-content with fixed-px digit boxes — content that never
     shrinks, so on a narrow card or a scaled-up card it just pushed past
     the card's edge instead of adapting. Digits now flex-share whatever
     width is actually available (min-width:0 lets them go below their
     own text's min-content size, same fix as everywhere else in this
     card), each capped at its old size so a wide card still looks like a
     normal-proportioned odometer instead of stretching. */
  width:100%; max-width:100%; box-sizing:border-box; min-width:0;
}
.rp-odo-digit{
  font-family:var(--font-mono); font-weight:700; font-size:min(calc(18px * var(--plugin-scale, 1)), 7cqw);
  color:var(--rp-ice); background:var(--rp-face);
  border:1px solid var(--rp-rim); border-radius:3px;
  flex:1 1 0; min-width:0; max-width:calc(16px * var(--plugin-scale, 1)); text-align:center;
  box-shadow:inset 0 2px 3px rgba(0,0,0,0.5);
}
.rp-odo-tenths{
  font-family:var(--font-mono); font-weight:700; font-size:min(calc(14px * var(--plugin-scale, 1)), 5.5cqw);
  color:var(--rp-face); background:var(--rp-amber);
  border-radius:3px; flex:0 1 auto; min-width:0; max-width:calc(13px * var(--plugin-scale, 1)); text-align:center;
  margin-left:2px;
}
.rp-odo-unit{
  font-family:var(--font-display); font-weight:700; font-size:min(calc(9px * var(--plugin-scale, 1)), 3.6cqw);
  letter-spacing:0.08em; color:var(--rp-text-dim); margin-left:6px;
  flex:0 0 auto; white-space:nowrap;
}

.rp-spec-sheet{
  /* A bare `1fr` track's *minimum* size is its content's min-content
     width, not 0 — with nowrap label/value text inside, that silently
     forced the whole grid (and the card around it) wider than the
     container on anything narrower than the content demanded.
     minmax(0, 1fr) is the actual "share space, shrink freely" track. */
  display:grid; grid-template-columns:minmax(0, 1fr) minmax(0, 1fr);
  gap:0 calc(14px * var(--plugin-scale, 1));
  min-width:0;
}
.rp-spec-row{
  display:flex; align-items:baseline; justify-content:space-between; gap:6px;
  padding:calc(5px * var(--plugin-scale, 1)) 0;
  border-bottom:1px solid var(--rp-rim);
  min-width:0;
}
.rp-spec-k{
  font-family:var(--font-display); font-weight:500; font-size:min(calc(10px * var(--plugin-scale, 1)), 4.2cqw);
  letter-spacing:0.02em; color:var(--rp-text-dim);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; min-width:0;
}
.rp-spec-v{
  font-family:var(--font-mono); font-weight:700; font-size:min(calc(11.5px * var(--plugin-scale, 1)), 4.8cqw);
  color:var(--rp-amber-dim); white-space:nowrap; flex:0 0 auto;
}

/* Card is narrow (Layout editor's 1/3-width column, or a small phone) —
   drop the two-column spec sheet to one so values never truncate. */
@container (max-width: 260px){
  .rp-spec-sheet{ grid-template-columns:1fr; }
  .rp-placards{ flex-direction:column; }
}

/* ---------- 3D Chase Cam plugin card ---------- */
.cc3d-surface{ padding:0; overflow:hidden; }
.cc3d-map-wrap{
  position:relative; width:100%; height:calc(320px * var(--plugin-scale, 1));
  background:var(--card-custom-bg, var(--bg-panel));
}
/* MapLibre adds its own "maplibregl-map" class directly to the container
   element it's given, and that library's stylesheet sets
   .maplibregl-map{position:relative; overflow:hidden}. That stylesheet
   loads dynamically (after this one), so at equal specificity
   (single-class selector either way) it wins the cascade and overrides
   this rule's position:absolute — collapsing .cc3d-map to 0 height in
   normal flow (its only child, the canvas, is itself position:absolute
   per MapLibre's own CSS, so contributes nothing to auto-height), and
   MapLibre's overflow:hidden then clips the correctly-sized canvas down
   to that zero-height box. Confirmed live: a real device reported the
   canvas rendering fine internally (889x836) while this container read
   0 height. The compound selector's higher specificity (two classes vs
   MapLibre's one) makes this win regardless of load order — no
   !important needed, and none added, so a real future override still can. */
.cc3d-map-wrap .cc3d-map{ position:absolute; inset:0; }
.cc3d-map .maplibregl-ctrl-logo,
.cc3d-map .maplibregl-ctrl-attrib{ display:none !important; }
.cc3d-fade-overlay{
  /* Fades the top of the map into the card's own themed background —
     a plain gradient overlay, not a CSS mask on the map/canvas itself.
     A `mask-image`/`-webkit-mask-image` on an element containing a WebGL
     canvas is a known compatibility landmine: on some browsers/GPUs the
     masked canvas renders fully invisible instead of partially faded,
     which is exactly what happened here (report: "map only shows the
     glow ring" — i.e. the whole map had gone invisible, mask and all).
     A sibling overlay achieves the same visual result without ever
     touching how the canvas itself composites. */
  position:absolute; inset:0; z-index:1; pointer-events:none;
  background:linear-gradient(to bottom, var(--card-custom-bg, var(--bg-panel)) 0%, rgba(0,0,0,0) 60%);
}
.cc3d-status{
  position:absolute; inset:0; z-index:3;
  display:flex; flex-direction:column; align-items:center; justify-content:center; text-align:center; padding:16px; gap:10px;
  background:var(--card-custom-bg, var(--bg-panel)); color:var(--text-primary);
  font-family:var(--font-display); font-size:12px; letter-spacing:0.02em;
}
.cc3d-status-retry{
  font-family:var(--font-display); font-weight:700; font-size:12px; letter-spacing:0.04em; text-transform:uppercase;
  padding:8px 18px; border-radius:999px; cursor:pointer;
  background:#ff9d2e; color:#1a0f05; border:none;
}
.cc3d-status-retry:hover{ filter:brightness(1.08); }
.cc3d-glow-ring{
  /* top must match CHASE_FOCUS_FRACTION in chase-cam-3d.js — the actual
     3D bike model now renders there (via MapLibre's `padding` camera
     option, not a CSS position), since a passenger-POV chase cam frames
     the rider low with the road extending ahead rather than dead center.
     This ring is a plain CSS decoration with no tie to the map's real
     camera, so it has to be told the same number by hand. */
  position:absolute; top:68%; left:50%; z-index:1;
  width:34%; aspect-ratio:1;
  transform:translate(-50%, -50%);
  border-radius:50%;
  border:3px solid rgba(255,157,46,0.55);
  box-shadow:0 0 22px 3px rgba(255,157,46,0.35), inset 0 0 18px 2px rgba(255,157,46,0.25);
  pointer-events:none;
  animation:cc3d-pulse 2.6s ease-in-out infinite;
}
.cc3d-glow-ring::before{
  content:"";
  position:absolute; inset:-22%;
  border-radius:50%;
  border:1px solid rgba(255,157,46,0.3);
}
@keyframes cc3d-pulse{
  0%, 100%{ opacity:0.5; transform:translate(-50%, -50%) scale(0.94); }
  50%{ opacity:0.9; transform:translate(-50%, -50%) scale(1.03); }
}
/* Mount point for the shared RydRDebugConsole (js/debug-console.js) —
   the console itself is a self-contained component that injects its own
   styling, this just gives it a slot below the map (rather than the old
   always-on overlay on top of it) and undoes .cc3d-surface's padding:0
   so it doesn't sit flush against the card edges. Empty/invisible unless
   the rider has turned on in-app debugging for this card in Settings >
   Advanced > Developer. */
.cc3d-debug-mount{ padding:0 12px 10px; }
.cc3d-hud{ position:absolute; left:10px; bottom:10px; z-index:2; display:flex; gap:8px; pointer-events:none; }
.cc3d-hud-chip{
  background:rgba(5,7,10,0.72); backdrop-filter:blur(4px);
  border:1px solid rgba(255,157,46,0.4); color:#ffb060;
  font-family:var(--font-mono); font-weight:700; font-size:12px;
  padding:4px 10px; border-radius:999px;
}
.cc3d-recenter-btn{
  position:absolute; right:10px; bottom:10px; z-index:2;
  width:34px; height:34px; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  background:rgba(5,7,10,0.72); backdrop-filter:blur(4px);
  border:1px solid rgba(255,157,46,0.4); color:#ffb060;
  font-size:17px; cursor:pointer;
}
.cc3d-recenter-btn:hover{ filter:brightness(1.15); }
.cc3d-close-btn{
  position:absolute; right:10px; top:10px; z-index:2;
  width:30px; height:30px; border-radius:50%;
  display:flex; align-items:center; justify-content:center;
  background:rgba(5,7,10,0.72); backdrop-filter:blur(4px);
  border:1px solid rgba(255,157,46,0.4); color:#ffb060;
  font-size:14px; cursor:pointer;
}
.cc3d-close-btn:hover{ filter:brightness(1.15); }

/* Default dashboard-card state — nothing loaded, nothing rendering, until
   tapped open. See the renderCollapsed()/openLive() split in
   chase-cam-3d.js: this is the single heaviest card on the dashboard
   (a live WebGL context + continuous GPS subscription), so unlike every
   other card it doesn't get to just idle here by default. */
.cc3d-collapsed{
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:12px; padding:28px 18px; text-align:center;
  min-height:calc(200px * var(--plugin-scale, 1));
}
.cc3d-collapsed-icon{ font-size:30px; }
.cc3d-collapsed-desc{
  font-family:var(--font-body); font-size:12.5px; line-height:1.5;
  color:var(--text-muted); max-width:32ch; margin:0;
}

/* ---------- 3D Chase Map (chase-map.html) — the same chase-cam-3d plugin
   card, stretched to fill the whole viewport instead of a small resizable
   dashboard card. Only overrides the plugin's own fixed card chrome
   (rounded corners, border, the calc(320px * scale) card height) — the
   map/HUD/recenter-button markup and behavior underneath are untouched. */
.chase-map-page{
  position:fixed; inset:0; overflow:hidden;
}
.chase-map-fullhost{ position:absolute; inset:0; }
/* padding:0/gap:0 undo .plugin-surface's card insets, and flex:1 1 auto +
   min-height:0 (the standard flexbox fix for a sized child inside a
   column flex container) make the map itself claim 100% of the page
   rather than 100% of whatever's left after the debug-mount sibling and
   the removed gap — without min-height:0 a flex item's automatic minimum
   size is its content size, which would still leave it short. */
.chase-map-fullhost .cc3d-surface{ height:100%; border-radius:0; border:none; padding:0; gap:0; }
.chase-map-fullhost .cc3d-map-wrap{ flex:1 1 auto; min-height:0; height:auto; }
.chase-map-fullhost .cc3d-debug-mount{ flex:0 0 auto; padding:0; }
.chase-map-fullhost .cc3d-debug-mount:empty{ display:none; }

/* Live "current street" toast — glass panel anchored in the same top row
   the header status bar uses elsewhere (left:64px clears the fixed
   .menu-btn instead of sitting under it), always dark/glassy regardless of
   the site theme since it always sits over this page's dark 3D map, same
   choice already made for the plugin card's own HUD chips. */
.chase-toast{
  position:fixed; z-index:50;
  top:calc(12px + env(safe-area-inset-top, 0px));
  left:64px; right:12px; max-width:420px;
  display:flex; align-items:flex-start; gap:10px;
  padding:11px 16px;
  border-radius:14px;
  background:rgba(8,11,16,0.62);
  border:1px solid rgba(255,255,255,0.1);
  box-shadow:0 6px 24px rgba(0,0,0,0.35), inset 0 1px 0 rgba(255,255,255,0.06);
  backdrop-filter:blur(14px) saturate(140%);
  -webkit-backdrop-filter:blur(14px) saturate(140%);
  pointer-events:none;
}
.chase-toast-icon{
  flex:0 0 auto; margin-top:1px; font-size:15px; line-height:1;
  filter:drop-shadow(0 0 6px rgba(255,157,46,0.55));
}
.chase-toast-text{ min-width:0; display:flex; flex-direction:column; gap:2px; }
.chase-toast-place{
  font-family:var(--font-display); font-weight:700; font-size:16px; letter-spacing:0.01em;
  color:#fff; line-height:1.2;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.chase-toast-road{
  font-family:var(--font-body); font-weight:500; font-size:12.5px; letter-spacing:0.01em;
  color:rgba(255,255,255,0.68); line-height:1.3;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
@media (max-width:480px){
  .chase-toast{ left:60px; right:10px; padding:9px 13px; gap:8px; }
  .chase-toast-place{ font-size:14.5px; }
  .chase-toast-road{ font-size:11.5px; }
}

/* ---------- Spotify card ----------
   Deliberately styled to match the official Spotify app rather than RydR's
   theme. Fully isolated with !important rules so that theme changes (light/dark/silver/etc.)
   or custom card background/font settings NEVER alter the authentic dark Spotify design.
   
   Every size below is multiplied by --sp-scale, set by dragging the corner
   grip, so a rider can size the hit targets once for gloved hands. */

/* Card wrapper & panel isolation */
.rr-card[data-card-id="spotify"] {
  background: transparent !important;
  border: none !important;
  box-shadow: none !important;
  overflow: visible !important;
}
.rr-card[data-card-id="spotify"]::before,
.rr-card[data-card-id="spotify"]::after {
  display: none !important;
}

.spotify-panel,
.spotify-panel-preview {
  --sp-scale: 1;
  position: relative !important;
  background: linear-gradient(180deg, #181818 0%, #121212 100%) !important;
  border: 1px solid #282828 !important;
  border-radius: 12px !important;
  padding: 16px !important;
  padding-bottom: 22px !important;
  overflow: hidden !important;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5) !important;
  font-family: Circular, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
  color: #ffffff !important;
  transition: none !important;
}

/* Header */
.spotify-head { display: flex !important; align-items: center !important; gap: 8px !important; margin-bottom: 14px !important; }
.spotify-mark { width: 20px !important; height: 20px !important; flex-shrink: 0 !important; display: block !important; }
.spotify-mark svg { width: 100% !important; height: 100% !important; display: block !important; }
.spotify-head-label {
  font-family: Circular, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
  font-weight: 700 !important;
  font-size: 13px !important;
  color: #ffffff !important;
  letter-spacing: -0.01em !important;
}
.spotify-status {
  margin-left: auto !important;
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace !important;
  font-size: 10px !important;
  color: #b3b3b3 !important;
  overflow: hidden !important;
  text-overflow: ellipsis !important;
  white-space: nowrap !important;
  max-width: 55% !important;
}

/* Header Action Buttons */
.spotify-icon-btn {
  flex-shrink: 0 !important;
  width: 32px !important;
  height: 32px !important;
  min-width: 32px !important;
  border-radius: 50% !important;
  display: grid !important;
  place-items: center !important;
  background: transparent !important;
  border: 1px solid #3a3a3a !important;
  color: #b3b3b3 !important;
  font-family: Circular, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
  font-size: 15px !important;
  font-weight: 700 !important;
  line-height: 1 !important;
  cursor: pointer !important;
  padding: 0 !important;
  margin-left: 6px !important;
  transition: all 0.2s ease !important;
}
.spotify-icon-btn img {
  width: 16px !important;
  height: 16px !important;
  display: block !important;
  object-fit: contain !important;
}
.spotify-icon-btn:hover { color: #ffffff !important; border-color: #5a5a5a !important; transform: scale(1.06) !important; }
.spotify-icon-btn:disabled { opacity: 0.35 !important; cursor: not-allowed !important; transform: none !important; }
.spotify-icon-btn.is-saved {
  color: #000000 !important;
  background: #1DB954 !important;
  border-color: #1DB954 !important;
}
.spotify-icon-btn.is-saved:hover { background: #1ed760 !important; border-color: #1ed760 !important; }

/* Playlist rows — shared by the standalone Spotify Playlists card below */
.spotify-recent-list { display: flex !important; flex-direction: column !important; gap: 2px !important; }
.spotify-recent-item {
  display: flex !important;
  align-items: center !important;
  gap: 10px !important;
  width: 100% !important;
  min-height: 44px !important;
  padding: 6px !important;
  border: none !important;
  border-radius: 8px !important;
  background: transparent !important;
  cursor: pointer !important;
  text-align: left !important;
  transition: background 0.15s ease !important;
}
.spotify-recent-item:hover { background: #383838 !important; }
.spotify-recent-item.is-playing {
  background: rgba(29, 185, 84, 0.15) !important;
  box-shadow: inset 0 0 0 1px #1DB954 !important;
}
.spotify-recent-item-art {
  width: 32px !important;
  height: 32px !important;
  border-radius: 4px !important;
  flex-shrink: 0 !important;
  background: #333333 center/cover no-repeat !important;
}
.spotify-recent-item-name {
  font-family: Circular, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
  font-size: 13px !important;
  color: #ffffff !important;
  overflow: hidden !important;
  text-overflow: ellipsis !important;
  white-space: nowrap !important;
  min-width: 0 !important;
}

/* Playlist drawer — attached to the bottom of the controller card,
   slides down (same max-height transition pattern as the weather
   card's precip drawer, see .weather-precip-drawer). Content is a
   single-row, 4-wide carousel: square thumbnail with the playlist name
   below it, swipe or use the arrows to see more. */
.spotify-playlists-drawer {
  max-height: 0 !important;
  overflow: hidden !important;
  opacity: 0 !important;
  transition: max-height 0.28s ease, opacity 0.2s ease, margin-top 0.28s ease !important;
}
.spotify-playlists-drawer.open {
  max-height: 200px !important;
  opacity: 1 !important;
  margin-top: 14px !important;
}
.spotify-playlists-drawer-head {
  display: flex !important;
  align-items: center !important;
  justify-content: space-between !important;
  margin-bottom: 10px !important;
}
.spotify-playlists-drawer-title {
  font-family: Circular, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
  font-size: 11px !important;
  font-weight: 700 !important;
  letter-spacing: 0.08em !important;
  text-transform: uppercase !important;
  color: #b3b3b3 !important;
}
.spotify-playlists-carousel {
  display: flex !important;
  align-items: center !important;
  gap: 6px !important;
}
.spotify-playlists-arrow {
  flex-shrink: 0 !important;
  width: 26px !important;
  height: 26px !important;
  border-radius: 50% !important;
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
  background: rgba(255, 255, 255, 0.08) !important;
  border: none !important;
  color: #ffffff !important;
  font-size: 16px !important;
  cursor: pointer !important;
}
.spotify-playlists-arrow:hover { background: rgba(29, 185, 84, 0.4) !important; color: #1DB954 !important; }
.spotify-playlists-track {
  display: flex !important;
  flex: 1 !important;
  min-width: 0 !important;
  gap: 8px !important;
  overflow-x: auto !important;
  scroll-snap-type: x mandatory !important;
  scroll-behavior: smooth !important;
  -webkit-overflow-scrolling: touch !important;
  scrollbar-width: none !important;
}
.spotify-playlists-track::-webkit-scrollbar { display: none !important; }
.spotify-playlist-tile {
  scroll-snap-align: start !important;
  flex: 0 0 calc((100% - 3 * 8px) / 4) !important;
  display: flex !important;
  flex-direction: column !important;
  align-items: center !important;
  gap: 6px !important;
  background: transparent !important;
  border: none !important;
  cursor: pointer !important;
  padding: 0 !important;
  min-width: 0 !important;
}
.spotify-playlist-tile-art {
  width: 100% !important;
  aspect-ratio: 1 / 1 !important;
  border-radius: 8px !important;
  /* Longhand, not the `background` shorthand — the shorthand implicitly
     resets background-image to none, and with !important that beat the
     inline `style="background-image:url(...)"` every tile sets per-
     playlist (js/dashboard.js's renderSpotifyPlaylistsCarousel()) no
     matter what, so no thumbnail — network cover art OR the local Liked
     Songs icon alike — could ever actually show. Leaving
     background-image undeclared here (not even without !important)
     lets that inline style set it freely; background-color is the
     fallback for playlists with no image (or before it's loaded). */
  background-color: #333333 !important;
  background-size: cover !important;
  background-position: center !important;
  background-repeat: no-repeat !important;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3) !important;
}
.spotify-playlist-tile.is-playing .spotify-playlist-tile-art {
  box-shadow: 0 0 0 2px #1DB954, 0 2px 6px rgba(0, 0, 0, 0.3) !important;
}
.spotify-playlist-tile-name {
  font-family: Circular, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
  font-size: 10.5px !important;
  font-weight: 600 !important;
  color: #ffffff !important;
  text-align: center !important;
  overflow: hidden !important;
  text-overflow: ellipsis !important;
  display: -webkit-box !important;
  -webkit-line-clamp: 2 !important;
  -webkit-box-orient: vertical !important;
  line-height: 1.25 !important;
  width: 100% !important;
}
.spotify-playlist-tile.is-playing .spotify-playlist-tile-name { color: #1DB954 !important; }

/* Track Metadata & Artwork */
.spotify-now { display: flex !important; align-items: center !important; gap: 12px !important; margin-bottom: 12px !important; min-width: 0 !important; }
.spotify-art {
  width: calc(60px * var(--sp-scale)) !important;
  height: calc(60px * var(--sp-scale)) !important;
  border-radius: 8px !important;
  /* background-color, not the `background` shorthand — the shorthand
     implicitly resets background-image to `none` for any component it
     doesn't mention, and with !important that permanently wins over the
     background-image the JS sets inline once a track loads (an inline
     style only beats a stylesheet rule when neither has !important) —
     the album art was rendering, just always hidden behind this. */
  background-color: #282828 !important;
  background-position: center !important;
  background-size: cover !important;
  background-repeat: no-repeat !important;
  flex-shrink: 0 !important;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.6) !important;
}
.spotify-art-demo { background-image: url('/icons/spotify_album_art.png') !important; background-size: cover !important; }
.spotify-meta { min-width: 0 !important; flex: 1 !important; }

/* Background art-style: fills the whole player with the album cover
   (::before) and fades it into the player's own dark background via a
   linear gradient that spans the full player height (::after), so the
   track info and controls stay legible without a separate scrim. The
   panel already clips to its border-radius via overflow:hidden, so the
   full-bleed layers pick that up for free. */
.spotify-panel.art-mode-background::before {
  content: "" !important;
  position: absolute !important;
  inset: 0 !important;
  background-image: var(--spotify-bg-image, none) !important;
  background-size: cover !important;
  background-position: center !important;
  z-index: 0 !important;
}
.spotify-panel.art-mode-background::after {
  content: "" !important;
  position: absolute !important;
  inset: 0 !important;
  background: linear-gradient(to bottom, rgba(18, 18, 18, 0) 0%, rgba(18, 18, 18, 0.55) 60%, #121212 100%) !important;
  z-index: 1 !important;
  pointer-events: none !important;
}
.spotify-panel.art-mode-background > .spotify-head,
.spotify-panel.art-mode-background > .spotify-now,
.spotify-panel.art-mode-background > .spotify-progress,
.spotify-panel.art-mode-background > .spotify-controls,
.spotify-panel.art-mode-background > #spotifyBody,
.spotify-panel.art-mode-background > #spotifyConnectBtn,
.spotify-panel.art-mode-background > .spotify-playlists-drawer {
  position: relative !important;
  z-index: 2 !important;
}
/* .resize-grip stays position:absolute (its base rule pins it to the
   panel's bottom-right corner) — switching it to position:relative like
   its siblings above would drop it into normal block flow instead,
   where a relative element's right:0/bottom:0 offsets are relative to
   its own natural flow position rather than the container's edge, so it
   renders as a plain block-level box flush against the LEFT edge
   instead of staying pinned to the corner. Only needs a z-index bump to
   sit above the background art layers, same as everything else here. */
.spotify-panel.art-mode-background > .resize-grip {
  z-index: 2 !important;
}
/* The small thumbnail swatch is redundant once the art fills the whole
   player, so it's hidden rather than doubled up. */
.spotify-panel.art-mode-background .spotify-art { display: none !important; }
.spotify-track-name {
  font-family: Circular, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
  font-weight: 700 !important;
  font-size: calc(15px * var(--sp-scale)) !important;
  color: #ffffff !important;
  line-height: 1.25 !important;
  overflow: hidden !important;
  text-overflow: ellipsis !important;
  white-space: nowrap !important;
}
.spotify-artist {
  font-family: Circular, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
  font-weight: 400 !important;
  font-size: calc(12px * var(--sp-scale)) !important;
  color: #b3b3b3 !important;
  margin-top: 3px !important;
  overflow: hidden !important;
  text-overflow: ellipsis !important;
  white-space: nowrap !important;
}

/* Progress Bar */
.spotify-progress { display: flex !important; align-items: center !important; gap: 8px !important; margin-bottom: 14px !important; }
.spotify-time {
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace !important;
  font-size: calc(10px * var(--sp-scale)) !important;
  color: #b3b3b3 !important;
  flex-shrink: 0 !important;
}
.spotify-bar {
  flex: 1 !important;
  height: 4px !important;
  border-radius: 2px !important;
  background: #4d4d4d !important;
  overflow: visible !important;
  position: relative !important;
  cursor: pointer !important;
}
.spotify-bar-fill {
  height: 100% !important;
  width: 0% !important;
  background: #ffffff !important;
  border-radius: 2px !important;
  position: relative !important;
  transition: background 0.15s ease !important;
}
.spotify-progress:hover .spotify-bar-fill {
  background: #1DB954 !important;
}
.spotify-progress:hover .spotify-bar-fill::after {
  content: "" !important;
  position: absolute !important;
  right: -5px !important;
  top: -4px !important;
  width: 12px !important;
  height: 12px !important;
  border-radius: 50% !important;
  background: #ffffff !important;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4) !important;
}

/* Controls & Buttons */
.spotify-controls {
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
  gap: calc(18px * var(--sp-scale)) !important;
}
.spotify-btn {
  width: calc(40px * var(--sp-scale)) !important;
  height: calc(40px * var(--sp-scale)) !important;
  border-radius: 50% !important;
  display: grid !important;
  place-items: center !important;
  background: transparent !important;
  border: none !important;
  box-shadow: none !important;
  color: transparent !important;
  font-size: 0 !important;
  line-height: 0 !important;
  cursor: pointer !important;
  padding: 0 !important;
  overflow: hidden !important;
  transition: transform 0.15s ease, opacity 0.15s ease !important;
}
.spotify-btn img {
  width: 100% !important;
  height: 100% !important;
  display: block !important;
  object-fit: contain !important;
  pointer-events: none !important;
}
.spotify-btn:hover { transform: scale(1.08) !important; }
.spotify-btn-play {
  width: calc(52px * var(--sp-scale)) !important;
  height: calc(52px * var(--sp-scale)) !important;
  background: transparent !important;
  border: none !important;
  box-shadow: none !important;
}
.spotify-btn-play img {
  width: 100% !important;
  height: 100% !important;
}
.spotify-btn-play:hover { transform: scale(1.06) !important; }
.spotify-btn:disabled { opacity: 0.35 !important; pointer-events: none !important; transform: none !important; }

/* Corner Resize Grip */
.resize-grip {
  position: absolute !important;
  right: 0 !important;
  bottom: 0 !important;
  width: 30px !important;
  height: 30px !important;
  cursor: ns-resize !important;
  touch-action: none !important;
  background: linear-gradient(135deg, transparent 50%, #4d4d4d 50%, #4d4d4d 60%, transparent 60%, transparent 68%, #4d4d4d 68%, #4d4d4d 78%, transparent 78%) !important;
}
.resize-grip:hover {
  background: linear-gradient(135deg, transparent 50%, #1DB954 50%, #1DB954 60%, transparent 60%, transparent 68%, #1DB954 68%, #1DB954 78%, transparent 78%) !important;
}
.spotify-panel-preview { margin-bottom: 6px !important; }

/* Screensaver variant: same information, none of the chrome. Flat colors,
   no album art, no gradients — cheapest thing to render while still
   showing what's playing and how far in. */
.screensaver-value-xs{ font-size:calc(min(7vw, 30px) * var(--sp-scale, 1)); }
.screensaver-spotify-time{
  font-family:var(--font-mono);
  font-size:calc(13px * var(--sp-scale, 1));
  color:#7a7a7a; margin-top:6px;
}

/* ==========================================================================
   APP THEMES (Harley Iron & Fire, Synthwave, Stealth, Desert, Apex, Light)
   ========================================================================== */

/* ==========================================================================
   APP THEMES (Harley Iron & Fire, Synthwave, Stealth, Desert, Apex, Light)
   ========================================================================== */

/* 1. Harley Davidson — Iron & Fire */
body.theme-harley {
  --bg-void: #0b0c0e;
  --bg-panel: #141518;
  --bg-panel-raised: #1d1e23;
  --bg-panel-hover: #272a31;
  --border-subtle: #2d3038;
  --border-strong: #ff5500;

  --card-bg-gradient: linear-gradient(145deg, #191b22 0%, #111216 100%);
  --speedo-bg: radial-gradient(circle at 50% 30%, #262934 0%, #111215 75%);
  --btn-primary-bg: linear-gradient(135deg, #ff5500 0%, #d94400 100%);
  --btn-primary-color: #ffffff;
  --btn-primary-border: #ff7722;
  --btn-secondary-bg: #1d1e23;
  --btn-secondary-border: #383c47;

  --neon-green: #ff5500; /* Molten Fire Orange */
  --neon-green-dim: #993300;
  --neon-green-glow: rgba(255, 85, 0, 0.4);
  --cyan: #ffaa00; /* Polished Chrome Gold */
  --alert-red: #ff2200;
  --warn-amber: #ffaa00;

  --text-primary: #f2f4f8;
  --text-muted: #9aa1ad;
  --text-dim: #5c626d;
  background-image:
    radial-gradient(circle at 15% 0%, rgba(255,85,0,0.12), transparent 45%),
    radial-gradient(circle at 85% 100%, rgba(255,170,0,0.08), transparent 50%);
}

/* 2. Cyber Synthwave */
body.theme-synthwave {
  --bg-void: #0d0818;
  --bg-panel: #150d26;
  --bg-panel-raised: #1f1437;
  --bg-panel-hover: #2c1b4e;
  --border-subtle: #341e5b;
  --border-strong: #ff007f;

  --card-bg-gradient: linear-gradient(145deg, #1d1133 0%, #120a20 100%);
  --speedo-bg: radial-gradient(circle at 50% 30%, #2d1a4d 0%, #140c26 75%);
  --btn-primary-bg: linear-gradient(135deg, #ff007f 0%, #cc0066 100%);
  --btn-primary-color: #ffffff;
  --btn-primary-border: #ff3399;
  --btn-secondary-bg: #1f1437;
  --btn-secondary-border: #422673;

  --neon-green: #ff007f; /* Hot Magenta */
  --neon-green-dim: #99004d;
  --neon-green-glow: rgba(255, 0, 127, 0.45);
  --cyan: #00f0ff; /* Electric Cyan */
  --alert-red: #ff0044;
  --warn-amber: #ffb700;

  --text-primary: #f7efff;
  --text-muted: #a795c4;
  --text-dim: #605179;
  background-image:
    radial-gradient(circle at 20% 0%, rgba(255,0,127,0.14), transparent 45%),
    radial-gradient(circle at 80% 100%, rgba(0,240,255,0.12), transparent 50%);
}

/* 3. Carbon Stealth */
body.theme-stealth {
  --bg-void: #070809;
  --bg-panel: #0d0f12;
  --bg-panel-raised: #14171d;
  --bg-panel-hover: #1c212a;
  --border-subtle: #202733;
  --border-strong: #3b4759;

  --card-bg-gradient: linear-gradient(145deg, #12151c 0%, #090b0e 100%);
  --speedo-bg: radial-gradient(circle at 50% 30%, #1a2029 0%, #0c0f13 75%);
  --btn-primary-bg: linear-gradient(135deg, #42a5f5 0%, #1976d2 100%);
  --btn-primary-color: #ffffff;
  --btn-primary-border: #64b5f6;
  --btn-secondary-bg: #14171d;
  --btn-secondary-border: #293242;

  --neon-green: #64b5f6; /* Glacier Ice */
  --neon-green-dim: #1e5387;
  --neon-green-glow: rgba(100, 181, 246, 0.35);
  --cyan: #90caf9;
  --alert-red: #ef5350;
  --warn-amber: #ffa726;

  --text-primary: #e8ecef;
  --text-muted: #808e9b;
  --text-dim: #485460;
  background-image:
    radial-gradient(circle at 10% 10%, rgba(100,181,246,0.06), transparent 40%),
    radial-gradient(circle at 90% 90%, rgba(30,83,135,0.1), transparent 40%);
}

/* 4. Desert Rally */
body.theme-desert {
  --bg-void: #110d09;
  --bg-panel: #1a140f;
  --bg-panel-raised: #241c15;
  --bg-panel-hover: #31241c;
  --border-subtle: #3a2b20;
  --border-strong: #d97706;

  --card-bg-gradient: linear-gradient(145deg, #221a13 0%, #140f0a 100%);
  --speedo-bg: radial-gradient(circle at 50% 30%, #312318 0%, #1a140f 75%);
  --btn-primary-bg: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
  --btn-primary-color: #0f0b07;
  --btn-primary-border: #fbbf24;
  --btn-secondary-bg: #241c15;
  --btn-secondary-border: #423122;

  --neon-green: #f59e0b; /* Sunset Gold */
  --neon-green-dim: #92400e;
  --neon-green-glow: rgba(245, 158, 11, 0.4);
  --cyan: #fb923c; /* Copper */
  --alert-red: #ef4444;
  --warn-amber: #f59e0b;

  --text-primary: #fbf3eb;
  --text-muted: #a89485;
  --text-dim: #635347;
  background-image:
    radial-gradient(circle at 15% 0%, rgba(245,158,11,0.1), transparent 45%),
    radial-gradient(circle at 85% 100%, rgba(251,146,60,0.08), transparent 50%);
}

/* 5. Titanium Apex */
body.theme-apex {
  --bg-void: #0a0e13;
  --bg-panel: #111720;
  --bg-panel-raised: #18212d;
  --bg-panel-hover: #222d3e;
  --border-subtle: #2a374a;
  --border-strong: #ccff00;

  --card-bg-gradient: linear-gradient(145deg, #151e2b 0%, #0d121a 100%);
  --speedo-bg: radial-gradient(circle at 50% 30%, #202c3d 0%, #111720 75%);
  --btn-primary-bg: linear-gradient(135deg, #ccff00 0%, #a3cc00 100%);
  --btn-primary-color: #0c1203;
  --btn-primary-border: #ddff33;
  --btn-secondary-bg: #18212d;
  --btn-secondary-border: #2e3e54;

  --neon-green: #ccff00; /* High-Vis Telemetry Yellow */
  --neon-green-dim: #739900;
  --neon-green-glow: rgba(204, 255, 0, 0.4);
  --cyan: #00e5ff; /* Laser Blue */
  --alert-red: #ff2a4b;
  --warn-amber: #ffb700;

  --text-primary: #edf3fa;
  --text-muted: #8b9bb0;
  --text-dim: #4e5e73;
  background-image:
    radial-gradient(circle at 15% 0%, rgba(204,255,0,0.08), transparent 45%),
    radial-gradient(circle at 85% 100%, rgba(0,229,255,0.07), transparent 50%);
}

/* 6. Silver Touring (Light: Polished Silver & Cobalt Blue) */
body.theme-silver {
  --bg-void: #e2e8f0;
  --bg-panel: #ffffff;
  --bg-panel-raised: #f1f5f9;
  --bg-panel-hover: #cbd5e1;
  --border-subtle: #cbd5e1;
  --border-strong: #2563eb;

  --btn-primary-bg: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
  --btn-primary-color: #ffffff;
  --btn-primary-border: #3b82f6;
  --btn-secondary-bg: #f1f5f9;
  --btn-secondary-border: #cbd5e1;

  --neon-green: #2563eb; /* Cobalt Blue */
  --neon-green-dim: #1d4ed8;
  --neon-green-glow: rgba(37, 99, 235, 0.25);
  --cyan: #0284c7;
  --alert-red: #dc2626;
  --warn-amber: #d97706;

  --text-primary: #1e293b;
  --text-muted: #475569;
  --text-dim: #64748b;
  --menu-panel-bg: rgba(255,255,255,0.92);
  background-image:
    radial-gradient(circle at 15% 0%, rgba(37,99,235,0.07), transparent 45%),
    radial-gradient(circle at 85% 100%, rgba(2,132,199,0.06), transparent 50%);
}

/* 7. Desert Dunes (Light: Warm Sand & Terracotta Copper) */
body.theme-sand {
  --bg-void: #f3eee8;
  --bg-panel: #fbf8f5;
  --bg-panel-raised: #eee7df;
  --bg-panel-hover: #e5dbcf;
  --border-subtle: #dcd0c2;
  --border-strong: #ea580c;

  --btn-primary-bg: linear-gradient(135deg, #ea580c 0%, #c2410c 100%);
  --btn-primary-color: #ffffff;
  --btn-primary-border: #f97316;
  --btn-secondary-bg: #eee7df;
  --btn-secondary-border: #dcd0c2;

  --neon-green: #ea580c; /* Terracotta Copper */
  --neon-green-dim: #c2410c;
  --neon-green-glow: rgba(234, 88, 12, 0.25);
  --cyan: #d97706;
  --alert-red: #dc2626;
  --warn-amber: #d97706;

  --text-primary: #271c19;
  --text-muted: #5c4740;
  --text-dim: #7d655c;
  --menu-panel-bg: rgba(255,255,255,0.92);
  background-image:
    radial-gradient(circle at 15% 0%, rgba(234,88,12,0.07), transparent 45%),
    radial-gradient(circle at 85% 100%, rgba(217,119,6,0.06), transparent 50%);
}

/* 8. High-Vis Safety (Light: Daylight Lime & Slate) */
body.theme-highvis {
  --bg-void: #edf2f7;
  --bg-panel: #ffffff;
  --bg-panel-raised: #e2e8f0;
  --bg-panel-hover: #cbd5e0;
  --border-subtle: #cbd5e0;
  --border-strong: #65a30d;

  --btn-primary-bg: linear-gradient(135deg, #65a30d 0%, #4d7c0f 100%);
  --btn-primary-color: #ffffff;
  --btn-primary-border: #84cc16;
  --btn-secondary-bg: #e2e8f0;
  --btn-secondary-border: #cbd5e0;

  --neon-green: #4d7c0f; /* High-Vis Lime */
  --neon-green-dim: #3f6212;
  --neon-green-glow: rgba(101, 163, 13, 0.25);
  --cyan: #0284c7;
  --alert-red: #dc2626;
  --warn-amber: #ca8a04;

  --text-primary: #0f172a;
  --text-muted: #334155;
  --text-dim: #64748b;
  --menu-panel-bg: rgba(255,255,255,0.92);
  background-image:
    radial-gradient(circle at 15% 0%, rgba(101,163,13,0.08), transparent 45%),
    radial-gradient(circle at 85% 100%, rgba(2,132,199,0.05), transparent 50%);
}

/* 9. Alpine Snow (Light: Ice White & Sky Blue) */
body.theme-alpine {
  --bg-void: #f0f7ff;
  --bg-panel: #ffffff;
  --bg-panel-raised: #e1f0ff;
  --bg-panel-hover: #cce4ff;
  --border-subtle: #b8daff;
  --border-strong: #0284c7;

  --btn-primary-bg: linear-gradient(135deg, #0284c7 0%, #0369a1 100%);
  --btn-primary-color: #ffffff;
  --btn-primary-border: #38bdf8;
  --btn-secondary-bg: #e1f0ff;
  --btn-secondary-border: #b8daff;

  --neon-green: #0284c7; /* Sky Blue */
  --neon-green-dim: #0369a1;
  --neon-green-glow: rgba(2, 132, 199, 0.25);
  --cyan: #06b6d4;
  --alert-red: #e11d48;
  --warn-amber: #d97706;

  --text-primary: #0c4a6e;
  --text-muted: #1e3a8a;
  --text-dim: #475569;
  --menu-panel-bg: rgba(255,255,255,0.92);
  background-image:
    radial-gradient(circle at 15% 0%, rgba(2,132,199,0.08), transparent 45%),
    radial-gradient(circle at 85% 100%, rgba(6,182,212,0.07), transparent 50%);
}

/* 10. Slate Pro — Indigo (Dark, professional, inspired by this redesign) */
body.theme-indigo {
  --bg-void: #0c0d14;
  --bg-panel: #15161f;
  --bg-panel-raised: #1c1e2b;
  --bg-panel-hover: #242637;
  --border-subtle: #262838;
  --border-strong: #3a3d54;

  --card-bg-gradient: linear-gradient(145deg, #1a1c29 0%, #101120 100%);
  --speedo-bg: radial-gradient(circle at 50% 30%, #232537 0%, #14151f 75%);
  --btn-primary-bg: linear-gradient(135deg, #8b7cf6 0%, #6d5ce8 100%);
  --btn-primary-color: #ffffff;
  --btn-primary-border: #a498f9;
  --btn-secondary-bg: #1c1e2b;
  --btn-secondary-border: #33354a;

  --neon-green: #8b7cf6; /* Indigo */
  --neon-green-dim: #5b4fc4;
  --neon-green-glow: rgba(139, 124, 246, 0.3);
  --cyan: #38bdf8;
  --alert-red: #f87171;
  --warn-amber: #fbbf24;

  --text-primary: #f1f0f7;
  --text-muted: #948fa8;
  --text-dim: #5c5870;
  background-image:
    radial-gradient(circle at 15% 0%, rgba(139,124,246,0.08), transparent 45%),
    radial-gradient(circle at 85% 100%, rgba(56,189,248,0.06), transparent 50%);
}

/* 11. Cloud Pro (Light, professional, inspired by this redesign) */
body.theme-cloud {
  --bg-void: #eef1f5;
  --bg-panel: #ffffff;
  --bg-panel-raised: #f6f8fb;
  --bg-panel-hover: #eaeef4;
  --border-subtle: #e2e6ee;
  --border-strong: #c7cede;

  --btn-primary-bg: linear-gradient(135deg, #2f6fed 0%, #1d54c9 100%);
  --btn-primary-color: #ffffff;
  --btn-primary-border: #4c86f5;
  --btn-secondary-bg: #f6f8fb;
  --btn-secondary-border: #dde2ec;

  --neon-green: #2f6fed; /* Confident blue */
  --neon-green-dim: #1d54c9;
  --neon-green-glow: rgba(47, 111, 237, 0.16);
  --cyan: #06b6d4;
  --alert-red: #dc2626;
  --warn-amber: #d97706;

  --text-primary: #161a22;
  --text-muted: #464e5c;
  --text-dim: #6b7280;
  --menu-panel-bg: rgba(255,255,255,0.92);
  background-image:
    radial-gradient(circle at 15% 0%, rgba(47,111,237,0.05), transparent 45%),
    radial-gradient(circle at 85% 100%, rgba(6,182,212,0.04), transparent 50%);
}

/* ---------- Theme Selection Cards ---------- */
.theme-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 10px;
  margin-bottom: 16px;
}

.theme-card {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  padding: 10px;
  background: var(--bg-panel-raised);
  border: 1px solid var(--border-subtle);
  border-radius: 12px;
  cursor: pointer;
  text-align: left;
  transition: all 0.2s ease;
}
.theme-card-create {
  align-items: center;
  justify-content: center;
  text-align: center;
  border-style: dashed;
}
.theme-card-create-icon {
  width: 28px; height: 28px;
  display: flex; align-items: center; justify-content: center;
  font-size: 18px; font-weight: 700;
  color: var(--neon-green);
  background: var(--bg-panel-hover);
  border-radius: 50%;
}
.theme-card-delete {
  position: absolute; top: 6px; right: 6px; z-index: 2;
  width: 20px; height: 20px;
  display: flex; align-items: center; justify-content: center;
  font-size: 11px; font-weight: 700;
  color: var(--text-muted);
  background: var(--bg-panel);
  border: 1px solid var(--border-subtle);
  border-radius: 50%;
  cursor: pointer;
}
.theme-card-delete:hover { color: #ffffff; background: var(--alert-red); border-color: var(--alert-red); }

.theme-card:hover {
  border-color: var(--neon-green);
  transform: translateY(-2px);
  box-shadow: 0 6px 16px rgba(0,0,0,0.3);
}

.theme-card.active {
  border-color: var(--neon-green);
  background: var(--bg-panel-hover);
  box-shadow: 0 0 12px var(--neon-green-glow);
}

.theme-swatch-bar {
  display: flex;
  width: 100%;
  height: 20px;
  border-radius: 6px;
  overflow: hidden;
  border: 1px solid rgba(255,255,255,0.1);
}

.theme-swatch-bar span {
  flex: 1;
  height: 100%;
}

.theme-card-info strong {
  display: block;
  font-family: var(--font-display);
  font-size: 13px;
  color: var(--text-primary);
}

.theme-card-info small {
  display: block;
  font-size: 11px;
  color: var(--text-muted);
}

.color-chip {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  border: 2px solid var(--border-subtle);
  cursor: pointer;
  transition: transform 0.15s ease;
}

.color-chip:hover {
  transform: scale(1.2);
  border-color: var(--text-primary);
}
.screensaver-spotify-controls{
  display:flex; align-items:center; justify-content:center;
  gap:calc(22px * var(--sp-scale, 1)); margin-top:18px;
}
.screensaver-spotify-btn{
  width:calc(58px * var(--sp-scale, 1));
  height:calc(58px * var(--sp-scale, 1));
  border-radius:50%;
  display:grid; place-items:center;
  background:transparent;
  border:2px solid #444;
  color:#fff;
  font-size:calc(22px * var(--sp-scale, 1));
  cursor:pointer; padding:0; line-height:1;
  -webkit-tap-highlight-color:transparent;
}

footer.foot{
  text-align:center; color:var(--text-dim); font-family:var(--font-mono);
  font-size:11px; padding:30px 10px 10px;
}

/* Screensaver widget reordering list in Settings */
.screensaver-widget-choices {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 10px;
}
.ss-widget-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  background: var(--bg-panel-raised, rgba(255, 255, 255, 0.05));
  border: 1px solid var(--border-subtle, rgba(255, 255, 255, 0.1));
  border-radius: 8px;
  padding: 8px 12px;
  transition: background 0.2s, border-color 0.2s;
}
.ss-widget-item:hover {
  background: var(--bg-panel-hover, rgba(255, 255, 255, 0.08));
  border-color: rgba(255, 255, 255, 0.2);
}
.ss-widget-left {
  display: flex;
  align-items: center;
  gap: 10px;
  font-weight: 500;
  font-size: 14px;
  color: var(--text-primary, #fff);
}
.ss-drag-grip {
  cursor: grab;
  color: rgba(255, 255, 255, 0.4);
  font-size: 16px;
  user-select: none;
}
.ss-widget-reorder-btns {
  display: flex;
  gap: 4px;
}
.ss-reorder-btn {
  background: rgba(255, 255, 255, 0.1);
  border: 1px solid rgba(255, 255, 255, 0.15);
  color: #fff;
  width: 30px;
  height: 30px;
  border-radius: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  font-size: 12px;
  transition: all 0.2s ease;
}
.ss-reorder-btn:hover:not(:disabled) {
  background: var(--neon-green, #1db954);
  color: #000;
  border-color: var(--neon-green, #1db954);
  transform: scale(1.05);
}
.ss-reorder-btn:disabled {
  opacity: 0.25;
  cursor: not-allowed;
}

/* Rider Maps & Full-Screen Views */
.full-screen-view {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  background: var(--bg-dark, #0b0f17);
  z-index: 500;
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
}

/* Full-Screen Rider Maps & Performance Views */
.full-screen-view {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  background: #0b0f17;
  z-index: 500;
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
  /* This duplicates the earlier .full-screen-view rule (same file) but
     comes later in the cascade, so at equal specificity it wins — and it
     used to hard-clip with overflow:hidden, silently overriding
     .hud-shell's own overflow-y:auto. On any view whose flex-column
     content is taller than the viewport (routinely true for the Track
     HUD in landscape, its own stated primary use case), that meant
     content past the fold wasn't just scrolled out of reach, it was
     unreachable full stop — including the back button once the gauges
     row's overflow (see .hud-gauges-row below) pushed into the topbar. */
  overflow-x: hidden;
  overflow-y: auto;
}

/* Floating controls replace the old top toolbar entirely — a full-width bar
   competed with the map for space and duplicated event listeners already
   registered by js/maps-nav.js's attachControlListeners() were what made the
   Layers button and Recenter silently misbehave (two handlers toggling/
   navigating against each other on one tap). Floating buttons keep a single
   owner per control and read as a more modern, map-first layout. */
.maps-fab-back,
.maps-fab {
  position: fixed;
  width: 46px;
  height: 46px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(15, 23, 42, 0.82);
  border: 1px solid rgba(255, 255, 255, 0.14);
  color: #f1f5f9;
  font-size: 18px;
  cursor: pointer;
  backdrop-filter: blur(10px);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.4);
  z-index: 510;
  transition: transform 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.maps-fab-back:hover,
.maps-fab:hover {
  border-color: rgba(255, 145, 0, 0.5);
  background: rgba(15, 23, 42, 0.95);
}
.maps-fab-back:active,
.maps-fab:active {
  transform: scale(0.94);
}
.maps-fab-back {
  top: 12px;
  left: 64px; /* clears the always-on hamburger .menu-btn at left:12px */
}
.maps-fab-stack {
  position: fixed;
  top: 12px;
  right: 16px;
  z-index: 510;
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.maps-fab-stack .maps-fab {
  position: static;
}

.maps-canvas {
  position: relative !important;
  flex: 1 !important;
  flex-grow: 1 !important;
  width: 100% !important;
  height: 100% !important;
  min-height: 400px !important;
  background: #e5e3df !important;
  display: block !important;
  overflow: hidden !important;
}

.leaflet-container {
  width: 100% !important;
  height: 100% !important;
  min-height: 400px !important;
  background: #e5e3df !important;
  z-index: 1 !important;
}

/* Current-location marker (Leaflet L.divIcon) — a pulsing dot instead of
   the bare, unstyled motorcycle emoji this previously rendered as (no CSS
   backed .motorcycle-marker-pin/.m-emoji/.m-pulse at all), and distinct
   from .maps-dest-marker below so "where I am" and "where I'm going" don't
   collapse into the same pin once a destination is passed to initMap(). */
.motorcycle-marker-pin {
  position: relative;
  width: 42px;
  height: 42px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.motorcycle-marker-pin .m-pulse {
  position: absolute;
  inset: -6px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(79, 140, 255, 0.35) 0%, rgba(79, 140, 255, 0) 70%);
  animation: motoMarkerPulse 1.8s ease-out infinite;
}
@keyframes motoMarkerPulse {
  0% { transform: scale(0.7); opacity: 0.9; }
  100% { transform: scale(1.9); opacity: 0; }
}
.motorcycle-marker-pin .m-emoji {
  position: relative;
  font-size: 20px;
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.5));
}

/* Destination pin (Leaflet only — Google's DirectionsRenderer already
   drops its own A/B markers) so a routed target has a visible endpoint of
   its own instead of only the dashed line drawn by navigateToCoords(). */
.maps-dest-marker {
  width: 30px;
  height: 30px;
  transform: translateY(-6px);
}
.maps-dest-marker svg {
  filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.45));
}

.maps-layer-drawer {
  position: fixed;
  top: 172px; /* clears the 3-button .maps-fab-stack above it */
  right: 16px;
  z-index: 510;
  background: rgba(15, 23, 42, 0.95);
  border: 1px solid rgba(255, 255, 255, 0.15);
  border-radius: 12px;
  padding: 14px 18px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.6);
  backdrop-filter: blur(12px);
  color: #fff;
  min-width: 240px;
  max-width: calc(100vw - 32px);
}
.maps-layer-drawer-title {
  font-family: var(--font-display);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #94a3b8;
  margin-bottom: 2px;
}
.maps-layer-option label {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  cursor: pointer;
  color: #e2e8f0;
}
.maps-layer-radius {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding-top: 8px;
  border-top: 1px solid rgba(255, 255, 255, 0.12);
}
.maps-layer-radius label {
  font-size: 13px;
  color: #e2e8f0;
}
.maps-layer-radius select {
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.18);
  color: #fff;
  border-radius: 8px;
  padding: 5px 8px;
  font-size: 12px;
}

.maps-footer-hud {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 16px;
  background: rgba(15, 23, 42, 0.95);
  border-top: 1px solid rgba(255, 255, 255, 0.1);
  z-index: 510;
  gap: 14px;
  backdrop-filter: blur(12px);
}
.maps-hud-stat {
  display: flex;
  flex-direction: column;
  align-items: center;
}
.maps-hud-stat .v {
  font-size: 18px;
  font-weight: 800;
  color: #00e5ff;
}
.maps-hud-stat .k {
  font-size: 10px;
  color: #94a3b8;
}
.maps-nav-instruction {
  font-weight: 700;
  font-size: 14px;
  color: #fff;
}
.maps-nav-sub {
  font-size: 12px;
  color: #94a3b8;
}

/* Leaflet divIcon for curvy-road/POI/event/dealer pins — a colored circle
   per category (checkered flag for race tracks, etc.) instead of a plain
   dot. Plain circle rather than a rotated teardrop — one less transform to
   get wrong on top of Leaflet's own icon positioning transform. */
.category-pin-marker { background: transparent; border: none; }
.category-pin {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  border: 2px solid #0b0f17;
  box-shadow: 0 3px 8px rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
}
.category-pin span {
  font-size: 15px;
  line-height: 1;
}
.road-sign-pin {
  width: 30px;
  height: 30px;
  filter: drop-shadow(0 3px 6px rgba(0, 0, 0, 0.5));
}
.road-sign-pin svg {
  width: 100%;
  height: 100%;
}

/* ---------- Destination search modal ---------- */
/* .full-screen-view sits at z-index:500 and this modal is opened from
   inside one (#mapsView), so it needs to sit above that layer the same way
   the Apex Drag settings modal does — the app's default .modal-overlay
   (z-index:100) would otherwise render behind the map. */
.maps-search-modal-overlay {
  z-index: 610;
}
.maps-search-modal-box {
  max-width: 480px;
}
.maps-search-modal-field {
  display: flex;
  gap: 8px;
  margin-bottom: 12px;
  flex-shrink: 0;
}
.maps-search-modal-field input {
  flex: 1;
  padding: 10px 12px;
  background: var(--bg-panel-raised);
  border: 1px solid var(--border-subtle);
  border-radius: 8px;
  color: var(--text-primary);
  font-size: 14px;
}
.maps-search-modal-results {
  flex: 1;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 16px;
}
.maps-search-category-title {
  font-family: var(--font-display);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin-bottom: 8px;
}
.maps-search-category-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.maps-search-result {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 12px;
  background: var(--bg-panel-raised);
  border: 1px solid var(--border-subtle);
  border-radius: 10px;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.maps-search-result:hover {
  border-color: rgba(255, 145, 0, 0.5);
  background: var(--bg-panel-hover, var(--bg-panel-raised));
}
.maps-search-result-name {
  font-weight: 600;
  font-size: 13px;
  color: var(--text-primary);
}
.maps-search-result-meta {
  font-size: 11px;
  color: var(--text-muted);
  margin-top: 2px;
}
.maps-search-result-distance {
  font-family: var(--font-mono);
  font-size: 12px;
  color: #ff9100;
  white-space: nowrap;
  flex-shrink: 0;
}
.maps-search-result > div:not(.maps-search-result-thumb):not(.maps-search-result-distance) {
  flex: 1;
  min-width: 0;
}
.maps-search-result-thumb {
  width: 44px;
  height: 44px;
  border-radius: 8px;
  object-fit: cover;
  flex-shrink: 0;
  cursor: pointer;
  border: 1px solid var(--border-subtle);
}

/* ---------- Where to Ride page ---------- */
.wtr-item {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px 12px;
  padding: 12px;
  background: var(--bg-panel-raised);
  border: 1px solid var(--border-subtle);
  border-radius: 10px;
  transition: border-color 0.15s ease;
}
.wtr-item:hover {
  border-color: rgba(255, 145, 0, 0.5);
}
.wtr-item-body {
  flex: 1 1 200px;
  min-width: 0;
}
.wtr-item-actions {
  display: flex;
  gap: 8px;
  flex-shrink: 0;
  width: 100%;
}
@media (min-width: 480px) {
  .wtr-item-actions {
    width: auto;
  }
}
.wtr-item-actions .btn {
  flex: 1;
  white-space: nowrap;
  padding: 8px 14px;
  font-size: 12px;
}

/* ---------- Fullscreen POI photo viewer ---------- */
.poi-image-modal-overlay {
  z-index: 620;
  flex-direction: column;
  background: rgba(3, 4, 6, 0.94);
  padding: 0;
}
.poi-image-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 20px;
  flex-shrink: 0;
}
.poi-image-modal-header h3 {
  margin: 0;
  font-size: 15px;
  color: #fff;
}
.poi-image-modal-body {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  padding: 0 12px;
}
.poi-image-modal-body img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  border-radius: 8px;
}
.poi-image-modal-arrow {
  background: rgba(0, 0, 0, 0.5);
  border: none;
  color: #fff;
  font-size: 28px;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  cursor: pointer;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 2;
}
.poi-image-modal-arrow-prev {
  position: absolute;
  left: 12px;
}
.poi-image-modal-arrow-next {
  position: absolute;
  right: 12px;
}
.poi-image-modal-footer {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 14px 20px calc(14px + env(safe-area-inset-bottom, 0px));
  flex-shrink: 0;
}
.poi-image-modal-dots {
  display: flex;
  gap: 6px;
}
.poi-image-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.3);
  cursor: pointer;
}
.poi-image-dot.active {
  background: #ff9100;
}
.poi-image-modal-counter {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-muted, #94a3b8);
}

/* ---------- Floating turn-by-turn directions button + modal ---------- */
.maps-fab-directions {
  position: fixed;
  left: 50%;
  bottom: 96px;
  transform: translateX(-50%);
  z-index: 510;
  display: none; /* shown only while a route is active */
  align-items: center;
  gap: 8px;
  padding: 10px 18px;
  border-radius: 999px;
  background: rgba(255, 145, 0, 0.94);
  color: #1a0f05;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 13px;
  border: none;
  cursor: pointer;
  box-shadow: 0 8px 20px rgba(255, 145, 0, 0.35);
}
.maps-fab-directions.is-visible {
  display: flex;
}
.maps-directions-modal-overlay {
  z-index: 610;
}
.maps-directions-modal-box {
  max-width: 480px;
}
.maps-directions-list {
  flex: 1;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.maps-directions-step {
  display: flex;
  gap: 12px;
  padding: 12px 4px;
  border-bottom: 1px solid var(--border-subtle);
  font-size: 13px;
  color: var(--text-primary);
  line-height: 1.5;
}
.maps-directions-step:last-child {
  border-bottom: none;
}
.maps-directions-step-num {
  font-family: var(--font-mono);
  font-weight: 700;
  color: #ff9100;
  flex-shrink: 0;
  width: 22px;
}
.maps-directions-step-dist {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--text-muted);
  margin-top: 3px;
}

/* ---------- ride replay: 3D map wrap + status card ---------- */
.replay-map-wrap{
  position:relative;
  border-radius:18px;
  overflow:hidden;
  border:1px solid var(--border-strong);
  background:#0a0c11;
  margin:0 -16px; /* bleeds past the parent .panel's 16px side padding so the map spans its full width */
}
.replay-map-wrap #map{
  width:100%; height:min(60vh, 460px); min-height:320px;
}
.replay-loading-overlay{
  position:absolute; inset:0; z-index:20;
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:12px;
  background:#0a0c11;
  transition:opacity 0.4s ease;
}
.replay-loading-overlay.hidden{
  opacity:0; pointer-events:none;
}
.replay-loading-spinner{
  width:34px; height:34px;
  border:3px solid rgba(255,255,255,0.15);
  border-top-color:#4f8cff;
  border-radius:50%;
  animation:replay-spin 0.85s linear infinite;
}
@keyframes replay-spin{ to{ transform:rotate(360deg); } }
.replay-loading-text{
  font-family:var(--font-mono); font-size:12.5px; color:rgba(255,255,255,0.7);
  letter-spacing:0.02em;
}
.replay-loading-bar{
  width:min(220px, 60%); height:4px;
  background:rgba(255,255,255,0.12);
  border-radius:2px;
  overflow:hidden;
}
.replay-loading-bar-fill{
  height:100%; width:0%;
  background:#4f8cff;
  border-radius:2px;
  transition:width 0.25s ease;
}
.replay-status{
  /* Sits flush at the top of the map, spanning its full width — the
     fullscreen button and MapLibre's NavigationControl (added via
     addControl in ride-detail.js) both live at the bottom corners now,
     so the top edge is free. */
  position:absolute; left:10px; right:10px; top:10px; z-index:5;
  background:rgba(11,14,20,0.65);
  backdrop-filter:blur(10px);
  border:1px solid rgba(255,255,255,0.08);
  border-radius:16px;
  padding:12px 14px;
  box-shadow:0 12px 30px rgba(0,0,0,0.45);
  color:#eef1f5;
}
.replay-status-top{
  display:flex; align-items:flex-end; justify-content:space-between; gap:12px;
}
.replay-status-label{
  font-family:var(--font-display); font-size:10px; letter-spacing:0.1em; text-transform:uppercase;
  color:#8b93a3; margin-bottom:2px;
}
.replay-status-value{
  font-family:var(--font-mono); font-weight:800; font-size:32px; line-height:1; color:#fff;
  display:flex; align-items:baseline; gap:4px;
}
.replay-status-unit{ font-size:13px; font-weight:600; color:#8b93a3; }
.replay-status-weather{
  display:flex; align-items:center; gap:8px;
  background:rgba(255,255,255,0.06);
  border:1px solid rgba(255,255,255,0.08);
  border-radius:12px;
  padding:6px 10px;
  font-family:var(--font-mono); font-size:13px; color:#dfe4ea;
  flex-shrink:0;
}
.replay-weather-icon{ font-size:20px; line-height:1; }
.replay-status-location{
  margin-top:10px; padding-top:10px;
  border-top:1px solid rgba(255,255,255,0.08);
  font-size:13px; color:#c3cad6;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}

@media (max-width:480px){
  .replay-status-value{ font-size:26px; }
  .replay-status-weather{ font-size:12px; padding:5px 8px; }
}

/* ---------- ride replay: perspective selector ---------- */
.replay-perspective-group{
  display:flex;
  gap:8px;
  overflow-x:auto;
  margin:10px 0 12px;
  -webkit-overflow-scrolling:touch;
  /* width:0 + min-width:100% (not just width:100%) is deliberate: .panel
     is a plain block descendant of .content-grid's grid track, not a grid
     item itself, so the browser's "scrollable overflow gets zero
     automatic minimum size" rule (which only applies to flex/grid items)
     doesn't kick in here — without this, this row's 4 unwrapped pill
     buttons set its own shrink-to-fit width, which then bubbles up as
     .panel's min-content and stretches the whole card past the viewport.
     width:0 makes this box report zero width during that intrinsic-sizing
     pass; min-width:100% then fills the real, already-resolved container
     width during normal layout. */
  width:0;
  min-width:100%;
  box-sizing:border-box;
}
.replay-perspective-btn{
  flex:0 0 auto;
  min-height:var(--touch);
  padding:0 16px;
  border-radius:999px;
  border:1px solid var(--border-subtle);
  background:var(--bg-panel-raised);
  color:var(--text-muted);
  font-family:var(--font-display);
  font-weight:600;
  font-size:12.5px;
  letter-spacing:0.03em;
  text-transform:uppercase;
  cursor:pointer;
  white-space:nowrap;
  transition:background 0.15s, color 0.15s, border-color 0.15s;
}
.replay-perspective-btn:disabled{ opacity:0.4; cursor:default; }
.replay-perspective-btn.active{
  background:var(--neon-green);
  border-color:var(--neon-green-dim);
  color:#02110a;
}

/* ---------- ride replay: fullscreen toggle ---------- */
.replay-fullscreen-btn{
  position:absolute;
  bottom:10px; left:10px; z-index:6;
  width:var(--touch); height:var(--touch);
  display:grid; place-items:center;
  border-radius:10px;
  border:1px solid rgba(255,255,255,0.14);
  background:rgba(9,12,17,0.72);
  backdrop-filter:blur(6px);
  color:#e8ecf3;
  font-size:18px;
  line-height:1;
  cursor:pointer;
}
.replay-fullscreen-btn:hover{ background:rgba(9,12,17,0.9); }
.replay-fullscreen-btn.is-active{ color:var(--neon-green); border-color:var(--neon-green-dim); }

.replay-map-wrap:fullscreen{
  margin:0;
  border-radius:0;
  background:#0a0c11;
}
.replay-map-wrap:fullscreen #map{ height:100%; }
.replay-map-wrap:-webkit-full-screen{ margin:0; border-radius:0; }
.replay-map-wrap:-webkit-full-screen #map{ height:100%; }

/* ---------- ride replay: dev tuning tools ---------- */
.replay-dev-tools{
  margin-top:14px;
  border:1px dashed var(--border-strong);
  border-radius:12px;
  padding:10px 12px;
}
.replay-dev-tools summary{
  cursor:pointer;
  font-family:var(--font-mono);
  font-size:12px;
  font-weight:600;
  color:var(--text-muted);
  letter-spacing:0.04em;
  text-transform:uppercase;
}
.replay-dev-sliders{
  display:grid;
  grid-template-columns:repeat(auto-fit, minmax(min(160px, 100%), 1fr));
  gap:12px 16px;
  margin:12px 0 10px;
  min-width:0;
}
.replay-dev-sliders label{
  display:flex;
  flex-direction:column;
  gap:6px;
  font-size:11px;
  color:var(--text-muted);
  font-family:var(--font-mono);
  min-width:0;
}
.replay-dev-sliders input[type=range]{
  accent-color:var(--neon-green);
  min-height:24px;
  width:100%;
  min-width:0;
}
.replay-dev-readout{
  margin:10px 0 0;
  padding:10px 12px;
  border-radius:8px;
  background:rgba(0,0,0,0.25);
  color:var(--neon-green);
  font-family:var(--font-mono);
  font-size:11px;
  line-height:1.6;
  white-space:pre-wrap;
  overflow-x:auto;
}

/* ---------- ride replay: rider badge marker ---------- */
.replay-3d-marker{
  position:relative;
  width:44px; height:44px;
  transform:translateY(-6px);
}
.rider-marker-shadow{
  position:absolute; left:50%; bottom:-4px; transform:translateX(-50%);
  width:30px; height:10px; border-radius:50%;
  background:rgba(0,0,0,0.5); filter:blur(3px);
}
.rider-marker-pulse{
  position:absolute; inset:-8px; border-radius:50%;
  background:radial-gradient(circle, rgba(79,140,255,0.35) 0%, rgba(79,140,255,0) 70%);
  animation:riderPulse 1.8s ease-out infinite;
}
@keyframes riderPulse{
  0%{ transform:scale(0.7); opacity:0.9; }
  100%{ transform:scale(1.9); opacity:0; }
}
.rider-marker-badge{
  position:relative; width:44px; height:44px;
  filter:drop-shadow(0 4px 10px rgba(0,0,0,0.5));
}
.rider-marker-svg{ width:100%; height:100%; }
.rider-marker-heading{
  position:absolute; left:50%; top:-16px;
  width:14px; height:14px;
  transform:translateX(-50%);
  transform-origin:50% 28px;
  transition:transform 0.2s linear;
  filter:drop-shadow(0 0 3px rgba(79,140,255,0.8));
}
.rider-marker-heading svg{ width:100%; height:100%; fill:#4f8cff; }

.replay-3d-stop-marker{
  width:16px; height:16px; border-radius:50%;
  background:#ef4444; border:2px solid #0a0c11;
  box-shadow:0 0 0 3px rgba(239,68,68,0.25);
}

/* ---------- share ride modal ---------- */
.share-card-preview{
  display:flex; align-items:center; justify-content:center;
  background:#0a0c11; border-radius:14px; overflow:hidden;
  min-height:280px; border:1px solid var(--border-subtle);
}
.share-card-preview img{ display:block; width:100%; height:auto; }
.share-card-spinner{
  color:var(--text-muted); font-size:13px; font-family:var(--font-mono);
  padding:40px 0;
}
.share-type-toggle{
  display:flex; gap:4px; margin-bottom:12px;
  background:var(--bg-panel-raised); border:1px solid var(--border-subtle);
  border-radius:999px; padding:3px;
}
.share-type-btn{
  flex:1; border:none; background:transparent; color:var(--text-muted);
  font-family:var(--font-display); font-weight:600; font-size:12px; letter-spacing:0.02em;
  padding:8px 10px; border-radius:999px; cursor:pointer;
}
.share-type-btn.active{ background:var(--bg-panel); color:var(--neon-green); }
.share-gfx-preview{
  background:#0a0c11; border-radius:14px; border:1px solid var(--border-subtle);
  padding:22px; text-align:center;
}
.share-gfx-preview .gfx-icon{ font-size:40px; margin-bottom:8px; }
.share-gfx-preview .gfx-name{ font-family:var(--font-display); font-weight:600; font-size:15px; color:var(--text-primary); }
.share-gfx-preview .gfx-meta{ font-family:var(--font-mono); font-size:12px; color:var(--text-muted); margin-top:4px; }
.share-gfx-preview .gfx-desc{ font-family:var(--font-body); font-size:12px; color:var(--text-dim); margin-top:10px; }
.gfx-format-row{ display:flex; flex-wrap:wrap; gap:6px; justify-content:center; margin-top:14px; }
.gfx-format-btn{
  border:1px solid var(--border-subtle); background:var(--bg-panel-raised); color:var(--text-muted);
  font-family:var(--font-display); font-weight:600; font-size:11px; letter-spacing:0.02em;
  padding:7px 12px; border-radius:999px; cursor:pointer;
}
.gfx-format-btn.active{ background:var(--bg-panel); color:var(--neon-green); border-color:var(--neon-green); }

.ride-share-type-option{
  display:flex; align-items:center; gap:12px; width:100%;
  background:var(--bg-panel-raised); border:1px solid var(--border-subtle);
  border-radius:14px; padding:14px 16px; cursor:pointer; text-align:left;
  font-family:var(--font-body); color:var(--text-primary);
}
.ride-share-type-option:active{ background:var(--bg-panel); }
.ride-share-type-option .icon{ font-size:24px; flex-shrink:0; }
.ride-share-type-option .label{ font-family:var(--font-display); font-weight:600; font-size:14px; }
.ride-share-type-option .sub{ font-family:var(--font-body); font-size:12px; color:var(--text-muted); margin-top:2px; }

.ride-card-share-btn, .ride-card-edit-btn{
  position:absolute; top:16px; z-index:2;
  width:32px; height:32px; border-radius:50%;
  border:1px solid var(--border-subtle); background:var(--bg-panel-raised);
  color:var(--text-muted); font-size:14px; cursor:pointer;
  display:flex; align-items:center; justify-content:center;
}
.ride-card-share-btn{ right:14px; }
.ride-card-edit-btn{ right:54px; }
.ride-card-share-btn:active, .ride-card-edit-btn:active{ background:var(--bg-panel); color:var(--neon-green); }
.ride-card.combine-mode .ride-card-share-btn, .ride-card.combine-mode .ride-card-edit-btn{ display:none; }

/* ---------- Labs (labs.html) ---------- */
/* The page-wide console card at the top of this page is
   js/page-console.js (RydRPageConsole) — a self-contained, reusable
   component that injects its own scoped styles (prefixed "rydr-pgc-"),
   same pattern as js/debug-console.js. Nothing to style here. */

.labs-grid{
  display:grid; grid-template-columns:repeat(auto-fit, minmax(min(320px, 100%), 1fr));
  gap:16px;
}
.labs-card{ display:flex; flex-direction:column; gap:10px; }
.labs-card-desc{ font-family:var(--font-body); font-size:13px; color:var(--text-muted); line-height:1.5; margin:0; }

.labs-model-wrap{
  position:relative; width:100%; height:280px;
  border-radius:var(--radius-md); overflow:hidden;
  background:var(--bg-panel);
}
.labs-model-canvas{ position:absolute; inset:0; width:100%; height:100%; display:block; touch-action:none; }
.labs-status{
  position:absolute; inset:0; z-index:3; display:none;
  flex-direction:column; align-items:center; justify-content:center; text-align:center; gap:10px; padding:16px;
  background:var(--bg-panel); color:var(--text-primary);
  font-family:var(--font-display); font-size:12px; letter-spacing:0.02em;
}
.labs-status-retry{
  font-family:var(--font-display); font-weight:700; font-size:12px; letter-spacing:0.04em; text-transform:uppercase;
  padding:8px 18px; border-radius:999px; cursor:pointer;
  background:var(--neon-green); color:#02110a; border:none;
}
.labs-status-retry:hover{ filter:brightness(1.1); }
.labs-debug-mount{ margin-top:2px; }
.labs-trailreplay-map{ height:360px; }
.labs-trailreplay-controls{ display:flex; flex-wrap:wrap; gap:8px; align-items:center; justify-content:space-between; }
.labs-trailreplay-presets{ display:flex; gap:6px; flex-wrap:wrap; }
.labs-preset-btn{
  font-family:var(--font-display); font-weight:600; font-size:11.5px; letter-spacing:0.03em; text-transform:uppercase;
  padding:6px 12px; min-height:32px; border-radius:999px; cursor:pointer;
  background:var(--bg-panel-raised); border:1px solid var(--border-subtle); color:var(--text-muted);
}
.labs-preset-btn.active{ background:var(--neon-green); border-color:var(--neon-green-dim); color:#02110a; }
.labs-download-link{
  display:flex; align-items:center; justify-content:center; gap:6px;
  font-family:var(--font-display); font-weight:600; font-size:12.5px; letter-spacing:0.03em;
  padding:10px 14px; border-radius:var(--radius-sm);
  background:var(--bg-panel-raised); border:1px solid var(--border-subtle);
  color:var(--text-primary); text-decoration:none;
}
.labs-download-link:active{ background:var(--bg-panel); }

.labs-spec-row{
  display:flex; align-items:baseline; justify-content:space-between; gap:10px;
  padding:7px 0;
  border-bottom:1px solid var(--border-subtle);
  font-family:var(--font-body); font-size:13px;
}
.labs-spec-row:last-child{ border-bottom:none; }
.labs-spec-row span:first-child{ color:var(--text-muted); }
.labs-spec-row span:last-child{ font-family:var(--font-mono); color:var(--text-primary); font-weight:600; text-align:right; }

.labs-check-list{ display:flex; flex-direction:column; }
.labs-check-row{
  display:flex; align-items:center; gap:10px;
  padding:8px 0;
  border-bottom:1px solid var(--border-subtle);
  font-family:var(--font-body); font-size:13px;
}
.labs-check-row:last-child{ border-bottom:none; }
.labs-check-dot{ width:9px; height:9px; border-radius:50%; flex:0 0 auto; background:var(--text-dim); }
.labs-check-dot.labs-check-pending{ background:var(--text-dim); animation:labs-check-pulse 1.2s ease-in-out infinite; }
.labs-check-dot.labs-check-ok{ background:#22c55e; box-shadow:0 0 6px rgba(34,197,94,0.6); }
.labs-check-dot.labs-check-fail{ background:var(--alert-red); box-shadow:0 0 6px var(--alert-red-glow); }
@keyframes labs-check-pulse{ 0%,100%{ opacity:1; } 50%{ opacity:0.35; } }
.labs-check-name{ flex:1; color:var(--text-primary); }
.labs-check-result{ color:var(--text-muted); font-family:var(--font-mono); font-size:12px; }

/* ---------- Telemetry Studio (plugin: telemetry-studio) ----------
   Dashboard card (.tls-card-*, standalone) + the full-screen replay view
   (.tls-*, mounted into js/plugins.js's generic plugin-screen-body). Uses
   the app's own dark surfaces throughout (--bg-panel/--bg-panel-raised) —
   no glass/blur panels — with one accent color driven by this plugin's own
   theme framework (--tls-accent/--tls-accent-dim/--tls-glow, set inline
   per-root by telemetry-studio.js's applyTheme(); "Apex Blue" default
   equals the app's own --neon-green/--cyan so an unthemed rider sees no
   visual seam). */
.tls-card-root{
  position:relative;
  height:100%; width:100%;
  min-height:calc(260px * var(--plugin-scale, 1));
  background:var(--bg-panel-raised);
  display:flex; flex-direction:column;
  padding:calc(14px * var(--plugin-scale, 1));
  gap:calc(8px * var(--plugin-scale, 1));
}
.tls-card-head{ display:flex; align-items:center; gap:8px; flex-shrink:0; }
.tls-card-icon{ font-size:calc(16px * var(--plugin-scale, 1)); }
.tls-card-title{
  font-family:var(--font-display); font-weight:700; letter-spacing:0.04em; text-transform:uppercase;
  font-size:calc(13px * var(--plugin-scale, 1)); color:var(--text-primary);
}
.tls-card-gauge{ width:100%; flex:1; min-height:0; }
.tls-card-stats{ display:flex; justify-content:space-around; gap:10px; flex-shrink:0; }
.tls-card-stat{ display:flex; flex-direction:column; align-items:center; gap:2px; }
.tls-card-stat span{ font-family:var(--font-mono); font-weight:700; font-size:calc(16px * var(--plugin-scale, 1)); color:var(--text-primary); }
.tls-card-stat small{ font-family:var(--font-display); font-size:9px; letter-spacing:0.08em; color:var(--text-dim); text-transform:uppercase; }
.tls-card-open{
  flex-shrink:0; border:1px solid var(--border-subtle); background:var(--bg-panel);
  color:var(--tls-accent, var(--neon-green)); border-radius:var(--radius-sm);
  font-family:var(--font-display); font-size:calc(11px * var(--plugin-scale, 1)); font-weight:600;
  letter-spacing:0.03em; text-transform:uppercase; padding:8px 10px; cursor:pointer; min-height:36px;
}
.tls-card-open:hover{ border-color:var(--tls-accent, var(--neon-green)); background:var(--tls-accent-dim, var(--neon-green-dim)); color:#fff; }

.tls-root{ display:flex; flex-direction:column; height:100%; min-height:0; }
.tls-toolbar{
  display:flex; align-items:center; justify-content:space-between; gap:10px; flex-wrap:wrap;
  padding:10px 14px; border-bottom:1px solid var(--border-subtle); flex-shrink:0;
}
.tls-mode-toggle{ display:flex; gap:4px; background:var(--bg-void); border:1px solid var(--border-subtle); border-radius:var(--radius-sm); padding:3px; }
.tls-mode-btn{
  border:none; background:transparent; color:var(--text-muted); font-family:var(--font-display);
  font-size:11px; font-weight:600; letter-spacing:0.03em; text-transform:uppercase;
  padding:7px 12px; border-radius:6px; cursor:pointer; min-height:36px;
}
.tls-mode-btn.active{ background:var(--tls-accent, var(--neon-green)); color:#02110a; box-shadow:0 0 10px var(--tls-glow, var(--neon-green-glow)); }
.tls-select{
  min-height:40px; background:var(--bg-void); border:1px solid var(--border-subtle); border-radius:6px;
  color:var(--text-primary); padding:0 10px; font-size:13px; flex:1; min-width:180px; max-width:320px;
}

.tls-empty{ flex:1; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:8px; padding:32px; text-align:center; }
.tls-empty-icon{ font-size:40px; opacity:0.6; }
.tls-empty-title{ font-family:var(--font-display); font-weight:700; font-size:16px; color:var(--text-primary); }
.tls-empty-sub{ color:var(--text-muted); font-size:13px; max-width:320px; }

.tls-body{ flex:1; min-height:0; display:flex; flex-direction:column; padding:12px 14px; gap:12px; }
/* .tls-empty/.tls-body/.tls-transport all set their own `display` above,
   same specificity as the browser's built-in `[hidden]{display:none}` —
   author origin wins — which silently defeats plain `.hidden = true/false`
   in JS (same bug documented on .hp-chart/.hp-status above). The extra
   [hidden] in the selector raises specificity enough to actually win. */
.tls-empty[hidden], .tls-body[hidden], .tls-transport[hidden]{ display:none; }

.tls-scene-wrap{ position:relative; flex:1; min-height:220px; border-radius:var(--radius-md); overflow:hidden; background:#0a0c11; border:1px solid var(--border-subtle); }
.tls-scene{ position:absolute; inset:0; }
.tls-live-badge{
  position:absolute; top:10px; left:10px; z-index:2; background:rgba(10,12,17,0.8); color:var(--alert-red);
  font-family:var(--font-mono); font-size:11px; font-weight:700; letter-spacing:0.05em;
  padding:4px 9px; border-radius:20px; border:1px solid var(--alert-red-glow);
}
.tls-scene-readout{ position:absolute; top:10px; right:10px; z-index:2; display:flex; gap:14px; background:rgba(10,12,17,0.8); border-radius:var(--radius-sm); padding:8px 12px; border:1px solid var(--border-subtle); }
.tls-scene-stat{ display:flex; flex-direction:column; align-items:flex-end; }
.tls-scene-stat span{ font-family:var(--font-mono); font-weight:700; font-size:18px; color:var(--text-primary); line-height:1.1; }
.tls-scene-stat small{ font-family:var(--font-display); font-size:9px; letter-spacing:0.08em; color:var(--text-dim); text-transform:uppercase; }

.tls-transport{ display:flex; align-items:center; gap:12px; flex-shrink:0; }
.tls-play-btn{
  width:44px; height:44px; flex-shrink:0; border-radius:50%; border:none; cursor:pointer;
  background:var(--tls-accent, var(--neon-green)); color:#02110a; font-size:16px; display:grid; place-items:center;
  box-shadow:0 0 12px var(--tls-glow, var(--neon-green-glow));
}
.tls-scrub{ flex:1; accent-color:var(--tls-accent, var(--neon-green)); height:6px; }
.tls-time{ font-family:var(--font-mono); font-size:12px; color:var(--text-muted); flex-shrink:0; min-width:112px; text-align:right; }

.tls-widgets{ display:grid; grid-template-columns:1fr 1fr; gap:12px; flex-shrink:0; }
.tls-widget{ background:var(--bg-panel-raised); border:1px solid var(--border-subtle); border-radius:var(--radius-md); padding:10px 12px; }
.tls-widget-wide{ grid-column:1 / -1; }
.tls-widget-title{ font-family:var(--font-display); font-size:10px; font-weight:700; letter-spacing:0.08em; text-transform:uppercase; color:var(--text-dim); margin-bottom:6px; }
.tls-widget canvas{ display:block; width:100%; height:auto; }

.tls-settings-label{ font-family:var(--font-display); font-size:11px; font-weight:700; letter-spacing:0.06em; text-transform:uppercase; color:var(--text-dim); margin:14px 0 8px; }
.tls-theme-row{ display:flex; gap:8px; flex-wrap:wrap; margin-bottom:6px; }
.tls-theme-swatch{
  display:flex; align-items:center; gap:6px; border:1px solid var(--border-subtle); background:var(--bg-void);
  color:var(--text-muted); border-radius:20px; padding:7px 12px; font-size:12px; cursor:pointer; min-height:36px;
}
.tls-theme-swatch.selected{ border-color:var(--tls-accent); color:var(--text-primary); }
.tls-theme-dot{ width:10px; height:10px; border-radius:50%; background:var(--tls-accent); flex-shrink:0; }

@media (max-width:640px){
  .tls-widgets{ grid-template-columns:1fr; }
}


