/* Self-hosted typefaces (2026-09-15): the latin subsets Google Fonts served,
   OFL 1.1, one variable file each. unicode-range keeps a page in another
   script from ever asking for them; swap because the figures on Stats are
   drawn on first paint and a fallback face beats an invisible number. */
@font-face {
  font-family: 'Instrument Sans';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("fonts/instrument-sans-latin.e35fad09c70b.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 100 800;
  font-display: swap;
  src: url("fonts/jetbrains-mono-latin.570751c5f8b4.woff2") format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* The Lines Project V2 — "Nightfall v2" design system (docs/V2-DESIGN.md).
 * Every value here is a token from the handoff; the classes are the
 * vocabulary the v2/ templates speak. One breakpoint, 720px.
 */
:root {
  --ground:#09090b; --map:#0c0c0f; --surface:#111114; --raised:#15151a; --sunken:#0f0f13;
  --hair:rgba(255,255,255,.08); --hair-strong:rgba(255,255,255,.14); --hair-faint:rgba(255,255,255,.06);
  --fg:#f4f4f5; --fg2:#a1a1aa; --muted:#71717a; --faint:#52525b;
  --red:#ff2d2d; --red-hover:#ff4444; --red-link:#ff5a52; --red-deep:#7a1010; --red-deep2:#5c1414;
  --ok:#3ddc84; --warn:#ffb020; --info:#4f9dff; --gold:#ffd700;
  --chrome:#3f3f46; --chrome2:#2a2a32; --chrome3:#26262c;
  --strava:#fc4c02; --strava-link:#fc5200; --rwgps:#d64227; --garmin:#0a7ec2;
  --sans:'Instrument Sans',ui-sans-serif,system-ui,sans-serif;
  --mono:'JetBrains Mono',ui-monospace,SFMono-Regular,Menlo,monospace;
  --r-tag:6px; --r-btn:10px; --r-lg:12px; --r-row:14px; --r-card:16px;
  --ease:cubic-bezier(.2,.7,.2,1);
}
*{box-sizing:border-box}
html,body{margin:0;background:var(--ground);color:var(--fg);font:15px/1.5 var(--sans);color-scheme:dark;-webkit-font-smoothing:antialiased}
a{color:var(--fg);text-decoration:none}a:hover{color:var(--red-link)}
button,input,select,textarea{font-family:inherit;font-size:inherit}
h1,h2,h3,p{margin:0}
@keyframes rise{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
@keyframes draw{from{stroke-dashoffset:1200}to{stroke-dashoffset:0}}
@keyframes pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.4;transform:scale(.7)}}
@keyframes glow{0%,100%{opacity:.55}50%{opacity:1}}
@keyframes slide{from{transform:translateX(-30%)}to{transform:translateX(130%)}}
@keyframes ping{0%{transform:scale(.4);opacity:.55}100%{transform:scale(1);opacity:0}}
@keyframes spin{to{transform:rotate(360deg)}}
/* The line reveal. It pairs with pathLength="1" stroke-dasharray="1" on the
 * path, so one dash is exactly the whole line whatever its real length —
 * `draw` above (1200→0) is only honest against a path whose length has been
 * measured, and the handoff's mistake #2 was a 2000/6000 dash against paths
 * nobody had measured, which starts the reveal mostly drawn. */
@keyframes drawLine{from{stroke-dashoffset:1}to{stroke-dashoffset:0}}
@keyframes markPop{from{transform:scale(0)}to{transform:scale(1)}}
.rise{animation:rise .5s var(--ease) both}
/* Reduced motion (handoff README § Motion) — half of it. This neutralises
 * every keyframe and transition and draws every dashed reveal in full; the
 * other half is v2.js's count-up, which checks the same query and sets its
 * scalar straight to 1, because CSS cannot change a number a script writes.
 * ⚠️ The ONE @media in this file that is not the 720px breakpoint;
 * v2.tests.ResponsiveTests names it and refuses any other. */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.001ms!important;animation-delay:0ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important}
  [stroke-dasharray]{stroke-dashoffset:0!important}
}
:focus-visible{outline:2px solid var(--red);outline-offset:2px}

/* ---- type ---- */
.title{font-size:32px;font-weight:600;letter-spacing:-.03em;line-height:1.1}
.sub{color:var(--fg2);margin-top:8px;max-width:640px;text-wrap:pretty}
.eyebrow{font-size:12px;letter-spacing:.14em;color:var(--red);font-weight:600;text-transform:uppercase}
.caps{font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);font-weight:500}
.mono{font-family:var(--mono)}
.muted{color:var(--muted)}.dim{color:var(--fg2)}.faint{color:var(--faint)}
.ok{color:var(--ok)}.warn{color:var(--warn)}.bad{color:var(--red-link)}
.fine{font-size:12px;color:var(--faint);line-height:1.5}
.small{font-size:12.5px;color:var(--muted)}
h2.h{font-size:16px;font-weight:600;margin:24px 0 12px}
h2.hc{font-size:15px;font-weight:600;margin:0 0 12px}
.num{font-family:var(--mono);font-weight:500;letter-spacing:-.02em}
.num.xl{font-size:26px}.num.lg{font-size:22px}.num.md{font-size:18px}
.unit{font-size:12px;color:var(--muted);margin-left:4px;font-family:var(--sans);font-weight:400}

/* ---- surfaces ---- */
.card{background:var(--surface);border:1px solid var(--hair);border-radius:var(--r-card)}
.card.pad{padding:22px}.card.pad-c{padding:16px 18px}
.card.ok-b{border-color:rgba(61,220,132,.25)}.card.warn-b{border-color:rgba(255,176,32,.25)}.card.red-b{border-color:rgba(255,45,45,.3)}
.well{background:var(--map);border:1px solid var(--hair-faint);border-radius:var(--r-lg);padding:12px 14px}
.tile{background:var(--surface);border:1px solid var(--hair);border-radius:var(--r-card);padding:18px 20px;min-width:0}
.tile .l{font-size:12px;color:var(--fg2)}
.tile .v{font-family:var(--mono);font-size:26px;font-weight:500;letter-spacing:-.02em;margin-top:6px}
.tile .n{font-size:12px;color:var(--muted);margin-top:2px}
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:14px}
.grid3{display:grid;grid-template-columns:repeat(3,1fr);gap:14px}
.grid4{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}
.detail{display:grid;grid-template-columns:minmax(0,1fr) 300px;gap:20px;align-items:start}
/* ⚠️ A grid item's automatic minimum is its CONTENT width, so one
 * `white-space:nowrap` row in a sidebar card — an activity name under
 * "Recent activities here", the "View on Strava" link — makes the whole
 * column that wide, and on a phone the map and every card beside it run
 * off the right edge of the screen while `overflow-x:hidden` hides the
 * scroll. Measured 2026-09-15: one nowrap row took the activity map from
 * 343px to 647px on a 375px screen. The desktop never showed it because
 * the sidebar is a fixed 300px column there. `min-width:0` on every item,
 * not on the two that happened to carry it inline. */
.detail>*{min-width:0}
.statnum{font-family:var(--mono);font-size:54px;font-weight:500;letter-spacing:-.05em;line-height:1;margin:10px 0 8px;font-variant-numeric:tabular-nums}
.tile.stat{border-radius:14px;padding:16px 18px}
.tile.stat .v{margin-top:4px;font-variant-numeric:tabular-nums}
.tile.stat .unit{font-size:13px}
.stack{display:flex;flex-direction:column;gap:14px}
.between{display:flex;flex-wrap:wrap;align-items:flex-end;justify-content:space-between;gap:16px 24px}
.hr{height:1px;background:var(--hair);margin:16px 0}
.hidden{display:none !important}

/* ---- controls ---- */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;border-radius:var(--r-btn);padding:9px 14px;font-size:13px;cursor:pointer;border:1px solid var(--hair-strong);background:transparent;color:var(--fg);transition:border-color .18s,background .18s,color .18s;white-space:nowrap;text-decoration:none}
.btn:hover{border-color:rgba(255,255,255,.35);color:var(--fg)}
.btn.primary{background:var(--red);border-color:var(--red);color:#fff;font-weight:600}
.btn.primary:hover{background:var(--red-hover);border-color:var(--red-hover)}
.btn.light{background:var(--fg);border-color:var(--fg);color:var(--ground);font-weight:600}
.btn.light:hover{background:#fff}
.btn.danger{background:rgba(255,45,45,.12);border-color:rgba(255,45,45,.4);color:var(--red-link)}
.btn.danger:hover{background:rgba(255,45,45,.2)}
.btn.pill{border-radius:999px}
.btn.lg{padding:13px 22px;font-size:15px;border-radius:999px}
.btn.wide{width:100%;padding:13px 16px;font-size:14px;border-radius:var(--r-lg)}
.btn.glow{box-shadow:0 0 0 1px rgba(255,45,45,.4),0 12px 32px -8px rgba(255,45,45,.6)}
.btn.sm{padding:7px 11px;font-size:12.5px;border-radius:8px}
.btn.xs{padding:3px 8px;font-size:11.5px;border-radius:7px;vertical-align:baseline}
/* The Home base typeahead. The list is absolutely positioned over whatever
   follows, so adding it moved nothing on the page. */
.combo{position:relative}
.combo-list{position:absolute;z-index:40;left:0;right:0;top:calc(100% + 4px);margin:0;padding:4px;list-style:none;background:var(--raised);border:1px solid var(--hair-strong);border-radius:var(--r-lg);box-shadow:0 18px 40px rgba(0,0,0,.5);max-height:264px;overflow:auto;scrollbar-width:thin}
.combo-list li{display:flex;align-items:baseline;justify-content:space-between;gap:12px;padding:9px 11px;border-radius:8px;cursor:pointer;font-size:13.5px}
.combo-list li i{font-style:normal;font-size:11.5px;color:var(--faint);font-family:var(--mono);flex:0 0 auto}
.combo-list li.on,.combo-list li:hover{background:rgba(255,255,255,.07)}
.combo-list li.on{outline:1px solid rgba(255,45,45,.35);outline-offset:-1px}
/* A note that is a caution rather than a fact. */
.fine.warn{color:var(--warn)}
.btn.icon-only{padding:0;border:0;background:none;line-height:0;border-radius:8px}
.btn[disabled],.btn[aria-disabled="true"],.is-disabled{opacity:.45;cursor:not-allowed;pointer-events:none}
.link{background:none;border:0;color:var(--fg2);font:inherit;font-size:13px;cursor:pointer;padding:0;text-decoration:underline;text-underline-offset:3px}
.link:hover{color:var(--fg)}
.input,.select{width:100%;background:var(--ground);border:1px solid rgba(255,255,255,.1);color:var(--fg);border-radius:var(--r-btn);padding:10px 12px;font-size:14px;outline:none}
.input.dark{background:var(--surface);padding:12px 14px}
.input:focus,.select:focus{border-color:var(--red);box-shadow:0 0 0 3px rgba(255,45,45,.2);outline:none}
.select{width:auto;padding:8px 10px;font-size:13px}
label.lab{display:block;font-size:12px;color:var(--fg2);margin-bottom:6px}
.seg{display:inline-flex;background:rgba(255,255,255,.04);border-radius:var(--r-btn);padding:3px;font-size:13px;white-space:nowrap;max-width:100%;overflow-x:auto;scrollbar-width:none}
.seg a,.seg button{padding:7px 14px;border-radius:8px;color:var(--fg2);border:0;background:transparent;cursor:pointer;text-decoration:none}
.seg .on{background:var(--fg);color:var(--ground);font-weight:600}
.chips{display:inline-flex;background:var(--ground);border:1px solid rgba(255,255,255,.1);border-radius:999px;padding:3px}
.chips button{border:0;padding:8px 16px;border-radius:999px;cursor:pointer;font-size:13px;font-weight:500;background:transparent;color:var(--fg2);transition:background .2s}
.chips .on{background:var(--red);color:#fff}
.tag{display:inline-block;font-size:10.5px;letter-spacing:.06em;text-transform:uppercase;border-radius:var(--r-tag);padding:2px 7px;background:rgba(255,255,255,.06);color:var(--fg2);white-space:nowrap}
.tag.ok{background:rgba(61,220,132,.14);color:var(--ok)}
.tag.warn{background:rgba(255,176,32,.14);color:var(--warn)}
.tag.bad{background:rgba(255,45,45,.14);color:var(--red-link)}
.tag.red-o{background:transparent;color:var(--red);border:1px solid rgba(255,45,45,.4)}
.tag.fill-warn{background:var(--warn);color:#141418;font-weight:600}
.dot{display:inline-block;width:7px;height:7px;border-radius:50%;background:var(--chrome);flex:0 0 auto}
.dot.ok{background:var(--ok)}.dot.warn{background:var(--warn)}.dot.red{background:var(--red)}.dot.info{background:var(--info)}
.dot.live{animation:pulse 1.6s ease-in-out infinite}
.dot.glow{box-shadow:0 0 8px currentColor}
.toggle{flex:0 0 auto;width:40px;height:22px;border-radius:999px;border:0;padding:0;position:relative;cursor:pointer;background:var(--hair-strong);transition:background .18s}
.toggle:focus-visible{outline-offset:3px}
.toggle .knob{position:absolute;top:2px;left:2px;width:18px;height:18px;border-radius:50%;background:var(--fg);transition:left .18s}
.toggle[aria-checked="true"]{background:var(--red)}
.toggle[aria-checked="true"] .knob{left:20px}
.notice{display:flex;gap:12px;align-items:flex-start;background:rgba(255,176,32,.06);border:1px solid rgba(255,176,32,.25);border-radius:var(--r-lg);padding:12px 16px;font-size:13px;color:var(--fg2);line-height:1.55}
.notice svg{flex:0 0 auto;margin-top:2px}
.notice.ok{background:rgba(61,220,132,.06);border-color:rgba(61,220,132,.25)}
.notice.bad{background:rgba(255,45,45,.04);border-color:rgba(255,45,45,.25)}
.notice.info{background:rgba(79,157,255,.06);border-color:rgba(79,157,255,.3)}
.notice b{color:var(--fg);font-weight:600}
/* A notice that folds to one line (the Next page's key, 2026-09-17). `.notice`
   is a flex row; a <details> cannot be, so the row moves onto the summary. */
.notice.fold{display:block;padding:0}
.notice.fold>summary{display:flex;gap:12px;align-items:center;padding:10px 16px;cursor:pointer;list-style:none;min-height:44px;box-sizing:border-box}
.notice.fold>summary::-webkit-details-marker{display:none}
.notice.fold>summary svg{margin-top:0}
.notice.fold .lbl{flex:1 1 auto;min-width:0}
.notice.fold .cnt{color:var(--muted)}
.notice.fold .routing{flex:0 0 auto;white-space:nowrap}
.notice.fold .chev{flex:0 0 auto;width:7px;height:7px;border-right:2px solid var(--fg2);border-bottom:2px solid var(--fg2);transform:rotate(45deg);margin:-3px 4px 0 0;transition:transform .18s}
.notice.fold[open] .chev{transform:rotate(-135deg);margin-top:3px}
.notice.fold>.body{padding:0 16px 13px 44px}
.empty{background:var(--surface);border:1px solid var(--hair);border-radius:var(--r-card);padding:28px;display:flex;flex-direction:column;align-items:flex-start;gap:10px;min-height:200px;justify-content:center}
.empty h2{font-size:17px;font-weight:600}
.empty p{font-size:13.5px;color:var(--fg2);line-height:1.6}
.empty.warn-b{border-color:rgba(255,176,32,.25);background:rgba(255,176,32,.04)}
.empty.red-b{border-color:rgba(255,45,45,.25);background:rgba(255,45,45,.04)}
.progress{height:6px;border-radius:3px;background:var(--hair-faint);overflow:hidden;position:relative}
.progress .bar{height:100%;background:var(--red);border-radius:3px;box-shadow:0 0 12px rgba(255,45,45,.6)}
.progress.busy{background:rgba(255,45,45,.16)}
.progress .sheen{position:absolute;top:0;bottom:0;width:30%;background:linear-gradient(90deg,transparent,rgba(255,255,255,.35),transparent);animation:slide 1.6s linear infinite}
.bar-thin{height:4px;border-radius:2px;background:var(--hair-faint);overflow:hidden}
.bar-thin > span{display:block;height:100%;border-radius:2px;background:var(--red)}
.messages{list-style:none;margin:0 0 16px;padding:0;display:flex;flex-direction:column;gap:8px}
.messages li{border-radius:var(--r-lg);padding:10px 14px;font-size:13px;background:rgba(255,255,255,.04);border:1px solid var(--hair);color:var(--fg2)}
.messages li.success{border-color:rgba(61,220,132,.25);background:rgba(61,220,132,.06)}
.messages li.warning{border-color:rgba(255,176,32,.25);background:rgba(255,176,32,.06)}
.messages li.error{border-color:rgba(255,45,45,.25);background:rgba(255,45,45,.06)}

/* ---- tables ---- */
.tblwrap{overflow-x:auto;scrollbar-width:thin}
/* ⚠️ `.tscroll` is `auto` here and `hidden` under 720px. A table wide enough
   to need a sideways scrollbar on a phone is a table that has not been made
   to fit; `.fitm` below is what makes it fit, and this is what proves it —
   if a cell still overflows, it clips visibly instead of hiding behind a
   gesture nobody performs. */
.tscroll{overflow:auto;scrollbar-width:thin}
.tbl.fitm{min-width:900px}
.tbl{width:100%;border-collapse:collapse;font-size:13.5px}
.tbl th{text-align:left;padding:10px 16px;font-weight:500;color:var(--muted);font-size:11px;letter-spacing:.08em;text-transform:uppercase;white-space:nowrap;background:var(--sunken)}
.tbl th.r,.tbl td.r{text-align:right}
.tbl td{padding:12px 16px;border-top:1px solid rgba(255,255,255,.05);vertical-align:middle}
.tbl.tight td{padding:9px 14px}
.tbl tr.go{cursor:pointer}.tbl tr.go:hover{background:rgba(255,255,255,.03)}
.tbl .sticky th{position:sticky;top:0;z-index:1;background:#141418}
.tblfoot{display:flex;flex-wrap:wrap;justify-content:space-between;align-items:center;gap:8px 16px;padding:11px 18px;border-top:1px solid var(--hair-faint);font-size:12.5px;color:var(--muted);background:var(--sunken)}
.rows{display:flex;flex-direction:column}
.rows .row{display:flex;flex-wrap:wrap;gap:10px 16px;align-items:center;justify-content:space-between;padding:12px 0;border-top:1px solid var(--hair-faint)}
.rows .row .t{font-size:13.5px}.rows .row .d{font-size:12.5px;color:var(--muted);line-height:1.5;margin-top:3px;text-wrap:pretty}
.kv{display:flex;flex-direction:column;gap:8px;font-size:13px;color:var(--fg2)}
.kv > div{display:flex;justify-content:space-between;gap:12px}
.kv .v{color:var(--fg)}

/* ---- public chrome ---- */
.pubhead{position:sticky;top:0;z-index:20;display:flex;align-items:center;gap:32px;padding:0 40px;height:64px;background:rgba(9,9,11,.72);backdrop-filter:blur(14px);border-bottom:1px solid var(--hair-faint)}
.pubhead .brand{display:flex;align-items:center;gap:12px}
.pubhead .brand .routemark{display:block;width:52px;height:auto;flex:0 0 auto}
/* The route symbol draws itself in on every page load wherever it appears — public header,
   page bar, rail tile (v2/_route_mark.html):
   the route left to right, the spurs, then the junction dots. Brand-handoff timings; the
   reduced-motion block at the top of this file ends it at its finished frame. */
.routemark .rm-line{stroke-dasharray:1;animation:drawLine 1.5s cubic-bezier(.4,0,.2,1) both}
.routemark .rm-s1{animation-duration:.55s;animation-delay:.8s}
.routemark .rm-s2{animation-duration:.7s;animation-delay:1s}
.routemark .rm-dot{transform-box:fill-box;transform-origin:center;animation:markPop .45s cubic-bezier(.2,.8,.3,1.3) .62s both}
.routemark .rm-dot2{animation-delay:1.02s}
.pubhead .wordmark{font-weight:600;letter-spacing:.14em;font-size:12px}
.pubhead nav{display:flex;gap:24px;font-size:13.5px;color:var(--fg2);margin-left:12px}
.pubhead nav a{color:var(--fg2)}.pubhead nav a:hover{color:var(--fg)}
.pubhead .right{margin-left:auto;display:flex;align-items:center;gap:10px}
.pubfoot{border-top:1px solid var(--hair-faint);padding:32px 40px;display:flex;justify-content:space-between;align-items:center;color:var(--muted);font-size:13px;max-width:1280px;margin:0 auto}
.pubfoot .wordmark{letter-spacing:.14em;font-weight:600;font-size:11px;color:var(--fg2)}
.pubwrap{max-width:1200px;margin:0 auto;padding:40px 36px 80px}

/* ---- landing ---- */
.hero{position:relative;overflow:hidden;padding:96px 40px 40px;min-height:640px}
.hero .bg{position:absolute;inset:0;width:100%;height:100%;opacity:.55;mask-image:radial-gradient(ellipse 70% 80% at 65% 45%,#000 30%,transparent 100%);-webkit-mask-image:radial-gradient(ellipse 70% 80% at 65% 45%,#000 30%,transparent 100%)}
.hero .cols{position:relative;max-width:1200px;margin:0 auto;display:grid;grid-template-columns:minmax(0,1.15fr) minmax(300px,.85fr);gap:40px;align-items:center}
.hero .badge{display:inline-flex;align-items:center;gap:8px;border:1px solid rgba(255,255,255,.1);border-radius:999px;padding:5px 12px 5px 8px;font-size:12px;color:var(--fg2);background:rgba(17,17,20,.7)}
.hero h1{font-size:clamp(40px,4.6vw,64px);line-height:1.02;letter-spacing:-.035em;font-weight:600;margin:24px 0 20px;text-wrap:balance}
.hero h1 .red{color:var(--red)}
.hero p{font-size:18px;line-height:1.55;color:var(--fg2);max-width:520px;margin:0 0 32px;text-wrap:pretty}
.hero .ctas{display:flex;gap:12px;align-items:center;flex-wrap:wrap}
.phone-wrap{display:flex;justify-content:center;align-items:center;position:relative;animation:rise .9s .15s var(--ease) both}
.phone-glow{position:absolute;width:420px;height:420px;border-radius:50%;background:radial-gradient(circle,rgba(255,45,45,.16),rgba(255,45,45,.04) 45%,transparent 70%);filter:blur(10px);pointer-events:none}
.phone{position:relative;transform:rotate(-4deg)}
.phone .side{position:absolute;width:3px;border-radius:2px 0 0 2px;background:linear-gradient(90deg,#6f6f79,#2e2e35)}
.phone .side.r{right:-3px;border-radius:0 2px 2px 0;background:linear-gradient(270deg,#6f6f79,#2e2e35)}
.phone .body{padding:11px;border-radius:59px;background:linear-gradient(148deg,#8b8b95 0%,#3a3a42 18%,#6c6c76 38%,#26262c 62%,#5e5e68 82%,#33333a 100%);box-shadow:0 70px 100px -34px rgba(0,0,0,.95),0 0 0 1px rgba(0,0,0,.6),0 0 70px -14px rgba(255,45,45,.22),inset 0 1px 1px rgba(255,255,255,.45),inset 0 -1px 1px rgba(255,255,255,.18)}
.phone .screen{border-radius:48px;overflow:hidden;background:#000;box-shadow:inset 0 0 0 3px #050507,0 2px 6px rgba(0,0,0,.9);width:280px;height:580px;position:relative}
.phone .screen img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;object-position:50% 44%;filter:brightness(1.22) saturate(1.05)}
.phone .sheen{position:absolute;inset:0;background:linear-gradient(150deg,rgba(255,255,255,.09),transparent 42%);pointer-events:none}
/* The iOS device frame, from the handoff's ios-frame.jsx (IOSDevice): the
 * dynamic island, the 9:41 status bar and the home indicator. Without them the
 * hero is a picture in a rounded rectangle, not a phone. */
.phone .island{position:absolute;top:11px;left:50%;transform:translateX(-50%);width:126px;height:37px;border-radius:24px;background:#000;z-index:50}
/* The gap is the island: two flex:1 halves 154px apart, which on this 280px
 * screen pushes the clock and the radios out past their own boxes and clear
 * of the island above them. space-between instead parks the radios under it
 * and the signal bars disappear (the island is z-50). */
.phone .iosbar{position:absolute;top:0;left:0;right:0;bottom:auto;z-index:10;display:flex;align-items:center;justify-content:center;gap:154px;padding:21px 24px 19px;color:#fff;font-family:-apple-system,"SF Pro",system-ui,sans-serif}
.phone .iosbar>*{flex:1;height:22px;display:flex;align-items:center;justify-content:center}
.phone .iosbar .time{font-weight:590;font-size:17px;line-height:22px}
.phone .iosbar .icons{gap:7px}
.phone .home{position:absolute;left:0;right:0;bottom:0;z-index:60;height:34px;display:flex;justify-content:center;align-items:flex-end;padding-bottom:8px;pointer-events:none}
.phone .home i{display:block;width:139px;height:5px;border-radius:100px;background:rgba(255,255,255,.7)}
/* The two pieces of app chrome the shot is meant to show off. */
.phone .seg{position:absolute;top:76px;left:14px;right:14px;display:flex;background:rgba(17,17,20,.85);border:1px solid rgba(255,255,255,.1);border-radius:10px;padding:3px;backdrop-filter:blur(8px);font-size:12px;font-weight:500;z-index:5}
.phone .seg span{flex:1;text-align:center;padding:7px;color:var(--fg2)}
.phone .seg span.on{border-radius:7px;background:var(--red);color:#fff}
.phone .shotcard{position:absolute;left:14px;right:14px;bottom:20px;background:rgba(17,17,20,.92);border:1px solid rgba(255,255,255,.1);border-radius:var(--r-card);padding:16px;backdrop-filter:blur(8px);z-index:5}
.phone .shotcard .big{display:flex;justify-content:space-between;align-items:baseline;margin-top:6px}
.phone .shotcard .big b{font-family:var(--mono);font-size:26px;font-weight:500;letter-spacing:-.02em}
.phone .shotcard .big span{font-size:12px;color:var(--fg2)}
.phone .shotcard hr{border:0;height:1px;background:var(--hair);margin:12px 0}
.phone .shotcard .row{display:flex;justify-content:space-between;font-size:12px;color:var(--fg2)}
.features{border-top:1px solid var(--hair-faint);border-bottom:1px solid var(--hair-faint);background:var(--map)}
.features .in{max-width:1200px;margin:0 auto;padding:72px 36px;display:grid;grid-template-columns:repeat(3,1fr);gap:24px}
.feature{background:var(--surface);border:1px solid var(--hair);border-radius:var(--r-card);padding:28px;display:flex;flex-direction:column;gap:14px}
.feature .ic{width:36px;height:36px;border-radius:10px;background:rgba(255,45,45,.12);display:grid;place-items:center}
.feature h3{font-size:20px;font-weight:600;letter-spacing:-.02em}
.feature p{color:var(--fg2);line-height:1.6}
.pricing{max-width:1200px;margin:0 auto;padding:88px 40px 96px}
.pricing .lead{text-align:center;max-width:560px;margin:0 auto 48px}
.pricing h2{font-size:40px;letter-spacing:-.03em;font-weight:600;margin:12px 0}
.plans{display:grid;grid-template-columns:repeat(3,1fr);gap:20px;align-items:stretch}
.plans.two{grid-template-columns:repeat(2,1fr);max-width:760px;margin-left:auto;margin-right:auto}
.plan{background:var(--surface);border:1px solid var(--hair);border-radius:20px;padding:30px;display:flex;flex-direction:column;gap:18px;position:relative}
.plan.hero-plan{background:#131316;border-color:rgba(255,45,45,.5);box-shadow:0 30px 60px -30px rgba(255,45,45,.35)}
.plan .pop{position:absolute;top:-12px;left:30px;background:var(--red);color:#fff;font-size:11px;font-weight:600;letter-spacing:.08em;border-radius:999px;padding:4px 10px}
.plan .price{font-size:44px;font-weight:600;letter-spacing:-.04em;font-family:var(--mono)}
.plan ul{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:10px;flex:1}
.plan li{display:flex;gap:10px;align-items:flex-start;font-size:14px}
.signup{display:flex;gap:8px;max-width:460px}
.signup .input{flex:1}

/* ---- app shell ---- */
.shell{display:grid;grid-template-columns:76px minmax(0,1fr);grid-template-rows:minmax(0,1fr) auto;height:100vh;height:100dvh;overflow:hidden}
.rail{grid-row:1 / span 2;height:100%;border-right:1px solid var(--hair-faint);background:var(--ground);display:flex;flex-direction:column;align-items:center;padding:16px 0;gap:6px}
.rail .logo{flex:0 0 44px;width:44px;height:44px;border-radius:14px;background:var(--red);display:grid;place-items:center;margin-bottom:14px;box-shadow:0 12px 30px -10px rgba(255,45,45,.7)}
.rail .logo .routemark{display:block;width:28px;height:auto}
.rail a.item{width:60px;padding:9px 0 7px;border-radius:12px;display:flex;flex-direction:column;align-items:center;gap:5px;color:var(--fg2);position:relative;font-size:10px;font-weight:500;letter-spacing:.01em}
.rail a.item:hover{color:var(--fg);background:rgba(255,255,255,.06)}
.rail a.item.on{color:var(--red);background:rgba(255,45,45,.12)}
.rail a.item.on::before{content:"";position:absolute;left:-8px;top:12px;bottom:12px;width:2px;border-radius:2px;background:var(--red)}
.rail .badge{position:absolute;top:6px;right:10px;background:var(--red);color:#fff;font-size:9px;font-weight:600;border-radius:999px;padding:1px 5px;font-family:var(--mono)}
.rail .tools{margin-top:auto;display:flex;flex-direction:column;align-items:center;gap:6px;padding-top:10px}
.rail .tools a.item{padding:8px 0 6px;font-size:9.5px}
.rail .sep{width:36px;height:1px;background:var(--hair);margin-bottom:6px}
.rail .plan{font-family:var(--mono);font-size:9px;margin-top:6px;letter-spacing:.08em;border:1px solid var(--hair-strong);border-radius:999px;padding:3px 7px;writing-mode:vertical-rl;transform:rotate(180deg);color:var(--fg2)}
.rail .plan.paid{color:var(--red);border-color:rgba(255,45,45,.5)}
.avatar{width:34px;height:34px;border-radius:50%;background:linear-gradient(135deg,var(--red),var(--red-deep));display:grid;place-items:center;font-size:13px;font-weight:600;color:#fff}
.avatar.lg{width:64px;height:64px;font-size:24px}
/* An uploaded photo fills the same circle the initial sat in (core.avatars). */
.avatar{overflow:hidden}
.avatar img{display:block;width:100%;height:100%;object-fit:cover;border-radius:50%}
.avatarpick{display:flex;flex-direction:column;align-items:center;gap:6px}
.avatarpick label.avatar{cursor:pointer}
.avatarpick .link{cursor:pointer;text-decoration:underline;text-underline-offset:3px}
/* The chooser is visually hidden, not display:none, so it stays focusable
   and a label still opens it in every browser. */
.filepick{position:absolute;width:1px;height:1px;opacity:0;overflow:hidden;clip:rect(0 0 0 0)}
.avatarpick:focus-within label.avatar{box-shadow:0 0 0 2px var(--fg)}
.main{min-width:0;min-height:0;overflow-y:auto;position:relative;scrollbar-width:thin}
.topline{position:sticky;top:0;z-index:20;display:flex;align-items:center;gap:12px;padding:0 36px;height:56px;background:rgba(9,9,11,.8);backdrop-filter:blur(14px);border-bottom:1px solid var(--hair-faint)}
.topline .brand{display:flex;align-items:center;gap:12px;min-width:0;border-radius:8px;-webkit-tap-highlight-color:transparent}
.topline .brand:hover .wordmark{color:var(--fg)}
.topline .brand:active{opacity:.6;transition-duration:0s}
.topline .routemark{display:block;width:40px;height:auto;flex:0 0 auto}
.topline .wordmark{font-weight:600;letter-spacing:.14em;font-size:11px;color:var(--fg2);white-space:nowrap}
.topline .right{margin-left:auto;display:flex;align-items:center;gap:12px;min-width:0;flex:1 1 auto;justify-content:flex-end}
.topline .search{display:flex;align-items:center;gap:8px;border:1px solid rgba(255,255,255,.1);border-radius:999px;padding:6px 12px 6px 10px;font-size:12px;color:var(--fg2);flex:0 1 260px;min-width:0;white-space:nowrap;overflow:hidden}
.topline .search input{background:none;border:0;color:var(--fg);outline:none;flex:1;min-width:0;font-size:12px}
.topline .kbd{margin-left:auto;font-family:var(--mono);font-size:10px;border:1px solid rgba(255,255,255,.1);border-radius:4px;padding:0 4px}
.planbadge{font-size:11px;font-weight:600;letter-spacing:.08em;border:1px solid var(--hair-strong);color:var(--fg2);border-radius:999px;padding:4px 10px}
.planbadge.paid{color:var(--red);border-color:rgba(255,45,45,.5)}
.page{max-width:1240px;padding:24px 36px 96px;animation:rise .5s var(--ease) both}
.page.narrow{max-width:900px}.page.thin{max-width:820px}.page.form{max-width:420px}
.back{font-size:13px;color:var(--fg2);display:inline-flex;align-items:center;gap:6px;margin-bottom:16px}
.back:hover{color:var(--fg)}
.tabbar{display:none}

/* ---- map screen ---- */
/* Fills the scroll area exactly. A percentage height chain through the
 * grid cell measured 0x0 in the browser pane (2026-09-08), which gives
 * MapLibre an 800x600 default canvas and a blank screen. */
.mapfull{position:absolute;inset:0}
.mapwrap{position:relative;overflow:hidden;background:var(--map);height:100%}
.mapwrap .ml{position:absolute;inset:0}
.glass{background:rgba(17,17,20,.88);border:1px solid rgba(255,255,255,.1);backdrop-filter:blur(12px)}
.ov{position:absolute;top:16px;left:16px;right:16px;bottom:auto;display:flex;justify-content:space-between;align-items:flex-start;gap:12px 16px;pointer-events:none;white-space:nowrap}
.ov > *{pointer-events:auto}
.ov-col{display:flex;flex-direction:column;gap:10px;min-width:0;flex:0 1 auto;animation:rise .5s var(--ease) both}
.modebar{display:flex;align-items:center;gap:6px;border-radius:999px;padding:5px;box-shadow:0 20px 40px -20px rgba(0,0,0,.8);align-self:flex-start;max-width:100%;flex-wrap:wrap}
.modebar button,.modebar a{border:0;padding:9px 16px;border-radius:999px;cursor:pointer;font-size:13.5px;font-weight:600;background:transparent;color:var(--fg2);transition:background .25s;text-decoration:none}
.modebar .on{background:var(--red);color:#fff}
.modebar .sep{width:1px;height:20px;background:rgba(255,255,255,.12);margin:0 4px}
.modebar .ctl{font-weight:400;font-size:13px;display:flex;align-items:center;gap:8px}
.modebar .ctl:hover{color:var(--fg)}
.modebar select{background:transparent;border:0;color:var(--fg2);font-size:13px;padding:9px 10px;cursor:pointer}
.modebar .layerbtn{display:none}
.mapctl{display:flex;align-items:center;gap:2px;min-width:0}
.ctlgroup{display:flex;align-items:center;gap:2px}
/* One DOM order, two reading orders: the sheet reads Basemap / Filter /
   Overlay top to bottom, the desktop pill reads Gaps → Years · Type · Basemap
   left to right. `order` is ignored once the phone makes .mapctl a block. */
.mapctl .g-over{order:1}.mapctl .g-filter{order:2}.mapctl .g-base{order:3}
.ctlcap,.sheetgrab,.sheethead,.bmgrid,.sheetscrim{display:none}
/* The captions used to be .hidden spans — a bare "Dark ⌄" named nothing. */
.modebar .ctl .cap{font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);font-weight:600}
.modebar .ctl select{padding:9px 6px;color:var(--fg)}
.modebar .gapctl .hint{display:none}
.legend{align-self:flex-start;display:flex;flex-wrap:wrap;gap:8px 16px;border-radius:999px;padding:8px 16px;font-size:12px;color:var(--fg2);animation:rise .4s both;max-width:100%}
.legend span{display:flex;align-items:center;gap:8px}
.legend i{display:inline-block;width:20px;height:3px;border-radius:2px}
.rebuild{display:flex;align-items:center;gap:10px;border-radius:999px;padding:8px 14px 8px 12px;font-size:12px;color:var(--fg2);animation:rise .4s both;border-color:rgba(255,176,32,.3)}
.rebuild .x{background:none;border:0;color:var(--faint);font-size:14px;line-height:1;cursor:pointer;padding:0 0 0 2px}
.rebuild .x:hover{color:var(--fg2)}
/* Smaller: 10.5px on 5/10 padding instead of 11.5px on 8/14. It is a receipt,
   not a headline — it sat as tall as the zoom control. */
/* ⚠️ A BLOCK, not flex: `text-overflow` does nothing on a flex container (its
   text runs become anonymous flex items), so the flex version clipped
   mid-word with no ellipsis and cut off the mode label at the end. */
.status{position:absolute;left:16px;bottom:16px;display:block;max-width:calc(100% - 32px);border-radius:999px;padding:5px 10px 5px 8px;font-family:var(--mono);font-size:10.5px;line-height:1.35;color:var(--fg2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.status .v{color:var(--fg)}
.status .dot{vertical-align:middle;margin:-2px 7px 0 0}
.status .narrow{display:none}
/* The rebuild notice TAKES the status pill's slot rather than stacking above
   it (Greg, 2026-09-14: "only one notification showing"), at the pill's own
   size. The sibling rule is the whole swap — map.js only toggles `.hidden` on
   the notice, so the pill comes back the moment the job ends or is dismissed.
   ⚠️ #rebuild must stay BEFORE .status in the markup for `~` to reach it.
   Flex for the dismiss button, so the ellipsis lives on the .msg child. */
.rebuild.pos{position:absolute;left:16px;bottom:16px;max-width:calc(100% - 32px);gap:7px;padding:5px 8px 5px 8px;font-family:var(--mono);font-size:10.5px;line-height:1.35;white-space:nowrap}
.rebuild.pos .msg{min-width:0;overflow:hidden;text-overflow:ellipsis}
.rebuild.pos .x{font-size:12px}
.rebuild.pos:not(.hidden) ~ .status{display:none}
.brctl{position:absolute;right:16px;bottom:16px;display:flex;flex-direction:column;align-items:flex-end;gap:8px}
/* ONE SIZE FOR EVERY MAP BUTTON (Greg, 2026-09-17): this cluster wears the
   small maps' metrics (.ectl below) — 34px for a pointer, 44px for a thumb in
   the breakpoint — on the compass, the zoom pair and Locate alike. It was
   44 / 38 / 38 beside the embeds' 34; the smaller won. MapButtonsAreOneSizeTests
   compares the two clusters on both sides of the breakpoint. */
.compass{width:34px;height:34px;border-radius:50%;cursor:grab;display:grid;place-items:center;padding:0;touch-action:none;border:1px solid rgba(255,255,255,.1);background:rgba(17,17,20,.9);backdrop-filter:blur(12px)}
.compass:hover{border-color:rgba(255,255,255,.3)}
.compass svg{width:21px;height:21px;transition:transform .5s var(--ease)}
.compass.dragging svg{transition:none}
.zoomcluster{width:34px;display:flex;flex-direction:column;border-radius:12px;overflow:hidden}
.zoomcluster button{width:100%;height:34px;border:0;background:transparent;color:var(--fg);font-size:18px;cursor:pointer;border-bottom:1px solid var(--hair);display:grid;place-items:center;padding:0}
.zoomcluster button:last-child{border-bottom:0}
.zoomcluster button:hover{background:rgba(255,255,255,.06)}
.zoomcluster button:active{background:rgba(255,255,255,.14);transition-duration:0s}
.zoomcluster button:focus-visible{outline-offset:-2px}
.locate.on{color:var(--info)}.locate.blocked{color:var(--muted)}
.locate .spin{animation:spin .9s linear infinite}
.readout{position:absolute;left:50%;transform:translateX(-50%);bottom:58px;display:flex;align-items:center;gap:8px;border-radius:999px;padding:7px 14px;font-family:var(--mono);font-size:11.5px;color:var(--fg2);animation:rise .3s both;border-color:rgba(79,157,255,.35)}
.readout.warn{border-color:rgba(255,176,32,.3);font-family:var(--sans);font-size:12px}
/* position:relative moved here off the element's inline style — inline, it
   outranked the phone rule below and the card ran off the screen. */
.webcard{position:relative;width:300px;flex:0 0 300px;white-space:normal;border-radius:22px;padding:20px;box-shadow:0 20px 40px -20px rgba(0,0,0,.8);animation:rise .5s var(--ease) both;background:rgba(17,17,20,.94)}
.webcard h2{margin:6px 0 16px;font-size:22px;font-weight:600;letter-spacing:-.02em;line-height:1.1;padding-right:26px}
.webcard .stats{display:grid;grid-template-columns:1fr 1fr;gap:12px}
.webcard .stats span.n{display:block;font-family:var(--mono);font-size:24px;font-weight:500;letter-spacing:-.03em;line-height:1}
.webcard .stats span.l{font-size:12px;color:var(--fg2)}
.webcard p{font-size:12.5px;color:var(--fg2);line-height:1.6;margin:16px 0 14px;border-top:1px solid var(--hair);padding-top:14px;text-wrap:pretty}
.webcard .acts{display:flex;gap:8px}
.webcard .acts .btn{flex:1;border-radius:999px;padding:10px;min-height:44px}
.webcard .close{position:absolute;top:14px;right:14px;width:28px;height:28px;background:none;border:0;color:var(--faint);font-size:16px;cursor:pointer;line-height:1}
/* Lines mode's activity card (2026-09-16) wears the web card. A long
 * activity name stops at two lines; the stepper walks every activity a tap
 * landed on when several are drawn along the same stretch. */
.actcard h2{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;overflow-wrap:anywhere}
/* The locked activity card (billing 6.3). It replaces the stats rather than
   greying them: the server sends no numbers for a locked ride, so there is
   nothing to grey out — the lock IS the content. */
/* The lock, wherever something is Explore-only (billing 6.3, 6.5). ⚠️ NOT
   scoped to `.actcard`: it started there, on the map's locked ride, and the
   upload page needs the same lock for the bulk uploader. A second copy under
   a second parent is two locks that drift — and `.actcard .lockbox` would
   beat `.lockbox` on specificity per PROPERTY, so the drift would show up as
   half this block applying and half not. */
.lockbox{display:flex;flex-direction:column;align-items:center;text-align:center;gap:.5rem;padding:.9rem .4rem .2rem}
.lockbox .lockicon{color:var(--muted);opacity:.85}
.lockbox .lockhead{margin:0;font-size:15px;font-weight:600;letter-spacing:.02em}
.lockbox .lockwhy{margin:0;font-size:12.5px;line-height:1.5;color:var(--muted);text-wrap:pretty;max-width:30ch}
.lockbox .lockcta{margin-top:.35rem;width:100%}
/* The card form is wider than the map's 260px panel, so the copy may breathe
   and the button need not span the card. */
.card.lockbox{gap:.7rem;max-width:none}
.card.lockbox .lockwhy{max-width:52ch;font-size:13.5px}
.card.lockbox .lockcta{width:auto;padding-left:1.4rem;padding-right:1.4rem}
.actcard.many h2{min-height:2.2em}
.actcard .eyebrow{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding-right:26px}
.actcard p:empty{display:none}
.actcard .pick{display:flex;align-items:center;justify-content:space-between;gap:8px;margin:14px 0 12px;padding:4px;border-radius:999px;background:rgba(255,255,255,.05);border:1px solid var(--hair)}
.actcard .pick.hidden{display:none}
.actcard .pick .of{font-size:12px;color:var(--fg2)}
.actcard .step{width:36px;height:36px;border-radius:50%;border:0;background:rgba(255,255,255,.07);color:var(--fg);font-size:20px;line-height:1;cursor:pointer}
.actcard .step:hover{background:rgba(255,255,255,.12)}
.actcard .step:active{background:rgba(255,255,255,.18);transition-duration:0s}
.actcard p:empty + .pick{margin-top:16px}
.actcard .close:active,.webcard .close:active{color:var(--fg);transition-duration:0s}
.maplibregl-ctrl-attrib,.maplibregl-ctrl-bottom-right,.maplibregl-ctrl-bottom-left{display:none}
.maplibregl-popup-content{background:var(--surface);color:var(--fg);border:1px solid var(--hair);border-radius:12px;font-family:var(--sans);font-size:13px;padding:12px 14px}
.maplibregl-popup-tip{border-top-color:var(--surface) !important;border-bottom-color:var(--surface) !important}
.maplibregl-popup-close-button{color:var(--muted)}
.mapbox{position:relative;border-radius:var(--r-card);overflow:hidden;border:1px solid var(--hair);background:var(--map);height:460px}
.mapbox .ml{position:absolute;inset:0}
.mapbox .key{position:absolute;left:14px;bottom:34px;display:flex;gap:14px;flex-wrap:wrap;border-radius:10px;padding:8px 12px;font-size:12px;color:var(--fg2);background:rgba(17,17,20,.86);border:1px solid rgba(255,255,255,.1);backdrop-filter:blur(8px)}
.mapbox .key span{display:flex;align-items:center;gap:7px}
.mapbox .key i{display:inline-block;width:20px;height:3px;border-radius:2px}
.mapbox .key i.dash{height:0;border-top:3px dashed var(--warn);background:none}
.mapbox .mapnote{position:absolute;left:14px;top:14px;border-radius:10px;padding:8px 12px;font-size:12px;color:var(--fg2);background:rgba(17,17,20,.86);border:1px solid rgba(255,255,255,.1)}
/* The small maps' control cluster (2026-09-16) — `embedchrome.js`, a MapLibre
 * control in `bottom-right` on all six embedded maps: compass, zoom pair,
 * basemap picker, the main map's `.brctl` corner and order one size down.
 * The rule above hides both bottom corners for the main map's sake; the two
 * the embeds use come back inside `.mapbox` only. ⚠️ The bottom-left one is
 * the OSM / OpenFreeMap attribution, which that rule had hidden on every
 * embed — a licence requirement, not a style. Scoped to `.mapbox`, so the
 * main map screen is untouched. */
/* the two corners the embeds actually use */
.mapbox .maplibregl-ctrl-bottom-right{display:flex;flex-direction:column;align-items:flex-end}
/* The left corner stops short of the cluster (12 + 34 + 12), so a long
 * attribution wraps instead of running under the basemap button. */
.mapbox .maplibregl-ctrl-bottom-left{display:block;right:58px}
/* The legend, moved into that corner by V2Embed, stacks above the credit. */
.mapbox .maplibregl-ctrl.key{position:static;float:left;margin:0 0 8px 12px;font-family:var(--sans);line-height:normal}
/* The credit on EVERY map (embedchrome.js credit()): small and quiet. The
 * global rule above hides MapLibre's attribution; `.ec-credit` is ours, and
 * three classes beat maplibre-gl.css's two. */
.maplibregl-ctrl.maplibregl-ctrl-attrib.ec-credit{
  display:block;padding:1px 5px;border-radius:4px;
  font:500 8.5px/12px var(--sans);letter-spacing:.01em;
  background:rgba(12,12,15,.32);color:rgba(255,255,255,.62);border:0}
.mapbox .maplibregl-ctrl-bottom-left .ec-credit{margin:0 0 8px 10px}
/* The main map: the credit sits left of the page's own compass cluster
 * (.brctl, 16px in, 34px wide — 44px on a phone) and wraps inside the right half, clear of the
 * status pill on the left. */
.mapwrap .maplibregl-ctrl-bottom-right{display:block;left:50%}
.mapwrap .maplibregl-ctrl-bottom-right .ec-credit{margin:0 58px 20px 0}
.maplibregl-ctrl-attrib.ec-credit a{color:inherit}
.maplibregl-ctrl-attrib.ec-credit a:hover{color:#fff;text-decoration:none}
/* Folded (embedchrome.js credit()): only the ⓘ shows. The button is 14px to
 * see and 34px to hit. */
.maplibregl-ctrl.maplibregl-ctrl-attrib.ec-credit.ec-folded{background:none;padding:0}
.ec-credit .maplibregl-ctrl-attrib-inner{display:inline}
.ec-credit.ec-folded .maplibregl-ctrl-attrib-inner{display:none}
.ec-credit-i{position:relative;display:inline-grid;place-items:center;width:14px;height:14px;margin:0 5px 0 -2px;padding:0;
  vertical-align:-3px;border:0;border-radius:50%;cursor:pointer;
  font:italic 700 9px/1 Georgia,serif;background:rgba(12,12,15,.45);color:rgba(255,255,255,.8)}
.ec-folded .ec-credit-i{margin:0}
.ec-credit-i::after{content:"";position:absolute;inset:-10px}
.ec-credit-i:hover{color:#fff;background:rgba(12,12,15,.7)}
.ec-credit-i:focus-visible{outline:2px solid var(--red);outline-offset:1px}

/* the cluster itself — compass, zoom pair, basemap; 12px off the corner */
.mapbox .maplibregl-ctrl.ectl{
  margin:0 12px 12px 0;display:flex;flex-direction:column;align-items:flex-end;gap:8px;background:none;box-shadow:none}
.ectl button{font-family:inherit;color:var(--fg);cursor:pointer;padding:0}
.ectl button:focus-visible{outline:2px solid var(--red);outline-offset:-2px}

.ectl .ec-compass{
  width:34px;height:34px;border-radius:50%;display:grid;place-items:center;touch-action:none;cursor:grab;
  border:1px solid rgba(255,255,255,.1);background:rgba(17,17,20,.9);backdrop-filter:blur(12px)}
.ectl .ec-compass:hover{border-color:rgba(255,255,255,.3)}
.ectl .ec-compass svg{width:21px;height:21px;transition:transform .5s var(--ease)}
.ectl .ec-compass.dragging{cursor:grabbing}
.ectl .ec-compass.dragging svg{transition:none}

.ectl .ec-zoom{
  width:34px;display:flex;flex-direction:column;border-radius:12px;overflow:hidden;
  background:rgba(17,17,20,.88);border:1px solid rgba(255,255,255,.1);backdrop-filter:blur(12px)}
.ectl .ec-zoom button{
  width:100%;height:34px;border:0;border-bottom:1px solid var(--hair);background:transparent;
  font-size:18px;line-height:1;display:grid;place-items:center}
.ectl .ec-zoom button:last-child{border-bottom:0}
.ectl .ec-zoom button:hover{background:rgba(255,255,255,.06)}
.ectl .ec-zoom button:active{background:rgba(255,255,255,.14);transition-duration:0s}

.ectl .ec-bm{position:relative;display:flex}
.ectl .ec-bmbtn{
  width:34px;height:34px;border-radius:12px;display:grid;place-items:center;
  border:1px solid rgba(255,255,255,.1);background:rgba(17,17,20,.88);backdrop-filter:blur(12px)}
.ectl .ec-bmbtn:hover{background:rgba(255,255,255,.06)}
/* the product's own `layers` glyph at the main map's weight: v2_tags.ICONS
 * ["layers"] at stroke-width 1.8 — the same icon the Layers button renders */
.ectl .ec-bmbtn svg{width:16px;height:16px}
.ectl .ec-bmbtn[aria-expanded="true"]{
  background:rgba(255,45,45,.16);border-color:rgba(255,45,45,.55);color:var(--red-link)}

/* the popover opens upward from the button and is inert until .open */
.ectl .ec-bmpop{
  position:absolute;right:0;bottom:calc(100% + 8px);width:196px;padding:10px;border-radius:14px;
  display:none;flex-direction:column;gap:9px;
  background:rgba(17,17,20,.94);border:1px solid rgba(255,255,255,.12);backdrop-filter:blur(12px);
  box-shadow:0 18px 40px -18px rgba(0,0,0,.9)}
.ectl .ec-bmpop.open{display:flex}
.ectl .ec-cap{font-size:10px;letter-spacing:.12em;color:var(--muted);font-weight:600;text-transform:uppercase}
.ectl .ec-bmgrid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:7px}
.ectl .ec-bmgrid button{
  display:flex;flex-direction:column;gap:5px;padding:5px;border-radius:10px;
  border:1px solid var(--hair);background:var(--surface);color:var(--fg2);text-align:left}
.ectl .ec-bmgrid button:hover{border-color:rgba(255,255,255,.22);color:var(--fg)}
.ectl .ec-bmgrid .sw{height:24px;border-radius:6px}
.ectl .ec-bmgrid .nm{font-size:10px;font-weight:600}
.ectl .ec-bmgrid button[aria-pressed="true"]{
  border-color:rgba(255,45,45,.6);background:rgba(255,45,45,.12);color:var(--fg)}
/* Per-page map heights. These are TWO classes deliberately, and every one of
 * them is answered again inside the one breakpoint: a height declared here
 * beats the breakpoint's bare `.mapbox` on specificity, and a height declared
 * in a template's `style=` is not reachable by the breakpoint at all. A map
 * that stays 480px tall on a 375px phone is what an inline height looks like
 * from the outside. `.sharemap` is the exception that proves it — its height
 * is viewport-relative, so it needs no phone rule. */
.mapbox.webmap{height:420px}
.mapbox.spanmap{height:480px}
.mapbox.gapmap{height:500px}
.mapbox.sharemap{height:min(62vh,560px);border-radius:20px}

/* ---- misc components ---- */
.steps{display:flex;gap:8px;align-items:center;margin-bottom:40px}
.steps .st{display:flex;align-items:center;gap:8px;flex:1}
.steps .n{width:26px;height:26px;border-radius:50%;display:grid;place-items:center;font-size:12px;font-weight:600;border:1px solid var(--hair-strong);color:var(--faint);font-family:var(--mono)}
.steps .l{font-size:13px;color:var(--faint)}
.steps .line{flex:1;height:1px;background:var(--hair);margin-left:8px}
.steps .st.done .n{background:var(--red);border-color:var(--red);color:var(--fg)}
.steps .st.done .l{color:var(--fg)}.steps .st.done .line{background:var(--red)}
.steps .st.now .n{background:rgba(255,45,45,.15);border-color:var(--red);color:var(--fg)}
.steps .st.now .l{color:var(--fg)}
.srcrow{background:var(--map);border:1px solid var(--hair);border-radius:var(--r-row);padding:16px;display:grid;grid-template-columns:160px minmax(0,1fr) auto;align-items:center;gap:16px}
.srcrow .logo{min-height:96px;display:grid;place-items:center}
.srcrow .name{font-size:15px;font-weight:600}
.srcrow .desc{font-size:12.5px;color:var(--muted);line-height:1.5;text-wrap:pretty}
.srcrow .btn{height:48px;padding:0 22px;border-radius:var(--r-btn);font-size:13.5px;font-weight:600;justify-self:start}
.dropzone{border:1px dashed var(--hair-strong);border-radius:var(--r-lg);padding:22px;text-align:center;color:var(--fg2);font-size:13px;cursor:pointer;transition:border-color .2s,background .2s}
.dropzone:hover,.dropzone.over{border-color:var(--red);color:var(--fg);background:rgba(255,45,45,.04)}
.dropzone.big{padding:38px 20px;border-radius:14px}
.dropzone .t{font-size:16px;font-weight:600;color:var(--fg)}
.srccard{text-align:left;border-radius:var(--r-card);padding:16px;cursor:pointer;display:flex;flex-direction:column;gap:12px;transition:border-color .18s,background .18s;background:var(--surface);border:1px solid var(--hair);color:var(--fg);text-decoration:none}
.srccard:hover{border-color:rgba(255,255,255,.28);color:var(--fg)}
.srccard.on{background:var(--raised);border-color:rgba(255,45,45,.45)}
.srccard .band{display:grid;place-items:center;min-height:116px;padding:14px;background:var(--map);border:1px solid var(--hair-faint);border-radius:var(--r-lg)}
.srccard .name{font-size:14.5px;font-weight:600}
.srccard .st{display:flex;align-items:center;gap:7px;font-size:12.5px;color:var(--muted)}
.srccard .st.ok{color:var(--ok)}
.note{background:var(--ground);border:1px solid var(--hair);border-radius:var(--r-btn);padding:12px 14px;font-family:var(--mono);font-size:12.5px;color:var(--fg2);line-height:1.85;white-space:pre-line}
.note .u{color:var(--fg)}
.gaprow{display:flex;flex-wrap:wrap;gap:14px 24px;align-items:center;background:var(--surface);border:1px solid var(--hair);border-radius:var(--r-row);padding:16px 20px}
.gaprow:hover{border-color:rgba(255,255,255,.18)}
.gaprow .rank{font-family:var(--mono);font-size:20px;color:var(--faint);width:32px}
.gaprow .who{flex:1 1 260px;min-width:0}
.gaprow .who .n{font-weight:500;font-size:15px}
.gaprow .who .s{font-size:12px;color:var(--muted);margin-top:2px;font-family:var(--mono)}
.gaprow .m{font-family:var(--mono);font-size:18px;font-weight:500;letter-spacing:-.02em}
.gaprow .ml{font-size:11px;color:var(--muted)}
.gaprow .tier{display:flex;align-items:center;gap:10px;font-size:12.5px;color:var(--fg2);flex:1 1 220px;min-width:0}
.gaprow .tier i{width:26px;height:0;border-top:3px solid var(--ok);flex:0 0 auto}
.gaprow .tier i.dash{border-top-style:dashed;border-top-color:var(--warn)}
.gaprow .acts{display:flex;gap:6px;flex-wrap:wrap}
.gaprow .tier .why{color:var(--muted)}
.routing{display:inline-flex;align-items:center;gap:6px}

/* ---- one gap and its route (Where next, 2026-09-16) ---- */
.gapacts{display:flex;gap:8px;flex-wrap:wrap;align-items:center}
.gapcard{padding:18px 20px}
.tile .v .u{font-size:13px;font-weight:400;color:var(--fg2)}
.cxkey i.gapkey{height:6px;border-radius:3px;background:linear-gradient(var(--warn) 0 30%,#fff 30% 70%,var(--warn) 70%)}
.cxkey i.gapkey.dash{height:0;border-top:3px dashed var(--warn);background:none}
.mapbox .mapstate{position:absolute;left:50%;top:14px;transform:translateX(-50%);z-index:3;display:flex;align-items:center;gap:8px;white-space:nowrap;border-radius:999px;padding:7px 14px;font-size:12.5px;color:var(--fg);background:rgba(17,17,20,.9);border:1px solid rgba(255,255,255,.12)}
.mapbox .mapstate[hidden]{display:none}
.spin{display:inline-block;flex:0 0 auto;width:12px;height:12px;border-radius:50%;border:2px solid rgba(255,255,255,.18);border-top-color:var(--warn);animation:spin .9s linear infinite}
.spin.lg{width:24px;height:24px;border-width:3px}
.cues{list-style:none;margin:0;padding:0}
.cue{display:grid;grid-template-columns:28px minmax(0,1fr) auto;gap:12px;align-items:center;padding:11px 0;border-top:1px solid var(--hair)}
.cue:first-child{border-top:0}
.cue b{font-weight:500}
.cue .n{font-size:12px;color:var(--faint);text-align:center}
.cue .dot{width:12px;height:12px;border-radius:50%;justify-self:center;box-shadow:0 0 0 2.5px var(--ground),0 0 0 3.5px rgba(255,255,255,.25)}
.cue .s{font-size:12px;color:var(--muted);margin-top:2px}
.cue .d{font-size:13px;color:var(--fg2);white-space:nowrap}
.cue.end .d{font-size:11px;color:var(--muted)}
.surfbar{display:flex;height:10px;border-radius:5px;overflow:hidden;background:var(--sunken)}
.surfbar i,.surfkey i{display:block}
.surfbar .unpaved,.surfkey .unpaved{background:#c98a4b}
.surfbar .paved,.surfkey .paved{background:#8d929c}
.surfbar .unknown,.surfkey .unknown{background:repeating-linear-gradient(135deg,#3f3f46 0 3px,#26262c 3px 6px)}
.surfkey{margin:12px 0 0;display:flex;flex-direction:column;gap:6px;font-size:13px}
.surfkey div{display:flex;justify-content:space-between;align-items:center}
.surfkey dt{display:flex;align-items:center;gap:8px;color:var(--fg2)}
.surfkey dd{margin:0}
.surfkey i{width:10px;height:10px;border-radius:3px}
.locker{position:absolute;left:0;right:0;bottom:0;height:62%;display:grid;place-items:center;background:linear-gradient(rgba(9,9,11,0),rgba(9,9,11,.85) 40%)}
.nmcard{background:var(--surface);border:1px solid var(--hair);border-radius:var(--r-card);overflow:hidden;display:flex;flex-direction:column}
.nmcard:hover{border-color:rgba(255,255,255,.18)}
.nmcard img,.nmcard svg.th{display:block;width:100%;background:var(--map);border-bottom:1px solid var(--hair-faint);aspect-ratio:300/170;object-fit:cover}
/* The thumbnail is wrapped in a link to the editor. Blockified anyway as a
 * flex item, but stated so the card does not depend on that. */
.nmcard>a{display:block;line-height:0}
.nmcard .body{padding:16px 18px;display:flex;flex-direction:column;gap:10px;flex:1}
/* ---- the connect-the-lines editor (V2-FEATURE-GAP D3) ---- */
/* The control row under the map. Two controls that do different jobs: the
 * frame CLIPS and is also the snap radius, the toggle does not clip at all. */
/* The editor's map is taller than a read-only detail map: the page asks for a
 * 40 m drag to be accurate. ⚠️ Declared as a CLASS, not as an inline
 * `style="height:520px"`, so the breakpoint below can actually reach it — an
 * inline declaration cannot be overridden by a media query without
 * `!important`, which is the specificity trap ResponsiveTests was written for
 * (measured here: 520px survived onto a 375px phone). */
.mapbox.cxmap{height:520px}
/* The editor's legend. Below the map rather than over it — five inks wrapped
 * to three lines as an overlay and covered the bottom fifth of a 375px map.
 * Same swatch shapes as `.mapbox .key` so the two read as one system. */
.cxkey{display:flex;flex-wrap:wrap;gap:8px 16px;margin-top:10px;font-size:12px;color:var(--fg2)}
.cxkey span{display:flex;align-items:center;gap:7px}
.cxkey i{display:inline-block;width:20px;height:3px;border-radius:2px}
.cxkey i.dash{height:0;border-top:3px dashed var(--warn);background:none;border-radius:0}
.cxbar{display:flex;flex-wrap:wrap;align-items:center;gap:10px 14px;margin-top:12px}
/* One ride in the sidebar's list, its swatch painted from the map's own ink by
 * connect.js so "which line is that?" is answerable without hovering every
 * line on a 400 m frame. */
.cxrow{display:flex;gap:10px;align-items:baseline;padding:9px 0;border-top:1px solid var(--hair-faint);font-size:13px}
.cxrow:first-child{border-top:0;padding-top:0}
.cxsw{flex:0 0 auto;width:12px;height:3px;border-radius:2px;background:var(--chrome);margin-top:6px}
/* The verdict panel. Sticky beside the map, and FIRST in the sidebar, because
 * it is the only thing on this page that appears mid-gesture — in V1 it began
 * below the map and two paragraphs of prose, so the answer to "did that drag
 * work?" was to stop and scroll (Greg, 2026-08-31). `top` clears the sticky
 * topline. Its phone form is a bottom sheet, inside the one 720px block. */
.cxpanel{position:sticky;top:78px;z-index:20;border-color:rgba(61,220,132,.25);box-shadow:0 18px 40px -18px rgba(0,0,0,.8)}
/* The Crop page (2026-09-15). The map's height is a class, declared here and
 * again inside the one 720px block, for the reason `.cxmap` gives above. */
.mapbox.cropmap{height:460px}
.susth{display:block;width:120px;height:80px;border-radius:10px;background:var(--map);border:1px solid var(--hair-faint)}
.cropslider.cut .track{background:var(--red)}
.cropslider.cut .fill{background:rgba(77,163,255,.85)}
.cropmodes{display:flex;gap:6px;margin-bottom:12px;flex-wrap:wrap}
.cropwhy{border-left:3px solid var(--warn);padding:2px 0 2px 12px;margin-bottom:14px}
.cxkey i.dot{width:10px;height:10px;border-radius:50%}
/* The dual-handle slider: two native ranges stacked on one track, each
 * transparent and click-through except at its thumb, with the kept stretch
 * painted red on a blue track — Strava's kept/removed colours, and the map's.
 * Native inputs rather than a drawn widget so the keyboard, a screen reader
 * and a coarse pointer all get the browser's own handling for free. */
.cropslider{position:relative;height:36px;margin:4px 0 2px}
.cropslider .track{position:absolute;left:0;right:0;top:15px;height:6px;border-radius:3px;background:rgba(77,163,255,.45)}
.cropslider .fill{position:absolute;top:0;bottom:0;left:0;right:0;border-radius:3px;background:var(--red)}
.cropslider input[type=range]{position:absolute;left:0;top:0;width:100%;height:36px;margin:0;padding:0;background:none;-webkit-appearance:none;appearance:none;pointer-events:none;outline:none;z-index:2}
.cropslider input[type=range].top{z-index:3}
.cropslider input[type=range]::-webkit-slider-runnable-track{background:none;height:36px}
.cropslider input[type=range]::-moz-range-track{background:none;height:36px}
.cropslider input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;pointer-events:auto;width:22px;height:22px;margin-top:7px;border-radius:50%;border:2px solid #0d0d0f;background:#3ddc84;cursor:grab;box-shadow:0 2px 8px rgba(0,0,0,.5)}
.cropslider input[type=range]::-moz-range-thumb{pointer-events:auto;width:18px;height:18px;border-radius:50%;border:2px solid #0d0d0f;background:#3ddc84;cursor:grab;box-shadow:0 2px 8px rgba(0,0,0,.5)}
.cropslider input#cropend::-webkit-slider-thumb{background:#ffb020}
.cropslider input#cropend::-moz-range-thumb{background:#ffb020}
.cropslider input[type=range]:focus-visible::-webkit-slider-thumb{box-shadow:0 0 0 3px rgba(255,45,45,.4)}
.cropslider input[type=range]:focus-visible::-moz-range-thumb{box-shadow:0 0 0 3px rgba(255,45,45,.4)}
.cropslider input[type=range]:disabled::-webkit-slider-thumb{opacity:.45;cursor:not-allowed}
/* A finger, not a cursor: crop.js adds `.coarse` on a coarse pointer, so the
 * thumbs grow to a real hit target without a second @media in this file. */
.cropslider.coarse input[type=range]::-webkit-slider-thumb{width:28px;height:28px;margin-top:4px}
.cropslider.coarse input[type=range]::-moz-range-thumb{width:24px;height:24px}
.cropends{display:grid;grid-template-columns:1fr 1fr 1fr;gap:12px;margin-top:10px;align-items:start}
.cropend.keep{text-align:center}
.cropend.last{text-align:right}
.cropend .num{white-space:nowrap}
.nudge{display:inline-flex;gap:4px;margin-top:6px}
.nudge .btn{min-width:38px;padding:5px 0;font-size:18px;line-height:1}
/* Photo pins on an activity map (2026-09-13). A MapLibre Marker positions its
   element with `position:absolute` and a `transform`, so NOTHING here may set
   either on .photopin itself — this sheet loads before maplibre-gl.css on some
   pages and after it on others, and a transform here would pin every photo to
   the map's corner. Hover scales the IMAGE, not the button. */
.photopin{width:44px;height:44px;box-sizing:border-box;border-radius:50%;overflow:hidden;padding:0;margin:0;background:#1a1a1f;cursor:pointer;box-shadow:0 2px 10px rgba(0,0,0,.5);display:block}
.photopin.fix{border:2px solid #fff}
.photopin.guess{border:2px dashed #4dd2ff}
.photopin img{width:100%;height:100%;object-fit:cover;display:block;transition:transform .15s;pointer-events:none}
.photopin:hover img,.photopin:focus-visible img{transform:scale(1.1)}
.photopin:focus-visible{outline:2px solid var(--red);outline-offset:2px}
.photopin.bare{width:16px;height:16px;background:#4dd2ff}
.photopin.guess.bare{background:#0c0c0f}
.photopin .more{position:absolute;left:0;right:0;bottom:0;font:600 10px/14px var(--mono);color:#fff;background:rgba(0,0,0,.62);text-align:center;display:none}
.photopin.stacked .more{display:block}
.photopin.gone{display:none}
/* The activity page's sidebar (2026-09-16 handoff). The four figures read as
 * number-then-label on one baseline, two columns at EVERY width — this is not
 * `.grid2`, which the breakpoint folds to one column and which made the card
 * push the meta list off a phone's first screen. */
.actstats{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:9px 14px}
.actstats>div{display:flex;align-items:baseline;gap:6px;min-width:0}
.actstats .n{font-family:var(--mono);font-weight:500;letter-spacing:-.02em;font-size:21px;white-space:nowrap}
.actstats .n.sm{font-size:15px}
.actstats .l{font-size:11px;color:var(--fg2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.actstats+.hr{margin:14px 0}
/* Every source that delivered the activity, arrival order. The one the map
 * draws is tagged; the rest say they were dropped as duplicates. */
.srcs{margin-top:14px;padding-top:14px;border-top:1px solid var(--hair-faint)}
.srcs .head{display:flex;align-items:baseline;justify-content:space-between;gap:10px;margin-bottom:9px;font-size:13px;color:var(--fg2)}
.srcs .head .mono{font-size:11px;color:var(--faint)}
.srcs .list{display:flex;flex-direction:column;gap:9px}
.srcs .src{display:flex;align-items:center;justify-content:space-between;gap:10px;min-width:0}
.srcs .who{display:flex;flex-direction:column;min-width:0}
.srcs .nm{font-size:12.5px;color:var(--fg);display:flex;align-items:center;gap:6px}
.srcs .id{font-family:var(--mono);font-size:11px;color:var(--faint);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.srcs .drawn{font-size:9.5px;letter-spacing:.06em;text-transform:uppercase;color:var(--red);border:1px solid rgba(255,45,45,.35);border-radius:5px;padding:1px 5px}
.srcs .view{color:var(--fg2);font-size:12px;text-decoration:underline;text-underline-offset:3px;white-space:nowrap;flex:0 0 auto}
.srcs .view:hover{color:var(--fg)}
.srcs .view.strava,.srcs .view.strava:hover{color:#fc5200;font-weight:600}
.photostrip{display:flex;gap:10px;overflow-x:auto;padding:16px 0 4px;scrollbar-width:thin}
.photostrip a{flex:0 0 auto;width:180px;height:130px;border-radius:var(--r-lg);border:1px solid var(--hair);overflow:hidden;background:var(--map);transition:transform .15s;position:relative}
.photostrip a:hover{transform:translateY(-3px)}
.photostrip img{width:100%;height:100%;object-fit:cover;display:block}
.photostrip .cap{position:absolute;left:10px;bottom:10px;font-size:11px;color:rgba(255,255,255,.7);font-family:var(--mono)}
/* A photo Strava is still processing: the strip's box, dashed and empty, with
   the words — never Strava's own grey placeholder graphic dressed as a photo. */
.photostrip .pending{flex:0 0 auto;width:180px;height:130px;border-radius:var(--r-lg);border:1px dashed var(--hair-strong);display:grid;place-items:center;padding:0 18px;text-align:center;font-family:var(--mono);font-size:11px;letter-spacing:.04em;text-transform:uppercase;color:var(--muted)}
.milestone{display:flex;align-items:center;gap:14px}
.milestone .ck{width:28px;height:28px;border-radius:50%;display:grid;place-items:center;flex:0 0 auto;border:1px solid var(--hair-strong)}
.milestone.done .ck{background:var(--red);border-color:var(--red)}
.bar-thin.grow>span{transition:width 1s}
/* ---- Stats: the unique-miles card (figure + curve, ONE card) ----
   The number IS the right-hand end of the line, so they share a surface. */
.uniquecard{padding:26px 24px 18px;margin-bottom:16px}
.uhead{display:flex;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;gap:16px}
.statnum{font-family:var(--mono);font-size:96px;font-weight:500;letter-spacing:-.05em;line-height:1;margin:10px 0 8px;font-variant-numeric:tabular-nums}
.uyear{text-align:right;flex:0 0 auto}
.uyear .l{font-size:11px;letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}
.uyear .v{font-family:var(--mono);font-size:20px;font-weight:500;letter-spacing:-.02em;margin-top:4px;color:var(--link);white-space:nowrap;font-variant-numeric:tabular-nums}
.uyear .n{font-size:11.5px;color:var(--faint);margin-top:2px;white-space:nowrap}
.ufoot{display:flex;justify-content:flex-end;align-items:baseline;flex-wrap:wrap;gap:8px;margin-top:6px;padding-top:14px;border-top:1px solid var(--hair-faint);font-size:12px;color:var(--muted)}
.tiles{margin-bottom:20px}
/* The growth chart's axis labels: HTML over the SVG, so they stay 11px at
   every card width. See _growth.html and views._growth_chart. */
.gwrap{position:relative;margin-top:12px}
.gwrap .gy,.gwrap .gx{position:absolute;font-size:11px;color:var(--faint);font-family:var(--mono);pointer-events:none;white-space:nowrap}
.gwrap .gy{left:0;transform:translateY(-50%)}
.gwrap .gx{top:88.5%;transform:translateX(-50%)}
/* Records: four figures, 2×2. Static — they take no part in the count-up. */
.records{gap:16px}
.records .num{font-size:24px}
.records .rl{font-size:12px;color:var(--fg2);margin-top:2px}
.records .rd{font-size:12px;color:var(--muted)}
.records .rd a{color:inherit}
.records .rd a:hover{color:var(--link)}
/* The web swatch before a name: the map's ink for that web's size rank. */
.swatch{display:inline-block;width:10px;height:3px;border-radius:2px;margin-right:10px;vertical-align:middle}

/* The streak card (Stats). Rest days are training, not failures: there is no
 * alarm state here and there must not be one (handoff README § 12). */
.streakcard{padding:22px 24px;margin-bottom:20px}
/* ⚠️ THE LEFT TRACK IS A FLOOR, NOT A WIDTH. A hard `190px` here is narrower
 * than the odometer the moment the streak reaches three digits — four slabs
 * plus DAYS measures 274px — and a fixed track does not grow, so the counter
 * ran 56px into the rail (Greg, 2026-09-20, on a 2,978-day streak). `minmax`
 * keeps 190px as the base so a two-digit streak looks exactly as designed,
 * and lets the track take its content's width past that; the rail's
 * `minmax(0,1fr)` absorbs the difference. */
.streak{display:grid;grid-template-columns:minmax(190px,max-content) minmax(0,1fr);gap:28px;align-items:center;margin-top:16px}
.streak .left{display:flex;flex-direction:column;gap:10px;align-items:flex-start}
.streak .fig{display:flex;align-items:baseline;gap:8px}
.streak .fig b{font-family:var(--mono);font-size:54px;font-weight:500;letter-spacing:-.04em;line-height:1;font-variant-numeric:tabular-nums}
/* `>span`, never ` span`: the count-up's own <span data-count> sits inside the
 * <b>, and a descendant selector shrank the 54px figure to the suffix's 14px. */
.streak .fig>span{font-size:14px;color:var(--fg2)}
.streak .when{font-size:12.5px;color:var(--muted);font-family:var(--mono)}
.streak .pill{display:flex;align-items:center;gap:8px;border-radius:999px;padding:6px 12px;font-size:12.5px;background:rgba(61,220,132,.08);border:1px solid rgba(61,220,132,.28);color:var(--ok)}
.streak .pill.warn{background:rgba(255,176,32,.06);border-color:rgba(255,176,32,.28);color:var(--warn)}
.streak .pill i{width:7px;height:7px;border-radius:50%;background:currentColor;flex:0 0 auto;animation:pulse 1.6s ease-in-out infinite}
/* The odometer. Digit slabs, minimum two — the number is what people care
   about, and a flame icon adds nothing a figure does not already say. */
.odo{display:flex;gap:6px;align-items:stretch}
.odo>span{background:var(--well);border:1px solid rgba(255,255,255,.1);border-radius:10px;padding:10px 4px;min-width:52px;display:grid;place-items:center;font-family:var(--mono);font-size:54px;font-weight:500;letter-spacing:-.04em;line-height:1;color:var(--fg);font-variant-numeric:tabular-nums}
.odo>b{align-self:flex-end;font-size:13px;font-weight:400;color:var(--muted);padding:0 0 12px 6px;letter-spacing:.06em;text-transform:uppercase}
.streak .when{margin-top:14px}
/* The rail, up to RAIL_CELL_MAX days: real elements, one per day, each with
   its own title. Height encodes the new line found that day. */
.streak .cells{display:flex;gap:2px;align-items:flex-end;height:44px}
.streak .cells span{flex:1 1 0;min-width:0;border-radius:1px;background:rgba(255,255,255,.07)}
.streak .cells span.live{background:var(--red)}
.streak .cells span.active{background:var(--red-deep)}
.streak .axis{display:flex;justify-content:space-between;margin-top:6px;font-size:11px;color:var(--faint);font-family:var(--mono)}
/* Past it, an SVG stretched to the box: 90 to 365 bars cannot be a flex row
   without each one collapsing sub-pixel. preserveAspectRatio="none", so the
   pitch is a ratio and holds at every window on the ladder. */
.streak .yearrail{display:block;width:100%;height:44px}
.streak .yearrail rect{fill:rgba(255,255,255,.11)}
.streak .yearrail rect.live{fill:var(--red)}
.streak .yearrail rect.active{fill:var(--red-deep)}
.streak .runmark{position:relative;height:15px}
.streak .runmark .rule{position:absolute;top:0;min-width:2px;height:2px;background:var(--red);border-radius:2px}
/* ⚠️ `line-height:1` is load-bearing: the row is 15px and the label sits
 * 3px down it, so an inherited 1.5 line box is 15.8px tall and hangs
 * 3.8px below its own row — 1.75px of that landed on the month label
 * under it, which is the crowding around 'Sep' in Greg's screenshot. */
.streak .runmark .lbl{position:absolute;right:0;top:3px;line-height:1;font-size:10.5px;color:var(--link);font-family:var(--mono);white-space:nowrap}
.streak .months{position:relative;height:14px;margin-top:2px}
.streak .months span{position:absolute;font-size:10px;color:var(--faint);font-family:var(--mono)}
.streak .winnote{margin-top:10px;font-size:11px;color:var(--faint);font-family:var(--mono)}
.streak .kpis{gap:14px;margin-top:18px;padding-top:16px;border-top:1px solid var(--hair-faint)}
.streak .kpi .l{font-size:12px;color:var(--fg2)}
.streak .kpi .v{font-family:var(--mono);font-size:15px;margin-top:4px}
.streak .kpi .v>span{font-size:12px;color:var(--muted);margin-left:4px;font-family:var(--sans)}
.streak .kpi .n{font-size:11.5px;color:var(--faint);margin-top:2px}
/* The count-up's first frame. stats.html's head script sets `cu-wait` before
 * the body parses and v2.js removes it on the frame it paints t=0, so the
 * first thing on screen is a zero and an undrawn line — never the final
 * number flashing before the roll. The script also removes it after 2.5 s on
 * its own, so a page whose v2.js never ran still shows its real figures. */
.cu-wait [data-count]:not([data-count-odo]){visibility:hidden}
/* The odometer hides its DIGITS, not its slabs: the boxes are the design and
   an empty row of them is what the first frame should show. */
.cu-wait [data-count-odo]>span{visibility:hidden}
.cu-wait [data-count-line]{stroke-dashoffset:1}
.cu-wait .bar-thin.grow>span{width:0!important}
.spark{width:60px;height:18px;display:block}
.spark polyline{fill:none;stroke:var(--fg2);stroke-width:1.5}
.lightbox{position:fixed;inset:0;background:rgba(9,9,11,.94);display:none;place-items:center;z-index:100}
.lightbox.open{display:grid}
.lightbox img{max-width:min(96vw,1400px);max-height:88vh;border-radius:8px}
.lightbox .x,.lightbox .nav{position:absolute;background:rgba(17,17,20,.8);border:1px solid var(--hair-strong);color:var(--fg);border-radius:999px;width:44px;height:44px;cursor:pointer;font-size:18px}
.lightbox .x{top:16px;right:16px}.lightbox .nav.l{left:16px;top:50%}.lightbox .nav.r{right:16px;top:50%}
.lightbox .count{position:absolute;bottom:16px;left:50%;transform:translateX(-50%);font-family:var(--mono);font-size:12px;color:var(--fg2)}
.signin-grid{display:grid;grid-template-columns:1fr 1fr;min-height:calc(100vh - 64px)}
.signin-art{position:relative;overflow:hidden;background:var(--map);border-right:1px solid var(--hair-faint)}
.signin-art .quote{position:absolute;left:48px;bottom:48px;right:48px;background:linear-gradient(transparent,rgba(9,9,11,.9));padding-top:80px}
.signin-art .quote p{font-size:26px;line-height:1.3;letter-spacing:-.02em;font-weight:500;margin:0 0 12px;max-width:440px;text-wrap:balance}
.signin-form{display:grid;place-items:center;padding:48px}
.signin-form .in{width:min(380px,100%);animation:rise .6s var(--ease) both}
.or{display:flex;align-items:center;gap:12px;margin:22px 0;color:var(--faint);font-size:12px}
.or span{flex:1;height:1px;background:var(--hair)}
.strength{display:flex;gap:4px;margin-top:8px}.strength span{flex:1;height:3px;border-radius:2px;background:rgba(255,255,255,.1)}.strength span.on{background:var(--ok)}
.sheet{position:fixed;inset:auto 0 0 0;background:rgba(9,9,11,.97);border-top:1px solid var(--hair);border-radius:16px 16px 0 0;padding:16px 16px 28px;display:none;z-index:50}
.sheet.open{display:block}
.sheet a{display:flex;align-items:center;gap:12px;padding:14px 8px;border-radius:12px;color:var(--fg);font-size:14px;min-height:44px}
.sheet a:hover{background:rgba(255,255,255,.06)}

/* ---- press feedback ----
 *
 * ⚠️ Every affordance above acknowledges the pointer with :hover, and a phone
 * has no hover. Greg, 2026-09-10: *"it doesn't even look like i've tapped, and
 * something will randomly load."* The server half of that is fixed
 * (core.views.review_open_count — the Review badge was running a geometry scan
 * on every page), but a navigation is never instant, and a tap that produces
 * no mark at all until the next page paints reads as a tap that missed.
 *
 * So: an :active state on everything that navigates or submits. It costs no
 * round trip and no script — it is painted by the browser while the request is
 * still in flight, which is exactly the window that was silent.
 *
 * `touch-action:manipulation` goes with it. It is not the old 300 ms
 * double-tap delay (the viewport meta in _base.html already retired that);
 * it stops a quick second tap on the same control being read as a zoom
 * gesture, which on a tab bar is a real thing to do by accident.
 *
 * ⚠️ **`-webkit-tap-highlight-color` is set to transparent ONLY where a real
 * :active rule replaces it.** Clearing it globally would take the one piece of
 * feedback iOS gives for free and hand back nothing.
 */
a,button,summary,.tbl tr.go{touch-action:manipulation}
a:active,button:active{-webkit-tap-highlight-color:transparent}

.rail a.item:active,.rail .logo:active,.rail .avatar:active{background:rgba(255,255,255,.14)}
.rail a.item.on:active{background:rgba(255,45,45,.22)}
.btn:active{border-color:rgba(255,255,255,.45);background:rgba(255,255,255,.07)}
.btn.primary:active{background:var(--red-deep);border-color:var(--red-deep)}
/* ⚠️ `.btn.light` and `.btn:active` are both one class plus one simple
 * selector, so they tie on specificity and the :active rule — written lower
 * in this file — wins the `background`. Without this line the pressed state
 * of the one solid-white button on the site (Sign in, Continue) is 7% white
 * on a near-black panel: the button does not look pressed, it looks gone,
 * which is a reason to press it again. Specificity picks a winner per
 * PROPERTY, so `.btn.light` keeps its own colour and only loses this one. */
.btn.light:active{background:#d4d4d8;border-color:#d4d4d8}
.btn:active,.srccard:active,.dropzone:active{transform:translateY(1px)}
.chips button:active,.modebar button:active,.modebar a:active{background:rgba(255,255,255,.14)}
.srccard:active,.dropzone:active{border-color:rgba(255,255,255,.35);background:rgba(255,255,255,.04)}
.photostrip a:active{transform:scale(.97)}
.back:active{color:var(--fg)}
/* The row is not a link, so iOS paints nothing for it at all. */
.tbl tr.go:active{background:rgba(255,255,255,.09)}

/* A press must not be swallowed by a transition that fades it in over 180 ms —
 * the point is that it lands on the first frame. */
.btn,.chips button,.modebar button,.modebar a,.srccard,.dropzone,.photostrip a{
  transition-duration:.18s}
.btn:active,.chips button:active,.modebar button:active,.modebar a:active,
.srccard:active,.dropzone:active,.photostrip a:active,.tbl tr.go:active{
  transition-duration:0s}

/* ---- responsive: one breakpoint ---- */
@media (max-width:720px){
  /* The folded key is ONE line on a phone: the spinner says "routing" alone. */
  .notice.fold .rt{display:none}
  .shell{grid-template-columns:minmax(0,1fr)}
  .rail{display:none}
  .tabbar{grid-column:1 / -1;display:flex;justify-content:space-around;align-items:stretch;background:rgba(9,9,11,.92);border-top:1px solid var(--hair);padding:6px 4px 18px;backdrop-filter:blur(14px)}
  .tabbar a{flex:1;display:flex;flex-direction:column;align-items:center;gap:4px;padding:6px 0;border-radius:10px;color:var(--fg2);position:relative;font-size:10px;font-weight:500;min-height:44px;justify-content:center}
  .tabbar a.on{color:var(--red)}
  .tabbar .badge{position:absolute;top:2px;left:calc(50% + 6px);background:var(--red);color:#fff;font-size:9px;font-weight:600;border-radius:999px;padding:1px 5px;font-family:var(--mono)}
  .page{padding:20px 16px 40px}
  .topline{padding:0 16px}.topline .search{display:none}
  .pubhead{padding:0 16px;gap:16px}.pubhead nav{display:none}.pubhead .brand .routemark{width:40px}
  .pubfoot{padding:24px 16px;flex-direction:column;gap:8px;align-items:flex-start}
  .pubwrap{padding:24px 16px 48px}
  .hero{padding:56px 20px 24px;min-height:0}
  .hero .cols{grid-template-columns:1fr}
  .phone-wrap{display:none}
  .features .in{grid-template-columns:1fr;padding:48px 16px}
  .pricing{padding:56px 20px 64px}.plans,.plans.two{grid-template-columns:1fr}
  .pricing h2{font-size:30px}
  .grid2,.grid3{grid-template-columns:1fr}.grid4{grid-template-columns:repeat(2,minmax(0,1fr))}
  .statnum{font-size:64px}
  .uniquecard{padding:20px 16px 14px}
  .uhead{gap:12px}
  .uyear{text-align:left}
  .streakcard{padding:20px 16px}
  .streak{grid-template-columns:minmax(0,1fr);gap:20px;align-items:start}
  .odo>span{font-size:44px;min-width:44px}
  /* ⚠️ BOTH TABLES FIT, AND THE CONTAINER CANNOT SCROLL SIDEWAYS.
   * Three separate causes, all fixed here. (1) `.fitm`'s 900px floor — that
   * alone pushed the name column off a ~286px canvas. (2) INTRINSIC content
   * width: even at min-width 0 these tables measured 404px, so the widths are
   * fixed percentages rather than negotiated. (3) Too many columns — nine in
   * 286px gives every cell 25px, which is worse than scrolling; the rest are
   * `hide-m`. Name cells wrap on ANY character because ride titles carry
   * unbreakable tokens ("Hayward — CAMBA Seeley·2026-08-17"). */
  .tscroll{overflow-x:hidden}
  .tbl.fitm{min-width:0;table-layout:fixed;width:100%}
  .tbl.fitm td,.tbl.fitm th{padding:9px 10px}
  .tbl.fitm td.name,.tbl.fitm th.name{white-space:normal;overflow-wrap:anywhere}
  .webtbl th:nth-child(1),.webtbl td:nth-child(1){width:9%}
  .webtbl th:nth-child(2),.webtbl td:nth-child(2){width:38%}
  .webtbl th:nth-child(3),.webtbl td:nth-child(3){width:26%}
  .webtbl th:nth-child(6),.webtbl td:nth-child(6){width:27%}
  .conntbl th:nth-child(1),.conntbl td:nth-child(1){width:30%}
  .conntbl th:nth-child(2),.conntbl td:nth-child(2){width:44%}
  .conntbl th:nth-child(5),.conntbl td:nth-child(5){width:26%}
  /* 2,914,600 overran its tile at 22px. */
  .tile.stat .v{font-size:19px}
  .detail{grid-template-columns:1fr}
  .tile .v{font-size:22px}
  .hide-m{display:none !important}
  .srcrow{grid-template-columns:minmax(0,1fr)}
  .srcrow .logo{min-height:72px}
  .signin-grid{grid-template-columns:1fr}.signin-art{display:none}
  .signin-form{padding:24px 16px}
  .mapbox{height:320px}
  /* The small maps' cluster: thumb targets, not pointer ones. */
  .mapbox .maplibregl-ctrl.ectl{margin:0 10px 10px 0;gap:9px}
  .mapbox .maplibregl-ctrl-bottom-left{right:64px}
  .mapbox .maplibregl-ctrl.key{margin:0 0 8px 10px}
  .ectl .ec-compass{width:44px;height:44px}
  .ectl .ec-compass svg{width:26px;height:26px}
  .ectl .ec-zoom{width:44px;border-radius:14px}
  .ectl .ec-zoom button{height:44px;font-size:20px}
  .ectl .ec-bmbtn{width:44px;height:44px;border-radius:14px}
  .ectl .ec-bmbtn svg{width:19px;height:19px}
  .ectl .ec-bmpop{width:216px}
  .ectl .ec-bmgrid button{min-height:44px}
  .mapbox.webmap{height:340px}
  .mapbox.spanmap{height:400px}
  /* A gap's route is long and thin like a span, and its directions follow. */
  .mapbox.gapmap{height:380px}
  .mapbox .mapstate{top:12px;font-size:12px}
  .gapacts{width:100%}
  .cue.end .d{display:none}
  /* Taller than the 320px above — this map is the page, and a bottom sheet
   * sits over its lower edge — but nowhere near the desktop 520px. */
  .mapbox.cxmap{height:400px}
  .mapbox.cropmap{height:340px}
  .cropends{gap:8px}
  .cropend .num.md{font-size:18px}
  .tbl th,.tbl td{padding:10px}
  /* z-index: the web card and the layers sheet both live in .ov, and without
   * it the compass, locate button and status chip — later siblings — paint
   * OVER the card. `.ov > *` keeps pointer events on the children only. */
  .ov{bottom:16px;z-index:2}
  /* ⚠️ No transform animation and no backdrop-filter on the sheet's
   * ancestors. Either one makes that ancestor the containing block for a
   * `position:fixed` descendant, so #mapctl — which lives INSIDE the pill —
   * pinned itself to the pill instead of the screen and drew half-open over
   * the map; the animation also trapped its z-index under the compass.
   * Measured in a browser 2026-09-13, not visible in the handoff's mock. */
  .ov-col{flex:1 1 auto;animation:none}
  /* One bar, full width: the three modes, then the Layers trigger. */
  .modebar{width:100%;flex-wrap:nowrap;padding:4px;gap:2px;backdrop-filter:none;background:rgba(17,17,20,.94)}
  .modebar [data-mode]{flex:1;min-width:0;padding:10px 0;font-size:13px}
  .modebar .sep{display:none}
  .modebar .layerbtn{display:flex;align-items:center;gap:6px;flex:0 0 auto;height:44px;padding:0 12px;border-radius:999px;border:1px solid var(--hair-strong);background:transparent;color:var(--fg);font-size:12.5px;font-weight:600;cursor:pointer}
  .modebar .layerbtn[aria-expanded="true"]{background:rgba(255,45,45,.16);border-color:rgba(255,45,45,.55);color:var(--red-link)}
  /* The sheet — same idiom as .cxpanel on a phone: fixed to the bottom edge,
     thumb-reachable, closed by Done, the scrim or Escape. */
  .mapctl{position:fixed;inset:auto 0 0 0;display:block;white-space:normal;background:rgba(9,9,11,.97);backdrop-filter:blur(14px);border-top:1px solid var(--hair);border-radius:16px 16px 0 0;padding:10px 16px calc(18px + env(safe-area-inset-bottom));z-index:60;max-height:76vh;overflow:auto;transform:translateY(105%);visibility:hidden;transition:transform .3s var(--ease),visibility 0s .3s}
  /* visibility, not just the transform: a closed sheet parked off-screen is
   * otherwise still in the tab order. */
  .mapctl.open{transform:none;visibility:visible;transition:transform .3s var(--ease)}
  .sheetgrab{display:block;width:34px;height:4px;border-radius:999px;background:rgba(255,255,255,.18);margin:0 auto 12px}
  .sheethead{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:14px;font-size:15px;font-weight:600}
  /* `.modebar button` would otherwise strip Done to bare text. */
  .modebar .sheethead .btn{border:1px solid var(--hair-strong);border-radius:8px;padding:6px 12px;min-height:36px;font-size:12px;font-weight:500;color:var(--fg)}
  .ctlgroup{display:block;margin-bottom:16px}
  .ctlgroup:last-child{margin-bottom:0}
  .ctlcap{display:block;font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);font-weight:600;margin-bottom:8px}
  .bmgrid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:7px}
  .bmgrid button{display:flex;flex-direction:column;gap:5px;min-height:44px;padding:6px;border-radius:10px;border:1px solid var(--hair);background:var(--surface);color:var(--fg2);font:inherit;text-align:left;cursor:pointer}
  .bmgrid .sw{height:26px;border-radius:6px}
  .bmgrid .nm{font-size:10.5px;font-weight:600}
  .bmgrid button[aria-pressed="true"]{border-color:rgba(255,45,45,.6);background:rgba(255,45,45,.12);color:var(--fg)}
  .mapctl .ctl{display:flex;align-items:center;justify-content:space-between;gap:10px;min-height:44px;margin-bottom:7px;padding:6px 8px 6px 12px;background:var(--surface);border:1px solid var(--hair);border-radius:10px;font-size:12.5px}
  .mapctl .ctl:last-child{margin-bottom:0}
  /* The select stays for a phone that never ran map.js; once the swatches
   * exist they replace it. Keyed on the grid being filled, so the no-JS
   * fallback is real — and three classes, because `.mapctl .ctl` above sets
   * display on the same element and would win a tie. */
  .bmgrid:empty{display:none}
  .mapctl .bmgrid:not(:empty) + .bmrow{display:none}
  .mapctl .ctl .cap{font-size:12.5px;letter-spacing:0;text-transform:none;font-weight:400;color:var(--fg2)}
  /* 16px or iOS zooms the whole map on focus. */
  .mapctl .ctl select{font-size:16px;font-weight:600;color:var(--fg);text-align:right}
  .mapctl .gapctl{font-weight:600}
  .mapctl .gapctl .hint{display:block;margin-top:2px;font-size:10.5px;font-weight:400;color:var(--muted)}
  .sheetscrim{display:block;position:fixed;inset:0;background:rgba(9,9,11,.55);z-index:55;opacity:0;pointer-events:none;transition:opacity .25s var(--ease)}
  .sheetscrim.on{opacity:1;pointer-events:auto}
  .legend{display:none}
  /* On a phone the pill owns the bottom row, so the credit sits a row up. */
  .mapwrap .maplibregl-ctrl-bottom-right{left:12px}
  .mapwrap .maplibregl-ctrl-bottom-right .ec-credit{margin:0 68px 30px 0}
  /* Small, but present: bottom-left above the tab bar, clear of the compass
     cluster (.brctl is right:16px), one line, ellipsis rather than wrap. */
  .status{left:12px;bottom:12px;padding:3px 7px;border-radius:7px;font-size:9px;max-width:calc(100% - 76px)}
  .status .dot{width:5px;height:5px;margin-right:5px}
  .status .wide{display:none}
  .status .narrow{display:inline}
  /* The rebuild notice shows on a phone too (Greg, 2026-09-14) — in the pill's
     phone slot at the pill's phone size, and still instead of it. */
  .rebuild.pos{left:12px;bottom:12px;gap:5px;padding:3px 7px;border-radius:7px;font-size:9px;max-width:calc(100% - 76px)}
  .rebuild.pos .dot{width:5px;height:5px}
  /* A 9px × is not a thumb target; the hit area grows, the pill does not. */
  .rebuild.pos .x{position:relative;font-size:11px;padding:0 0 0 2px}
  .rebuild.pos .x::after{content:"";position:absolute;inset:-14px -12px}
  /* The compass and locate button sit in the lower-right corner, on the
     status pill's row (Greg, 2026-09-15); the pill's max-width above already
     leaves them the 76px they need. The 86px lift was the handoff's. */
  .brctl{right:16px;bottom:12px;align-items:center;gap:9px}
  /* The zoom pair shows on a phone too (Greg, 2026-09-17) — it was hidden
     here, leaving Locate alone as a circle. Thumb targets, the embeds' own. */
  .compass{width:44px;height:44px}
  .compass svg{width:26px;height:26px}
  .zoomcluster{width:44px;border-radius:14px}
  .zoomcluster button{height:44px;font-size:20px}
  /* A bottom sheet, and it has to fit a 390px screen with the tab bar under
     it: tighter padding, 18px figures, a capped height that scrolls. */
  /* -16px undoes .ov's own 16px inset, so the sheet meets the map's edges. */
  .webcard{position:absolute;left:-16px;right:-16px;bottom:-16px;top:auto;width:auto;flex:none;border-radius:18px 18px 0 0;padding:12px 14px 18px;max-height:calc(100% - 56px);overflow:auto}
  .webcard h2{margin:3px 0 12px;font-size:18px}
  .webcard .stats{gap:8px 12px}
  .webcard .stats span.n{font-size:18px}
  .webcard .stats span.l{font-size:10.5px}
  .webcard p{margin:11px 0;padding-top:11px;font-size:11.5px;line-height:1.5}
  .webcard .acts .btn{padding:10px 8px;font-size:12.5px}
  /* A thumb's target, and the close button gets one too: it sits exactly
   * where a thumb reaching for the card's top edge lands. */
  .webcard .close{width:44px;height:44px;top:0;right:0}
  .actcard .eyebrow{padding-right:36px}
  .actcard .step{width:44px;height:44px}
  .actcard .pick{margin:10px 0}
  .readout{bottom:150px}
  /* Press feedback for the two controls that exist only on a phone. These
   * live INSIDE the one breakpoint deliberately — a second
   * `@media (max-width:720px)` block would split the mobile layout across
   * two places, which is the thing docs/V2-DESIGN.md rule 6 forbids. */
  .tabbar a:active{background:rgba(255,255,255,.12)}
  .tabbar a.on:active{background:rgba(255,45,45,.18)}
  .sheet a:active{background:rgba(255,255,255,.09)}
  /* Was its own `@media (max-width:720px)` one-liner beside the rule it
   * overrides. It is here now so the file has exactly ONE breakpoint —
   * which is what v2.tests.ResponsiveTests reads it as. */
  .addride .panel{position:static;width:auto;max-height:none;margin-top:8px}
  /* The connect editor's verdict panel. Under 720px the sidebar sits BELOW
   * the map, so "sticky beside the map" stops meaning anything — the panel
   * becomes a bottom sheet over the map instead, which is where a thumb
   * mid-drag can already see it. Same element and same ids; only the parking
   * changes. It lives in here rather than in its own breakpoint for the
   * reason docs/V2-DESIGN.md rule 6 gives and v2.tests.ResponsiveTests
   * enforces: this file has exactly one breakpoint. */
  .cxpanel{position:fixed;inset:auto 0 0 0;top:auto;border-radius:16px 16px 0 0;border-left:0;border-right:0;border-bottom:0;padding-bottom:calc(16px + env(safe-area-inset-bottom));background:rgba(9,9,11,.97);backdrop-filter:blur(14px);z-index:60}
}
