/* Meticulous — standalone styles */
:root{
  --navy:#1F3764; --orange:#F26522; --pale:#D9E1F3;
  --bg:#f4f6fb; --card:#ffffff; --ink:#1a2340; --muted:#6b7690;
  --green:#2ecc71; --yellow:#f1c40f; --red:#e74c3c;
  --radius:14px; --shadow:0 2px 10px rgba(31,55,100,.10);
}
*{box-sizing:border-box; -webkit-tap-highlight-color:transparent;}
html,body{margin:0;padding:0;}
/* v121 (owner, Oct 17 2026 — his Fuel-screen recording: "Why is
   this acting like a website? Why is the entire panel not
   straight?"): THE SIDEWAYS PAN, KILLED AT THE ROOT. The page
   carried no horizontal lock anywhere, so any element that sized
   even a few px past the phone viewport made the WHOLE panel
   draggable sideways, website-style, and it would sit crooked
   (same family as the v116 Revenue chips, which was fixed row by
   row but never locked globally). overflow-x:clip is the lock
   where supported — unlike hidden it creates no scroll
   container, so the sticky topbar / module heads keep sticking;
   hidden is the fallback line for older WebViews. Vertical
   scroll is untouched. Pinned by tools/overflow-lock-test.js. */
html,body{overflow-x:hidden;overflow-x:clip;}
#app{overflow-x:clip;}
body{
  font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Arial,sans-serif;
  background:var(--bg); color:var(--ink); min-height:100vh;
}
#app{max-width:480px;margin:0 auto;min-height:100vh;background:var(--bg);position:relative;
  box-shadow:0 0 24px rgba(31,55,100,.12);}
.view{display:none;min-height:100vh;}
.view.active{display:block;}

/* ---------- header ---------- */
.topbar{background:var(--navy);color:#fff;display:flex;align-items:center;gap:12px;
  padding:14px 16px;position:sticky;top:0;z-index:40;}
.topbar .logo{width:36px;height:36px;border-radius:10px;background:#fff;display:flex;
  align-items:center;justify-content:center;overflow:hidden;flex:none;}
.topbar .logo img{width:32px;height:32px;}
.topbar h1{font-size:18px;margin:0;flex:1;}
.iconbtn{background:transparent;border:0;color:#fff;font-size:22px;cursor:pointer;padding:8px;min-width:44px;min-height:44px;}

/* ---------- sidebar ---------- */
#sidebar{position:fixed;top:0;left:0;bottom:0;width:280px;max-width:84vw;background:#fff;z-index:100;
  transform:translateX(-105%);transition:transform .25s ease;box-shadow:4px 0 18px rgba(0,0,0,.18);
  display:flex;flex-direction:column;}
#sidebar.open{transform:translateX(0);}
#sidebar .who{background:var(--navy);color:#fff;padding:22px 18px;}
#sidebar .who .nm{font-weight:700;font-size:16px;}
#sidebar .who .em{font-size:12px;opacity:.75;margin-top:2px;}
#sidebar nav{flex:1;overflow-y:auto;padding:10px 0;}
#sidebar nav button{display:flex;width:100%;text-align:left;background:none;border:0;padding:15px 20px;
  font-size:15px;color:var(--ink);cursor:pointer;align-items:center;gap:14px;min-height:52px;}
#sidebar nav button:active{background:var(--pale);}
#scrim{position:fixed;inset:0;background:rgba(10,16,38,.45);z-index:90;display:none;}
#scrim.show{display:block;}

/* ---------- dashboard ---------- */
.pad{padding:20px;}
.trialbar{background:#fff8e1;border:1px solid #ffe082;color:#7a5c00;border-radius:var(--radius);
  padding:12px 16px;margin:16px 16px 0;font-size:14px;}
/* v296 (SYNC-FAILURE-WARNING-UNMISSABLE): the dashboard's red
   backup warning — unmissable on purpose: full-width red strip,
   bold, above the tracker card. Shown/hidden by js/cloudsync.js
   from the one sync truth; never dismissible by hand. */
.syncwarn{background:#fdecea;border:2px solid #c0392b;color:#922b21;border-radius:var(--radius);
  padding:12px 16px;margin:16px 16px 0;font-size:15px;font-weight:700;}
/* v357 (CONNECT-ALL DASHBOARD MIRROR): the whose-book picker on
   the dashboard and the persistent viewing banner. Amber, on
   purpose — a mirrored book must never be mistaken for your own
   (the Nov 7 zeros were exactly that confusion). */
.mirrorbar{background:#fff8e6;border:2px solid #b45309;border-radius:var(--radius);
  padding:12px 16px;margin:16px 16px 0;}
.mirrorbar .mr-pick{width:100%;margin-top:6px;font-size:15px;font-weight:700;padding:10px;
  border:1.5px solid #b45309;border-radius:10px;background:#fff;color:var(--navy);}
.mirrorbanner{display:flex;align-items:center;justify-content:space-between;gap:10px;
  background:#fff8e6;border-bottom:2px solid #b45309;color:#7c2d12;
  padding:10px 16px;font-size:14px;}
.mirrorbanner.hidden{display:none;}
.tracker{background:var(--navy);border-radius:var(--radius);margin:16px;padding:26px 18px 24px;
  text-align:center;color:#fff;box-shadow:var(--shadow);}
.tracker .clock{font-size:36px;font-weight:700;letter-spacing:1px;margin:8px 0 16px;font-variant-numeric:tabular-nums;}
.tracker .meta{font-size:13px;opacity:.8;margin-bottom:14px;}
/* v242 (WORK-PROFILES): the active profile chip — one quiet line on the tracker card */
.tracker .trkprofile{display:inline-block;font-size:12.5px;font-weight:700;letter-spacing:.02em;background:rgba(255,255,255,.16);border-radius:999px;padding:3px 12px;margin-bottom:10px;}
.tracker .miles{font-size:15px;font-weight:600;margin:0 0 12px;}
/* v62: wake-lock status — deliberately tiny; on screen only while the hold is real */
.tracker .wake{font-size:12px;opacity:.75;margin:-6px 0 10px;}
/* v41: dynamic tracker — sub-line under the hero, tappable weight unit, mode buttons */
.tracker .trksub{font-size:13px;opacity:.85;margin:-8px 0 12px;font-variant-numeric:tabular-nums;}
.tracker .trkUnit{font-size:22px;cursor:pointer;text-decoration:underline dotted;text-underline-offset:5px;}
.tracker .trkUnitLbl{font-size:22px;opacity:.75;}
.trkAddBtn{background:rgba(255,255,255,.14);border:1px solid rgba(255,255,255,.45);color:#fff;
  border-radius:22px;padding:9px 24px;font-size:15px;font-weight:700;cursor:pointer;margin-top:14px;}
.trkAddBtn:active{transform:scale(.97);}
.trkLoadCount{margin-top:14px;font-size:16px;font-weight:600;color:#fff;}
.trkLoadCount .trkLoadSrc{font-size:12px;font-weight:500;opacity:.75;}
.trkAddBtn.trkFixBtn{padding:6px 16px;font-size:13px;margin-top:8px;opacity:.85;}
/* v220 (LOADS-CORRECTION-BUTTONS): the correction pair — big,
   distinct, glove-tappable: +1 green, -1 red, side by side. */
.trkLoadBtns{display:flex;gap:12px;justify-content:center;margin-top:10px;}
.trkLoadBtns .trkAddBtn{margin-top:0;padding:14px 26px;font-size:17px;border-radius:26px;border:0;flex:0 1 auto;}
.trkLoadBtns .trkPlusBtn{background:var(--green,#2e9e4f);}
.trkLoadBtns .trkMinusBtn{background:var(--red,#e74c3c);}
/* v282 (LOADS-SITE-PEGS): the peg pair — as big and glove-tappable
   as the correction pair, in their own colours (pickup blue, dump
   purple — never the green/red of +1/-1), full-width side by side,
   with the peg state line (times only) underneath. */
.trkPegBtns{display:flex;gap:12px;justify-content:center;margin-top:10px;}
.trkPegBtns .trkAddBtn{margin-top:0;padding:14px 18px;font-size:16px;border-radius:26px;border:0;flex:1 1 0;}
.trkPegBtns .trkPegLoadBtn{background:#2470c2;}
.trkPegBtns .trkPegDumpBtn{background:#7d3c98;}
.trkPegState{margin-top:8px;font-size:13px;font-weight:500;color:#fff;opacity:.85;text-align:center;}
.tbtn{border:0;border-radius:50%;width:96px;height:96px;font-size:15px;font-weight:700;color:#fff;
  cursor:pointer;box-shadow:0 4px 14px rgba(0,0,0,.25);}
.tbtn.play{background:var(--green);}
.tbtn.pause{background:var(--yellow);color:#4a3c00;}
.tbtn.stop{background:var(--red);margin-left:14px;width:68px;height:68px;font-size:13px;}
.modgrid{display:grid;grid-template-columns:1fr 1fr;gap:16px;padding:6px 16px 100px;}
/* v74: park-place nudge card (js/parkplace.js) — sits under the tracker card; asks once, never acts */
.parkcard{background:#fff;border:2px solid var(--navy);border-radius:var(--radius);margin:0 16px 4px;padding:16px;box-shadow:var(--shadow);}
.parkcard .parkTxt{font-size:16px;font-weight:600;color:var(--navy);margin-bottom:12px;}
.parkcard .parkRow{display:flex;gap:10px;}
.parkcard .parkRow .btn{flex:1;}
.modcard{background:var(--card);border-radius:var(--radius);box-shadow:var(--shadow);padding:24px 14px 20px;
  text-align:center;cursor:pointer;border:1px solid #e8ecf6;position:relative;}
.modcard:active{transform:scale(.97);}
.modcard .ic{font-size:32px;margin-bottom:10px;}
.modcard .t{font-weight:700;font-size:14px;}
.modcard .s{font-size:11px;color:var(--muted);margin-top:6px;line-height:1.5;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.modcard.locked{opacity:.55;}
.modcard .lock{position:absolute;top:10px;right:12px;font-size:16px;}

/* ---------- module view ---------- */
.modhead{background:var(--navy);color:#fff;padding:14px 16px;display:flex;align-items:center;gap:12px;
  position:sticky;top:0;z-index:30;}
.modhead h2{margin:0;font-size:17px;flex:1;}
.card{background:var(--card);border-radius:var(--radius);box-shadow:var(--shadow);padding:20px;margin:16px 0;}
.card h3{margin:0 0 12px;font-size:15px;color:var(--navy);}
.row{display:flex;gap:12px;align-items:center;}
.row>*{flex:1;}
/* v124: the dispatch action row — three secondary actions at ONE
   height/radius/voice under the card's single primary. The .btn
   base margins are zeroed (they are what let row siblings sit at
   ragged heights) and each button centers its label so a wrapped
   two-line label and a one-line label land at the same height. */
.dsp-actions{gap:8px;margin-top:8px;align-items:stretch;}
.dsp-actions .btn{margin:0;min-height:48px;display:flex;align-items:center;justify-content:center;text-align:center;}
/* v121: flex/grid children may shrink BELOW their content's
   min-content size. Without this, a child's floor is its longest
   unbreakable content — when the floors in a row summed past the
   screen, the row sized the PAGE wide and the whole panel panned
   sideways (the mechanism behind the owner's Oct 17 recording,
   worst on big-text handsets). Measured on the v120 tree at
   360px: the dashboard module grid had a ~449px hard minimum
   (nowrap card subtitles), P&L's three stat cards summed to
   422px, Dispatch's start-time input forced 376px, Invoice's
   period-to date input 408px, a Revenue stat card crossed at
   370px. Visually identical whenever the content fits; when it
   doesn't, the child wraps/shrinks inside its own box instead
   of dragging the page. */
.row>*,.kv>*,.modgrid>*,.optgrid>*,.hero-btns>*{min-width:0;}
input,select,textarea{min-width:0;}
/* v116 (owner, Oct 16 2026): wrapping chip rows — the Revenue
   pay-period chips (six across) used to sit in a plain no-wrap
   .row and push "Custom" half off the right edge of a phone
   screen. A .rvchips row wraps instead: children share each row
   evenly (three per row at phone width), may shrink below their
   content size, and never cross the edge. The selected state is
   untouched — it lives in the .btn primary/ghost classes. */
.rvchips{flex-wrap:wrap;gap:8px;}
.rvchips>*{flex:1 1 28%;min-width:0;}
label.f{display:block;font-size:13px;color:var(--muted);margin:12px 0 6px;}
input,select,textarea{width:100%;padding:14px;border:1px solid #ccd4ea;border-radius:10px;
  font-size:16px;background:#fff;color:var(--ink);}
textarea{min-height:90px;resize:vertical;}
.btn{display:block;width:100%;padding:15px;border:0;border-radius:12px;font-size:16px;font-weight:700;
  cursor:pointer;margin:12px 0;min-height:52px;}
.btn.primary{background:var(--orange);color:#fff;}
.btn.navy{background:var(--navy);color:#fff;}
.btn.ghost{background:#eef1f8;color:var(--navy);}
.btn.small{padding:10px;font-size:14px;min-height:44px;}
.listitem{background:#fff;border:1px solid #e8ecf6;border-radius:12px;padding:16px;margin:12px 0;}
.listitem .a{font-weight:700;font-size:14px;}
.listitem .b{font-size:12px;color:var(--muted);margin-top:5px;line-height:1.5;}
/* v121: long unbroken strings (station names, addresses, pins)
   in list rows and key/value values wrap inside their own box —
   anywhere-wrapping only bites on a token that could otherwise
   force the row wide; normal spaced text wraps as it always did.
   Images can never exceed their container. */
.listitem .a,.listitem .b{overflow-wrap:anywhere;}
.kv b{overflow-wrap:anywhere;}
img{max-width:100%;}
.pill{display:inline-block;background:var(--pale);color:var(--navy);border-radius:20px;
  padding:4px 12px;font-size:12px;font-weight:600;}
.toggle{display:flex;justify-content:space-between;align-items:center;padding:14px 0;
  border-bottom:1px solid #eef1f8;}
.toggle:last-child{border-bottom:0;}
.switch{position:relative;width:46px;height:26px;flex:none;}
.switch input{opacity:0;width:100%;height:100%;position:absolute;margin:0;cursor:pointer;}
.knob{position:absolute;inset:0;background:#ccd4ea;border-radius:20px;transition:.2s;}
.knob:before{content:"";position:absolute;width:20px;height:20px;border-radius:50%;background:#fff;
  top:3px;left:3px;transition:.2s;box-shadow:0 1px 3px rgba(0,0,0,.3);}
.switch input:checked + .knob{background:var(--orange);}
.switch input:checked + .knob:before{left:23px;}
/* v207 (CONNECT-PICK): the Connect module tick row — ONE full-width
   tap target per module; the tick box sits in a single right-hand
   column on every row, so the eye reads one column of ticks. */
.cn-modrow{display:flex;align-items:center;justify-content:space-between;gap:12px;width:100%;text-align:left;margin-top:6px;padding:10px 12px;background:#fff;border:1.5px solid #ccd4e8;border-radius:12px;font-size:15px;color:var(--navy);cursor:pointer;}
.cn-modrow .cn-modbox{flex:none;width:24px;height:24px;border:2px solid #9aa7c7;border-radius:7px;display:flex;align-items:center;justify-content:center;font-size:15px;font-weight:800;color:#fff;background:#fff;}
.cn-modrow[aria-pressed="true"]{border-color:var(--navy);}
.cn-modrow[aria-pressed="true"] .cn-modbox{background:var(--orange);border-color:var(--orange);}
.empty{text-align:center;color:var(--muted);padding:26px 10px;font-size:14px;}

/* ---------- onboarding ---------- */
#onb{background:#fff;min-height:100vh;display:flex;flex-direction:column;}
#onb .hero{background:var(--navy);color:#fff;text-align:center;padding:44px 24px 32px;}
#onb .hero img{width:88px;height:88px;border-radius:22px;box-shadow:0 6px 20px rgba(0,0,0,.3);}
#onb .hero h2{margin:16px 0 8px;font-size:24px;}
#onb .hero p{margin:0;opacity:.8;font-size:15px;}
#onb .body{flex:1;padding:26px 24px;max-width:480px;width:100%;margin:0 auto;}
.prog{height:6px;background:#e8ecf6;border-radius:4px;overflow:hidden;margin-bottom:22px;}
.prog i{display:block;height:100%;background:var(--orange);transition:width .25s;}
.step{display:none;}
.step.on{display:block;animation:fade .2s;}
@keyframes fade{from{opacity:0;transform:translateY(8px);}to{opacity:1;}}
.step h3{font-size:20px;color:var(--navy);margin:0 0 8px;}
.step p.hint{color:var(--muted);font-size:14px;margin:0 0 16px;line-height:1.6;}
.optgrid{display:grid;grid-template-columns:1fr 1fr;gap:12px;}
.opt{border:2px solid #dfe4f2;border-radius:14px;padding:22px 12px;text-align:center;cursor:pointer;
  font-weight:600;font-size:14px;background:#fff;min-height:96px;}
.opt.sel{border-color:var(--orange);background:#fff5ef;color:var(--navy);}
.opt .e{font-size:28px;display:block;margin-bottom:8px;}
#onb .nav{display:flex;gap:12px;padding:16px 24px 30px;max-width:480px;width:100%;margin:0 auto;}
#onb .nav .btn{margin:0;}
.skipbtn{background:none;border:0;color:var(--muted);font-size:14px;margin:6px auto 0;display:block;cursor:pointer;padding:10px;}

/* ---------- auth ---------- */
#auth{background:var(--navy);min-height:100vh;
  display:flex;flex-direction:column;align-items:center;justify-content:center;color:#fff;padding:0;}
.auth-land{width:100%;max-width:480px;text-align:left;animation:fade .35s;min-height:100vh;display:flex;flex-direction:column;}
.hero-photo{position:relative;height:52vh;min-height:340px;overflow:hidden;}
.hero-photo img{width:100%;height:100%;object-fit:cover;object-position:center;}
.hero-photo::after{content:'';position:absolute;left:0;right:0;bottom:0;height:45%;
  background:linear-gradient(to bottom,transparent,#141f38);}
.hero-copy{background:#141f38;flex:1;padding:6px 28px 34px;}
.hero-copy h2{margin:0 0 12px;font-size:38px;font-weight:800;letter-spacing:.2px;line-height:1.12;color:#fff;}
.hero-copy p.tag{margin:0 0 30px;font-size:15px;line-height:1.6;color:#D9E1F3;}
.hero-btns{display:flex;gap:14px;margin-bottom:6px;}
.hero-btns .btn{flex:1;margin:0;}
.btn.big{padding:17px;font-size:17px;border-radius:14px;margin:8px 0;}
.btn.purple{background:#4b3aff;}
.btn.purple:active{background:#3d2fe0;}
.btn.outline{background:transparent;color:#fff;border:2px solid rgba(255,255,255,.55);}
.btn.outline:active{background:rgba(255,255,255,.12);}
.skipbtn.light{color:#D9E1F3;margin-top:16px;}
#auth .box{background:#fff;color:var(--ink);border-radius:20px;padding:28px 24px;width:100%;max-width:380px;
  box-shadow:0 8px 30px rgba(0,0,0,.25);animation:fade .25s;}
.backlink{background:none;border:0;color:var(--muted);font-size:14px;cursor:pointer;padding:4px 0;margin-bottom:8px;}
.auth-mini{width:56px;height:56px;border-radius:14px;display:block;margin:0 auto 10px;}
#auth .box h2{color:var(--navy);margin:0 0 4px;text-align:center;}
#auth p.sub{text-align:center;color:var(--muted);font-size:13px;margin:0 0 14px;}
#auth .err{background:#fdecea;color:#b3261e;border-radius:8px;padding:9px 12px;font-size:13px;margin:8px 0;display:none;}

/* ---------- floating feedback ---------- */
#fbFab{position:fixed;right:16px;bottom:18px;width:56px;height:56px;border-radius:50%;
  background:var(--orange);color:#fff;border:0;font-size:24px;z-index:80;cursor:pointer;
  box-shadow:0 4px 14px rgba(242,101,34,.5);}
#fbSheet{position:fixed;left:0;right:0;bottom:0;z-index:110;background:#fff;border-radius:18px 18px 0 0;
  padding:18px;max-width:480px;margin:0 auto;transform:translateY(105%);transition:transform .25s;
  box-shadow:0 -4px 24px rgba(0,0,0,.2);}
#fbSheet.open{transform:translateY(0);}

/* ---------- v145: pull-to-refresh (js/app.js PTR region) ----------
   The gesture belongs to the app: the browser's own pull-reload
   would throw away app state, so the top overscroll is owned here.
   The pill is the whole face of it — pull hint, refreshing, and the
   settled "✓ Updated" — pointer-transparent, above the sheets. */
body{overscroll-behavior-y:none;}
#ptrPill{position:fixed;top:calc(8px + env(safe-area-inset-top,0px));left:50%;
  transform:translate(-50%,-64px);z-index:1300;background:var(--navy);color:#fff;
  font-size:13.5px;font-weight:600;padding:8px 16px;border-radius:999px;
  box-shadow:0 4px 16px rgba(0,0,0,.28);transition:transform .22s ease;
  pointer-events:none;white-space:nowrap;}
#ptrPill.show{transform:translate(-50%,0);}

/* ---------- v93: the road hand (js/chatbot.js) ----------
   ONE round mic-first Ask button, bottom-right, stacked above
   the feedback FAB so neither covers the home tracker or a
   screen's primary action. The sheet follows the fbSheet pattern. */
#chatFab{position:fixed;right:16px;bottom:88px;width:60px;height:60px;border-radius:50%;
  background:var(--navy);color:#fff;border:0;font-size:26px;z-index:85;cursor:pointer;
  box-shadow:0 4px 14px rgba(31,55,100,.35);}
@media(min-width:520px){ #chatFab{right:calc(50% - 224px);} }
#chatScrim{position:fixed;inset:0;background:rgba(10,16,38,.45);z-index:118;display:none;}
#chatScrim.show{display:block;}
#chatSheet{position:fixed;left:0;right:0;bottom:0;z-index:120;background:#fff;border-radius:18px 18px 0 0;
  max-width:480px;margin:0 auto;transform:translateY(105%);transition:transform .25s;
  box-shadow:0 -4px 24px rgba(0,0,0,.2);display:flex;flex-direction:column;height:82vh;max-height:640px;}
#chatSheet.open{transform:translateY(0);}
.chat-head{display:flex;align-items:center;gap:8px;padding:12px 14px;border-bottom:1px solid #e6eaf4;}
.chat-title{flex:1;font-weight:800;color:var(--navy);font-size:17px;}
.chat-hbtn{background:transparent;border:0;font-size:20px;cursor:pointer;padding:8px;min-width:44px;min-height:44px;}
.chat-msgs{flex:1;overflow-y:auto;padding:12px 14px;display:flex;flex-direction:column;gap:10px;background:var(--bg);}
.chatcard{position:relative;max-width:88%;padding:10px 36px 10px 12px;border-radius:14px;background:#fff;
  box-shadow:var(--shadow);font-size:15px;line-height:1.45;white-space:normal;align-self:flex-start;}
.chatcard.me{align-self:flex-end;background:var(--navy);color:#fff;padding:10px 12px;}
.chat-tag{display:inline-block;font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;
  color:var(--orange);margin-bottom:4px;}
.chat-spk{position:absolute;top:6px;right:6px;background:transparent;border:0;font-size:16px;cursor:pointer;padding:4px;}
.chat-chips{display:flex;flex-wrap:wrap;gap:8px;padding:10px 14px 2px;background:var(--bg);}
.chat-chips:empty{display:none;}
.chat-chip{border:1.5px solid var(--navy);color:var(--navy);background:#fff;border-radius:999px;
  padding:8px 12px;font-size:13.5px;cursor:pointer;text-align:left;}
.chat-row{display:flex;gap:8px;padding:12px 14px;border-top:1px solid #e6eaf4;align-items:center;}
.chat-row input{font-size:16px;padding:12px;border:1.5px solid #ccd4e8;border-radius:12px;min-width:0;}
/* v96: the text field IS the row's primary element — the global
   .btn{width:100%} made the Ask button's flex base the whole row
   and crushed the field to a sliver (owner field test, Oct 16).
   The field takes every spare pixel; the send button is compact. */
.chat-row .chat-in{flex:1 1 auto;width:auto;}
.chat-row .btn{flex:none;width:auto;padding:12px 20px;}
.chat-mic{width:48px;height:48px;border-radius:50%;border:0;background:var(--orange);color:#fff;
  font-size:21px;cursor:pointer;flex:none;}
.chat-mic.listening{background:var(--red);}

/* ---------- subscription ---------- */
.tier{border:2px solid #dfe4f2;border-radius:14px;padding:16px;margin:12px 0;background:#fff;}
.tier.hot{border-color:var(--orange);box-shadow:0 4px 16px rgba(242,101,34,.18);}
.tier h3{margin:0;color:var(--navy);}
.tier .price{font-size:26px;font-weight:800;margin:6px 0;}
.tier .price small{font-size:13px;font-weight:400;color:var(--muted);}
.tier ul{margin:8px 0;padding-left:20px;font-size:14px;}
.tier li{margin:4px 0;}
.paywall{text-align:center;padding:40px 20px;}
.paywall .big{font-size:52px;}

/* ---------- misc ---------- */
.hidden{display:none!important;}
.center{text-align:center;}
.mt{margin-top:14px;}
.note{font-size:12px;color:var(--muted);}
.backbtn{background:none;border:0;color:#fff;font-size:20px;cursor:pointer;padding:8px;min-width:44px;min-height:44px;}
.kv{display:flex;justify-content:space-between;gap:12px;padding:10px 0;border-bottom:1px solid #eef1f8;font-size:14px;}
/* v344 (MULTI-DAY-MANIFEST-COLUMNS): the multi-day summary table —
   one straight right-aligned column per figure, a light rule
   under every row, the heading row in the house navy. */
.mn-sumtab{width:100%;border-collapse:collapse;font-size:14px;}
.mn-sumtab th,.mn-sumtab td{padding:7px 6px;border-bottom:1px solid #eef1f8;text-align:left;}
.mn-sumtab th{color:#1F3764;border-bottom:2px solid #1F3764;}
.mn-sumtab .r{text-align:right;font-variant-numeric:tabular-nums;}
.kv.stack{display:block;}
.kv.stack span{display:block;margin-bottom:2px;}
.kv.stack b{display:block;}
.kv:last-child{border:0;}
.kv b{color:var(--navy);}
canvas.chart{width:100%;height:170px;}
@media(min-width:520px){ #fbFab{right:calc(50% - 224px);} }

/* ---------- v28 tracker start-time adjust ("I started at 8:30 but opened the app at 11:20") ---------- */
.trkStartRow{margin:0 0 14px;font-size:13px;opacity:.85;display:flex;align-items:center;justify-content:center;gap:8px;}
.trkEdit{background:rgba(255,255,255,.15);border:1px solid rgba(255,255,255,.35);color:#fff;border-radius:20px;padding:4px 12px;font-size:12px;cursor:pointer;}
#trkStartSheet,#trkEndSheet,#odoSheet,#loadSheet,#ptiNudgeSheet{position:fixed;left:0;right:0;bottom:0;z-index:110;background:#fff;border-radius:18px 18px 0 0;
  padding:18px;max-width:480px;margin:0 auto;transform:translateY(105%);transition:transform .25s;
  box-shadow:0 -4px 24px rgba(0,0,0,.2);}
#trkStartSheet.open,#trkEndSheet.open,#odoSheet.open,#loadSheet.open,#ptiNudgeSheet.open{transform:translateY(0);}
#trkStartTime,#trkEndTime,#ldWeight,#ldUnit{width:100%;font-size:22px;padding:10px;border:2px solid #dfe4f2;border-radius:12px;margin:4px 0 12px;}
@media(min-width:520px){ #trkStartSheet,#trkEndSheet,#odoSheet,#loadSheet,#ptiNudgeSheet{right:calc(50% - 224px);left:calc(50% - 224px);} }
/* v174: the standing no-PTI flag on the tracker card */
.ptiflag{margin:6px auto 0;max-width:340px;padding:7px 10px;border:2px solid #b45309;background:#fdf6e9;color:#7c4a03;border-radius:10px;font-size:14px;font-weight:600;cursor:pointer;}
/* v256 (PTI-WARNING-BLOCKS-CONTENT): the wake rows (.tracker .wake) carry a
   -6px top margin so they snug up under the miles line. With the PTI flag
   visible between them, that same pull-up dragged the "Screen held" row up
   UNDER the flag, hiding behind it. The flag is in normal flow and owns its
   space: while it is shown, the wake rows below it take a plain 6px top
   margin and shift down instead of sliding underneath. Flag hidden = the
   rule does not match and the layout is exactly as before. */
.tracker .ptiflag:not(.hidden) ~ .wake{margin-top:6px;}

/* ---------- v27 cloud sync dot (topbar) ---------- */
/* v34: padding + background-clip gives a ~24px tap target around the 10px
   dot (tap = sync status popup, wired in app.js) without changing its look */
.syncdot{width:10px;height:10px;border-radius:50%;display:inline-block;margin-left:6px;vertical-align:middle;background:#9aa3b2;flex:0 0 auto;cursor:pointer;padding:7px;background-clip:content-box;}
.syncdot.sync-off{background:#9aa3b2;}
.syncdot.syncing{background:#f5a623;animation:syncpulse 1s ease-in-out infinite;}
.syncdot.synced{background:#2ecc71;}
.syncdot.syncerr{background:#e74c3c;}
@keyframes syncpulse{50%{opacity:.3;}}
/* v27: auto-start controls */
label.chk{display:flex;align-items:center;gap:8px;font-size:14px;margin:6px 0 2px;}
label.chk input{width:18px;height:18px;}
#pf_autodays{display:flex;flex-wrap:wrap;gap:6px;margin:2px 0 4px;}
.daypill{display:inline-flex;align-items:center;gap:4px;font-size:13px;border:1px solid var(--line,#dfe4ec);border-radius:999px;padding:4px 10px;cursor:pointer;}
.daypill input{width:15px;height:15px;margin:0;}

/* ---------- v77 Navigator (turn-by-turn) ---------- */
.nv-map{position:absolute;inset:0;height:auto;min-height:0;border-radius:0;overflow:hidden;background:#e8edf5;z-index:0;}
.nv-banner{background:#1F3764;color:#fff;border-radius:12px;padding:12px 14px;margin-bottom:10px;}
/* v101: full-screen Navigator shell (owner, Oct 16 — the map
   fills the screen; the destination doors and route actions sit
   in a bottom sheet over the shell, the banner and the mode row
   overlay the map itself). z-index 60 sits above the module page
   and its sticky bars, below the app drawer (100) and sheets
   (110+); the shell carries its own Back button to the dashboard. */
.nv-shell{position:fixed;inset:0;z-index:60;background:#eef1f6;display:flex;flex-direction:column;overflow:hidden;}
/* v348 (NAV-SETTINGS-BUTTON-RIGHT): the head's buttons sit
   flush to their edges — Back hard at the left, Settings hard
   at the right (its margin-left:auto does the pushing), the
   same 4px inset mirrored on both sides — and the title is
   centred on the bar itself, not left floating after Back. */
.nv-shell-head{position:relative;display:flex;align-items:center;gap:10px;background:#1F3764;color:#fff;padding:8px 4px;flex:0 0 auto;}
/* NAV-HEADER-WHITE-ON-WHITE-NOV7: the head's buttons are .btn —
   the global .btn{width:100%} is a 100% flex base, so in this
   flex row each pill swelled to half the bar and the centred
   title landed on the Back pill; and the v101 white-text
   override sat on the ghost pill's near-white #eef1f8 ground —
   white on white. Rescue, same shape as .chat-row/.nv-drive-bar:
   natural width, and the standard ghost pairing (navy text on
   the pale pill). */
.nv-shell-head .btn{flex:none;width:auto;color:var(--navy);}
.nv-shell-title{font-weight:700;font-size:17px;position:absolute;left:50%;transform:translateX(-50%);pointer-events:none;}
.nv-mapzone{position:relative;flex:1 1 auto;min-height:0;}
/* v348 (NAV-YOU-ARE-HERE-PEEK): ONE bottom stack for the map
   face — the banner card and the Truck/Car/Follow/Sound row
   ride in a single bottom-anchored flex column with a fixed
   8px gap, lifted by the safe-area inset so nothing sits in
   the gesture-bar zone. The banner is a plain in-flow card in
   the stack: it can never slide behind the row (the v343
   bottom:62px anchor let a grown row swallow its subtitle —
   the ghosting in the owner's screenshot), and the stack's
   height is whatever its children need, at any font scale. */
.nv-map-bottom{position:absolute;left:10px;right:10px;bottom:calc(10px + env(safe-area-inset-bottom,0px));z-index:5;display:flex;flex-direction:column;gap:8px;}
/* the banner stops 72px short of the right edge: the app's
   chat FAB (#chatFab, right:16px, 60px wide, z 85 — above this
   shell) floats over exactly that corner at banner height, and
   the card must never slide under it (16 + 60 = 76; the 72px
   margin + the stack's 10px right inset clear it by 6px). */
.nv-banner-overlay{position:static;margin:0 72px 0 0;box-shadow:0 2px 10px rgba(0,0,0,.3);}
/* the row is locked to ONE line (nowrap + shrinkable buttons):
   its height is a known constant, so the stack above the map
   is predictable at any width. */
.nv-maprow{position:static;background:rgba(255,255,255,.93);border-radius:10px;padding:6px;gap:6px;margin:0;flex-wrap:nowrap;}
.nv-maprow .btn{min-width:0;white-space:nowrap;padding-left:4px;padding-right:4px;}
/* v343 (NAV-MAP-CORNERS-LAYOUT): the map view's corner tucks —
   the search bar + Work/Personal pills stack at the map's TOP
   edge, the saved/recent places drop as a panel under that
   stack, and the banner (moved above) hugs the BOTTOM edge
   over the mode row. The map's centre stays clear. */
.nv-map-top{position:absolute;top:10px;left:10px;right:10px;z-index:6;display:flex;flex-direction:column;gap:6px;}
.nv-mapsearch{display:flex;gap:6px;background:rgba(255,255,255,.97);border-radius:10px;padding:6px;box-shadow:0 2px 10px rgba(0,0,0,.3);}
.nv-mapsearch input{border:0;background:transparent;min-width:0;}
/* NAV-SEARCH-BAR-SHOWS-GO-NOV7: same .btn{width:100%} flex-base
   poison — the Go button's base was the whole bar while the
   input (flex:1, base 0) resolved to 0px wide, so the bar
   showed only a full-width Go and no text box at all. Natural
   width for the button; the input's flex:1 takes the rest. */
.nv-mapsearch .btn{flex:none;width:auto;}
.nv-kindrow{display:flex;gap:6px;align-items:center;}
.nv-kindrow .btn{background:rgba(255,255,255,.93);box-shadow:0 2px 8px rgba(0,0,0,.25);}
.nv-kindrow .btn.navy{background:#1F3764;color:#fff;}
.nv-places-panel{position:absolute;top:108px;left:10px;right:10px;z-index:7;max-height:42%;overflow-y:auto;background:rgba(255,255,255,.98);border-radius:12px;padding:8px;box-shadow:0 4px 18px rgba(0,0,0,.35);}
/* v348 (NAV-PLACES-PANEL-NO-EXIT): the panel's head — its
   title at the left, its ✕ Close at the right. */
.nv-places-head{display:flex;align-items:center;justify-content:space-between;font-weight:700;margin-bottom:4px;}
.nv-places-head .btn{width:auto;}
/* v348 (NAV-YOU-ARE-HERE-PEEK): the sheet takes its content
   height and is never flex-squashed (flex:0 0 auto; the
   mapzone above it has min-height:0 and absorbs all the
   shrink), and its bottom padding carries the safe-area
   inset so the collapsed handle — the "Destination, places &
   directions" strip — sits fully inside the screen, never
   clipped by the gesture/nav-bar zone. */
.nv-sheet{flex:0 0 auto;max-height:47vh;overflow-y:auto;padding:10px 10px calc(10px + env(safe-area-inset-bottom,0px));background:#eef1f6;}
/* v330 (NAV-MAP-FIRST): the collapsed sheet hides its body — the
   map owns the opening screen; the handle stays as the one row. */
.nv-sheet-collapsed #nv_sheet_body{display:none;}
.nv-banner-main{font-size:19px;font-weight:700;line-height:1.25;}
.nv-banner-sub{font-size:14px;color:#D9E1F3;margin-top:3px;min-height:18px;}
/* v128: when the places detail expands inside the results list
   (directly under the tapped row) it reads as an expansion of
   that row — indented, accent edge — not as another list item.
   The home slot keeps the element's inline margin. */
#nv_results #nv_detail{margin:2px 0 10px 10px;border-left:3px solid #f26522;padding-left:10px;}

/* ---------- v101 Navigator drive screen (3D heading-up) ----------
   Full-screen fixed overlay. The 3D effect is pure CSS wrapped
   around the Leaflet map.
   v139 REWRITE (see .nv-drive-plane for the full note): the
   v101–v135 stack — perspective parent + tilt layer + spinning
   will-change layer + a 340% map box — is gone. There is ONE
   transformed element now: a 190% plane centred on the driver
   anchor (50% / 64%), carrying the map; the drive tick writes
   its one composed transform (perspective + pitch + heading)
   only when the heading really turns. The you-marker is an
   overlay OUTSIDE the plane, margin-centred on the anchor,
   always pointing up — heading-up means the world moves, not
   the driver. All chrome lives untransformed in the root. */
.nv-drive{position:fixed;inset:0;z-index:2000;background:#0d1526;}
/* v124 (owner, Oct 17 08:09 — the Drive screen "looks like an
   afterthought", thick bezels around a boxed map window): the
   map is now FULL-BLEED — the viewport covers the whole screen
   edge to edge, and the chrome (maneuver banner, ETA bar) FLOATS
   over it as cards, the purpose-built-navigator pattern. Before,
   banner and bar were solid blocks in a flex column with the map
   squeezed in a frame between them. Only Drive changes; the
   planning screen keeps its own shell. */
/* v139 (owner, Oct 17 12:48 MDT — "This image breaking is
   getting worst on every build! fix it"): the v133 promotion
   theory is RETIRED. On the owner's glass the promoted banner
   and bar were composited displaced by the same vector,
   clipped, or dropped outright while the 340% plane beneath
   them shed tiles — the chrome shared the starved compositor
   with the monster plane, promotion and all. The v139 chrome
   carries NO transform, NO will-change, NO transition: plain
   absolutely-positioned boxes in the .nv-drive root context,
   painted where they are laid out. (The full diagnosis + the
   new plane architecture are at .nv-drive-plane below and in
   BUGS.md.) */
.nv-drive-top{position:absolute;top:calc(10px + env(safe-area-inset-top));left:10px;right:10px;background:#1F3764;color:#fff;padding:10px 12px;border-radius:16px;z-index:3;box-shadow:0 4px 18px rgba(0,0,0,.45);opacity:1;max-height:150px;overflow:hidden;}
/* v280 (NAV-INSTRUCTIONS-COVER-MAP, owner Oct 29, 2026 — "display
   is covered with instruction text, no visible map"): the drive
   chrome now carries a hard BUDGET at his 411x960 viewport.
   Banner: max-height 150px (36px distance + at most 2 instruction
   lines + the road line + padding ≈ 128px, the cap is the law
   when text runs long — the instruction clamps, the map does not
   shrink). Lanes strip: docked at top 146px, ~46px tall. Bottom:
   the drive bar only (~86px). Top + bottom chrome ≈ 282px of
   960 — the map keeps ~70% of the glass, always. */
.nv-drive-dist{font-size:36px;font-weight:800;line-height:1;}
.nv-drive-inst{font-size:19px;font-weight:700;line-height:1.25;margin-top:4px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;}
.nv-drive-road{font-size:15px;color:#D9E1F3;margin-top:4px;min-height:20px;}
/* v107 (Stage 7): the dead gray band is dead. At 210% the tilted
   plane's far edge projected ~a third short of the viewport top
   on tall phones, exposing the flat viewport background as a gray
   slab. The plane is now 300% and the perspective deeper
   (1000px), which pushes the projected far edge past the top;
   and the viewport background is a SKY gradient (deep navy at
   the top into horizon blue), so any residual sliver above the
   horizon reads as sky, not as a broken map. */
/* v139: THE PLANE — the whole 3D drive view is now ONE element.
   What it replaces (v101–v135): a perspective parent holding a
   static rotateX tilt layer, holding a will-change spin layer
   (with a .45s transition re-rasterizing through every turn),
   holding a 340%×340% map box — three nested 3D contexts and a
   raster of ≈340%² ≈ 11.6 viewport areas (≈24M device px at
   phone DPR), re-projected on every heading write. The owner's
   frames show that compositor shedding tiles (grey wedges,
   route line in stubs) and misplacing the small promoted
   chrome layers around it (banner/bar displaced by one vector,
   clipped, or gone). The new plane: 190% × 190% of the
   viewport (≈3.2× less raster), centred on the driver anchor —
   left/top place its centre exactly at (50%, 64%) — and ONE
   composed transform written by the drive tick only on a ≥2°
   heading change: perspective(1100px) rotateX(45deg)
   rotate(−heading), transform-origin at the plane's centre
   (= the anchor). No will-change, no transition, no
   backface-visibility, no perspective PROPERTY anywhere: the
   viewport is a plain overflow-hidden box again. Tilt is 45°
   (was 52°): at bounded size the steeper pitch's far reach
   outruns the plane on tall phones — the bottom of the
   standard 45–60° navigation band, traded openly for a stable
   image (see BUGS.md). Coverage, portrait 360×640: the
   above-anchor reach the tilt needs is 0.64·640/cos45° ≈ 580px
   ≤ the plane's 608px half-height, and the perspective term
   foreshortens the far field further, so the bound is
   conservative; the sky gradient above remains the backstop
   for any residual sliver, exactly its v107 job. */
.nv-drive-viewport{position:absolute;inset:0;overflow:hidden;background:linear-gradient(180deg,#0b1424 0%,#274b7a 40%,#9db8d9 58%,#e8edf5 64%);}
/* v220 (NAV-CUTOFF-REPEAT): the plane is TALLER than it is wide —
   230% x 300%, still centred on the anchor (left:-65% = 50 - 230/2,
   top:-86% = 64 - 300/2). At the 45° pitch the far field is the
   first thing the projection starves: at 230% tall the plane's top
   edge projected barely past the screen top and fell short at the
   corners on diagonal headings — the owner's repeated "the top of
   the map is cut off". The extra height is all far-field: the map
   now reaches the top of the glass at every heading. Width is
   unchanged (side coverage was never the complaint) and the
   raster grows ~1.3x, bounded and deliberate.
   v260 (NAV-TOP-CUTOFF, owner, Oct 26, 2026 07:41 MDT):
   the v220 coverage math was done for a 360x640 screen at
   45°. On the owner's real drive screen (768x1024) at the
   v259 50° posture, a homography of the exact composition
   shows the far edge projecting only ~477px above the
   anchor against ~655px needed — the top ~15% uncovered at
   EVERY heading (his "wide dark band" under the banner).
   Two moves fix it: the transform gains a translateY
   compensation (planeTransform in navigator.js — lift =
   0.49 x viewport height, follow camera compensated to
   match), and the plane widens 230% -> 260% (left -80% =
   50 - 260/2, still centred on the anchor) for the raster
   the wider reach samples. Height/top are unchanged —
   300%/-86% already centre the tall axis on the anchor.
   v273 (NAV-TOP-CUTOFF-STILL): the flat 0.49 lift is
   retired — it was tuned at 768x1024 and runs out on the
   owner's real 411x960 screen (and in the corners at
   768x1024 too). The lift is now DERIVED from the live
   viewport by NAVPURE.coverageLiftPx, whose constants
   (260%x300%, anchor 50%/64%, perspective 1100, pitch 50)
   are a matched pair with THIS rule — change them
   together.
   v277 (NAV-SCREEN-STILL-CUTTING, owner, Oct 28, 2026
   07:27 MDT): width WAS the complaint after all — the
   v273 derivation only ever sampled the viewport's TOP
   STRIP, and at 411x960 the 260% width (half 534px)
   cannot hold the full screen at diagonal/side headings:
   the pre-image of the lower two-thirds walks off the
   plane's sides (63% of the glass uncovered at heading
   90 — his "thin map strip at the top, truck floating on
   the pale gradient"). The plane widens 260% -> 460%
   (left -180% = 50 - 460/2, still centred on the anchor;
   half-width 945px at 411 wide against the ~896px the
   full-viewport homography demands there) and
   coverageLiftPx now derives against the WHOLE viewport.
   Height/top are unchanged — 300%/-86% already centre
   the tall axis on the anchor and clear every class. */
.nv-drive-plane{position:absolute;width:460%;height:300%;left:-180%;top:-86%;transform-origin:50% 50%;}
/* v220 (PIP-REDESIGN): the drive screen's picture-in-picture
   posture — NOT a miniature of the big map: the top directions
   bar, the turn inset, the ETA bar and every control go away;
   the position marker becomes the pointed arrow (JS swap); the
   next direction rides in small words at the bottom. */
/* v231 (PIP-FACE-DESIGN-V41): the PiP bubble face is the
   owner's BUBBLE CLUSTER, not a card and not a rectangle: a BIG
   bubble top-left (the tracker's own toggle — GREEN while
   working, YELLOW while paused), a SMALLER RED stop bubble at
   the big bubble's right-bottom, and one WIDE PILL BAR under
   both carrying the running timer and the load tracker (count
   with its big −1 RED / +1 GREEN correction pair). The spaces
   BETWEEN the shapes are SEE-THROUGH (owner, Oct 23 10:54:
   "all the space between them are see through! Not on a
   card."): the container and the wrappers paint NOTHING and
   let taps fall through the gaps; only the bubbles and the bar
   are solid. Same cluster in both postures: full (system PiP
   on shells without their own drawn face) and docked (Back on
   the map page, floated over the dashboard). */
#bubbleFace{position:fixed;inset:0;z-index:95;color:#fff;background:transparent;
  pointer-events:none;padding:14px;}
#bubbleFace.hidden{display:none;}
.bub-cluster{pointer-events:none;width:100%;}
.bub-bubbles{position:relative;height:170px;pointer-events:none;}
.bub-main{position:absolute;left:0;top:0;width:148px;height:148px;border-radius:50%;
  border:none;font-size:56px;color:#fff;cursor:pointer;pointer-events:auto;
  display:flex;align-items:center;justify-content:center;
  box-shadow:0 10px 28px rgba(0,0,0,.45);}
.bub-main.bub-go{background:var(--green);}
.bub-main.bub-pause{background:var(--yellow);color:#1A1A1A;}
.bub-stopb{position:absolute;left:112px;top:84px;width:88px;height:88px;border-radius:50%;
  border:none;background:var(--red);font-size:32px;color:#fff;cursor:pointer;pointer-events:auto;
  display:flex;align-items:center;justify-content:center;
  box-shadow:0 8px 22px rgba(0,0,0,.45);}
.bub-bar{margin-top:10px;background:#0C1424;border-radius:999px;pointer-events:auto;
  box-shadow:0 10px 30px rgba(0,0,0,.5);padding:10px 18px;
  display:flex;align-items:center;flex-wrap:wrap;gap:2px 14px;}
.bub-clock{font-size:32px;font-weight:800;line-height:1.1;font-variant-numeric:tabular-nums;color:#fff;}
.bub-loadbox{display:flex;align-items:center;gap:10px;margin-left:auto;}
.bub-loads{font-size:18px;font-weight:700;color:#fff;white-space:nowrap;}
.bub-cnt{width:48px;height:48px;border-radius:50%;border:none;color:#fff;
  font-size:19px;font-weight:800;line-height:1;cursor:pointer;flex:none;
  display:flex;align-items:center;justify-content:center;}
#bubPlus{background:var(--green);}
#bubMinus{background:var(--red);}
/* v273: same law as the PiP line — the docked face's direction
   line wraps whole words instead of ellipsizing the street
   name away ("…toward Deerfoot Tr…", the owner's Oct 26
   frame). The bar already wraps its rows; the line now wraps
   inside its own row too. */
.bub-line{flex:1 0 100%;font-size:12.5px;font-weight:600;color:#C7D0E0;letter-spacing:.02em;
  white-space:normal;}
.bub-line:empty{display:none;}
/* v231: the DOCKED posture — the same cluster, compact, floated
   over the MAIN DASHBOARD (Back on the map page). Still no card:
   the container paints nothing; the cluster hugs the left, the
   bar spans the float's width. */
#bubbleFace.bub-dock{inset:auto;left:12px;right:12px;
  bottom:calc(86px + env(safe-area-inset-bottom));z-index:2901;padding:0;}
#bubbleFace.bub-dock .bub-bubbles{height:106px;}
#bubbleFace.bub-dock .bub-main{width:94px;height:94px;font-size:36px;}
#bubbleFace.bub-dock .bub-stopb{left:76px;top:50px;width:56px;height:56px;font-size:21px;}
#bubbleFace.bub-dock .bub-bar{margin-top:8px;padding:8px 14px;}
#bubbleFace.bub-dock .bub-clock{font-size:27px;}
#bubbleFace.bub-dock .bub-loads{font-size:16px;}
#bubbleFace.bub-dock .bub-cnt{width:44px;height:44px;font-size:17px;}
#bubbleFace.bub-dock .bub-line{font-size:12px;}
/* v273 (NAV-CARD-NO-USEFUL-INFO — owner verdict, Oct 26, 2026
   17:46 MDT): the line WRAPS now. It was nowrap + ellipsis, so
   at PiP width the street name was cut mid-word by design
   ("130 m · Turn right onto Herita…") — the one thing the
   small window exists to say, unreadable. Owner law: the full
   street name, never truncated mid-word; the line takes a
   second line (radius drops to a rounded bar when it does)
   and stays centred. */
.nv-drive-pip-line{position:absolute;left:50%;transform:translateX(-50%);bottom:12px;z-index:7;
  background:rgba(10,18,32,.85);color:#fff;font-size:13px;font-weight:600;line-height:1.25;
  padding:6px 12px;border-radius:18px;max-width:94%;white-space:normal;text-align:center;}
.nv-drive.nv-pip .nv-drive-top,.nv-drive.nv-pip .nv-drive-inset,.nv-drive.nv-pip .nv-drive-bar,
.nv-drive.nv-pip .nv-avatar-picker,.nv-drive.nv-pip .nv-drive-reroute,.nv-drive.nv-pip .nv-drive-speed,
.nv-drive.nv-pip .nv-layers-btn,.nv-drive.nv-pip .nv-layers-panel,
.nv-drive.nv-pip #nvd_recenter,.nv-drive.nv-pip .nv-drive-traffic,
.nv-drive.nv-pip .nv-drive-offer{display:none !important;}
/* v222 (PIP-LANDS-HOME): the IN-APP screen-in-screen. The dock is
   a fixed, click-through shell layer above the dashboard; the
   parked drive screen inside it shrinks to a floating window
   (bottom-right, above the dashboard's own bars) wearing the
   compact PiP face. A tap on the window reopens the Navigator
   full-screen (modules/navigator.js parkDrive). */
#pipDock{position:fixed;inset:0;z-index:2900;pointer-events:none;}
#pipDock:empty{display:none;}
/* v227: the parked drive screen SURVIVES in the dock hidden —
   it is not the floated piece any more (the tracker face is);
   display:none keeps the live drive's state and tick intact
   for the tap-back restore without showing a map miniature. */
.nv-drive.nv-parked{display:none !important;}
.nv-drive.nv-mini{inset:auto;right:12px;bottom:calc(86px + env(safe-area-inset-bottom));
  width:min(47vw,340px);height:auto;aspect-ratio:4/3;border-radius:16px;overflow:hidden;
  box-shadow:0 10px 34px rgba(0,0,0,.55);border:1px solid rgba(255,255,255,.22);
  pointer-events:auto;cursor:pointer;}
/* v292 (PIP-MAP-COVERED-BY-DIRECTIONS; owner Oct 31, 2026 screen
   recording): in the MINI the pip line was the base wrapping pill —
   centered, 94% wide, the 47-char turn line wrapping ~4 deep at
   his font scale, i.e. a black card covering most of a ~145px-tall
   map. His PiP law: the map is never buried by text. In the mini
   ONLY, the line becomes a slim single-line caption strip pinned
   to the bottom edge (~17% of the mini's height); the map stays
   clearly visible and the full wording is one tap away (the tap
   reopens full navigation; the tray notification also carries
   it). The SYSTEM PiP posture keeps the v273 wrapping pill
   untouched — its window is system-sized and that law stands
   there. */
.nv-drive.nv-mini .nv-drive-pip-line{left:0;right:0;bottom:0;transform:none;
  max-width:none;border-radius:0;font-size:11.5px;line-height:1.3;
  padding:5px 10px;white-space:nowrap;overflow:hidden;
  text-overflow:ellipsis;text-align:center;}
.nv-drive-map{width:100%;height:100%;background:#e9e2d0;} /* v260: warm sand under the vibrant palette while tiles load (was pale blue-grey #e8edf5) */
/* v135: THE AVATAR IS A TRUCK (owner, Oct 17 12:05 MDT: the
   30px ▲ "arrow as you" was "something puny" — "the moving
   avatar should be something bigger, a truck which you should
   be able to click and change"). The anchor keeps the v101
   numbers (the spin origin projects the truck's position at
   50%/64% of the viewport) but the marker is now a ~52×82px
   top-down truck glyph CENTRED on that point, with a white-halo
   drop shadow for contrast on any tile. It is TAPPABLE
   (pointer-events:auto) — the tap opens the chooser below; the
   drive tick never writes this node (v133 stands: the glyph is
   swapped on the tap, not the tick). No transition, no
   animation, no opacity rule — the marker holds steady-state
   visibility exactly as painted (v135 fading audit). */
/* v139: the marker moved OUT of the perspective viewport (in
   the v135 tree it sat inside it, a transformed, drop-shadow-
   filtered child of the 3D context) and lost both crutches: it
   is centred on the anchor by NEGATIVE MARGINS (its box size is
   set per avatar by navigator.js — AVATAR_BOX), and the soft
   ground shadow the drop-shadow filter used to fake is PAINTED
   INTO the SVG art itself. No transform, no filter: the marker
   is a plain box in the root context and paints where it sits. */
.nv-drive-you{position:absolute;left:50%;top:64%;z-index:5;pointer-events:auto;cursor:pointer;-webkit-tap-highlight-color:transparent;display:flex;align-items:center;justify-content:center;}
.nv-drive-you svg{width:100%;height:100%;display:block;}
/* v141: the vehicle markers are rendered PNG assets (the arrow
   stays SVG, above): the <img> fills the AVATAR_BOX box the same
   way the glyph did — plain sizing only, no transform, no filter;
   the ground shadow is baked into the raster itself.
   v175: the renders are rear-45° chase views now (see
   AVATAR_BOX in navigator.js) — this rule is untouched: the
   marker's layer contract did not move with the art. */
.nv-drive-you img{width:76%;height:76%;display:block;}
/* v280 (NAV-DEMO-TRUCK-ICON-OVERSIZED): the glyph renders at 76%
   inside its AVATAR_BOX — vehicle-sized against the road widths
   instead of covering the junction markings. The BOX (the anchor
   contract, centred by negative margins) is untouched; the flex
   centring above keeps the smaller glyph on the same centre. */
/* v135: the avatar chooser — a compact navy dock floating just
   above the drive bar, centred; a tap on an option applies and
   persists the choice and closes it (wiring in navigator.js).
   Hidden by the shared .hidden class until the avatar is tapped. */
/* v139: centred without a transform — left/right 0 + auto
   margins + max-content width (the translateX(-50%) it used
   to carry put the picker in the promoted-layer failure class
   with everything else). */
.nv-avatar-picker{position:absolute;left:0;right:0;margin-left:auto;margin-right:auto;width:max-content;bottom:calc(106px + env(safe-area-inset-bottom));background:#1F3764;border-radius:16px;padding:10px;display:flex;gap:8px;z-index:6;box-shadow:0 6px 20px rgba(0,0,0,.45);}
.nv-avatar-opt{background:rgba(255,255,255,.08);border:2px solid transparent;border-radius:12px;color:#fff;padding:6px 8px 5px;font:inherit;font-size:11px;line-height:1.2;display:flex;flex-direction:column;align-items:center;gap:4px;cursor:pointer;}
.nv-avatar-opt svg{width:24px;height:36px;}
.nv-avatar-opt img{width:36px;height:44px;object-fit:contain;display:block;}
.nv-avatar-opt.on{border-color:#f5a623;background:rgba(255,255,255,.16);}
.nv-drive-reroute{position:absolute;left:0;right:0;margin-left:auto;margin-right:auto;width:max-content;bottom:calc(96px + env(safe-area-inset-bottom));background:#b3540a;color:#fff;font-weight:700;padding:8px 16px;border-radius:999px;z-index:6;box-shadow:0 2px 8px rgba(0,0,0,.35);white-space:nowrap;}
/* v262: the traffic-ahead chip — same pill family as the
   reroute line, alert red, docked just above it; and the
   incident marker badge (Leaflet's divIcon shell itself is
   neutralised by .nv-traffic-pin-wrap). */
.nv-drive-traffic{position:absolute;left:0;right:0;margin-left:auto;margin-right:auto;width:max-content;max-width:92%;bottom:calc(140px + env(safe-area-inset-bottom));background:#b71c1c;color:#fff;font-weight:700;padding:8px 16px;border-radius:999px;z-index:6;box-shadow:0 2px 8px rgba(0,0,0,.35);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.nv-traffic-pin-wrap{background:transparent;border:none;}
.nv-traffic-pin{width:30px;height:30px;border-radius:50%;background:#ffb300;border:2px solid #fff;box-shadow:0 1px 5px rgba(0,0,0,.45);color:#4e2600;font-size:17px;line-height:26px;text-align:center;font-weight:900;}
.nv-drive-bar{position:absolute;left:10px;right:10px;bottom:calc(10px + env(safe-area-inset-bottom));background:#1F3764;color:#fff;padding:12px 14px;border-radius:16px;display:flex;align-items:center;gap:8px;z-index:3;box-shadow:0 4px 18px rgba(0,0,0,.45);opacity:1;}
/* v124 (owner, Oct 17 07:17 — "What's the arrival time?", asked
   over a Drive screen showing NO arrival info at all): the v107
   row below claimed the buttons "keep their natural width", but
   the global .btn rule is width:100% and flex:none takes flex-basis
   from that width — so EACH button claimed the whole bar, the ETA
   (flex:1, min-width:0) was crushed to a measured 0 px wide, and
   End drive sat entirely off-screen. The bar now scopes its
   buttons to natural width with the .btn margins zeroed, and the
   ETA is a two-line stack that cannot be crushed: the arrival
   CLOCK time on top (bold, never wraps), the remaining
   minutes + distance under it (ellipsized if long). */
/* v125 (owner's Oct 17 recording + landscape shot): three
   more bar defects measured in a real engine on the v124 tree.
   (1) The ETA column sized by content lost the flex fight to
   the buttons' max-content, landing at ~105 px in portrait —
   the sub-line ellipsized to "Arrive · 4 min · …", so the
   arrival answer was technically rendered and practically
   unreadable. The column now takes ALL leftover space
   (flex-basis 0) and the sub-line WRAPS to two lines instead
   of truncating. (2) The Sound button shrinks a step (13 px,
   tighter padding) so the column gets the room. (3) While
   Drive is open the shell chat FAB is hidden (rule below) —
   it sat on End drive in portrait and on Sound in landscape. */
.nv-drive-eta{flex:1 1 0%;min-width:0;display:flex;flex-direction:column;justify-content:center;gap:1px;}
.nv-drive-eta-main{font-size:20px;font-weight:800;line-height:1.15;white-space:nowrap;}
.nv-drive-eta-sub{font-size:12.5px;color:#D9E1F3;line-height:1.3;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;}
.nv-drive-miles{font-size:12px;color:#9FB4DD;line-height:1.25;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.nv-drive-bar .btn{flex:none;width:auto;margin:0;min-height:48px;padding:10px 12px;font-size:15px;}
.nv-drive-bar #nvd_mute{padding:10px;min-width:52px;display:flex;align-items:center;justify-content:center;}
/* v139 (owner, Oct 17 12:42 MDT — "Instead of saying sound on
   and off! It should give an option to mute!"): the drive
   bar's sound control is a MUTE BUTTON — a speaker glyph that
   gains a slash + × and a warning-tint ground when muted. The
   state reads at a glance from across the cab: tint + slashed
   icon, never a text label to squint at. (Colour only — no
   opacity, per the v135/v139 chrome discipline.) */
.nv-drive-bar #nvd_mute.muted{background:rgba(242,101,34,.30);color:#ffd9c4;}
body.mt-driving #fbFab{display:none!important;}
/* v135 CORRECTION OF THE RECORD (owner, Oct 17 12:05 MDT):
   the v125 rule above targeted #fbFab — the FEEDBACK button —
   and it works on that button. But the button in the owner's
   recordings, floating mid-map in landscape at ~69% across /
   ~68% down in EVERY frame (and sitting on the ▶ Drive button
   on the post-arrival page of the 11:44–11:56 recording), is
   the CHAT FAB, #chatFab — a different element no mt-driving
   rule had ever governed. Its ordinary spot (right:16px;
   bottom:88px, pulled to the centred app column's right edge
   at ≥520px widths) IS that mid-map spot in landscape. The
   #fbFab hide stays; the chat FAB is handled by the dock below. */
/* v135: THE MIC DOCKS TO THE SIDE (owner verbatim: "That
   should be on the side!"). While Drive is open, the chat FAB
   pins to the screen's RIGHT EDGE, vertically centred — a
   compact 52px button hard against the edge (plus the
   safe-area inset), never floating over the map's middle, in
   portrait and landscape alike. The ≥520px column rule
   (right:calc(50% - 224px)) is overridden by specificity here
   (body.mt-driving #chatFab beats #chatFab), so the dock hugs
   the GLASS edge, not the app column's edge. z-index 2100
   puts it above the drive screen (.nv-drive is 2000) —
   reachable — and the sheet/scrim rise with it, so tapping
   the docked mic actually opens the road hand over the drive
   face instead of underneath it. Outside Drive none of this
   applies: the class leaves with exitDrive and the FAB is
   back at its ordinary spot, governed by syncFab in
   js/chatbot.js. NO transition is declared: the dock snaps,
   it does not glide or fade (v135 fading audit). */
/* v139: the dock centres the 52px button with arithmetic,
   not a transform (top:calc(50% - 26px)) — the v135
   translateY(-50%) put the docked mic in the same promoted-
   layer class that kept vanishing with the chrome. */
body.mt-driving #chatFab{top:auto;bottom:calc(96px + env(safe-area-inset-bottom,0px));left:calc(92px + env(safe-area-inset-left,0px));right:auto;width:52px;height:52px;font-size:22px;z-index:2100;}
/* v280 (NAV-DEMO-FLOATING-CONTROLS-OVER-MAP): the mic's drive dock
   MOVES from the right-edge middle (it floated over the junction /
   road area on the right of his frame) to the bottom-left chrome
   corner, on the speedometer's own line, beside the speed readout —
   off the driving surface's centre and right side entirely. */
/* v280 (NAV-PIP-NO-MIC-BUTTON): in PiP the mic is GONE — no
   microphone button on the PiP map face, his standing PiP law.
   The class is raised by the Navigator's applyPipFace. */
body.mt-pip #chatFab{display:none!important;}
body.mt-driving #chatScrim{z-index:2110;}
body.mt-driving #chatSheet{z-index:2120;}
/* v125 landscape: on a short wide screen the portrait banner
   ate ~a third of the display (measured 124 of 390 px) and the
   map shrank to a strip. In landscape the banner goes compact
   and horizontal — distance left, instruction + road stacked
   beside it — and the bar tightens, so the map keeps the
   middle of the screen.
   v300 (NAV-LANDSCAPE-ORIENTATION): the height cap rises
   500 -> 760 px. His phone is a Galaxy Z Fold — unfolded in
   landscape its screen is ~700 CSS px tall, so the v125 query
   NEVER matched on the truck mount and the drive view kept
   its portrait posture sideways ("no landscape mode",
   Oct 31). The compact landscape face now engages on the
   Fold open or closed, and on ordinary phones as before. */
@media (orientation:landscape) and (max-height:760px){
.nv-drive-top{display:flex;align-items:center;gap:16px;padding:8px 14px;top:calc(6px + env(safe-area-inset-top));left:8px;right:8px;}
.nv-drive-toptext{flex:1;min-width:0;}
.nv-drive-dist{font-size:30px;}
.nv-drive-inst{font-size:16px;-webkit-line-clamp:2;margin-top:0;}
.nv-drive-road{font-size:12.5px;min-height:0;margin-top:2px;}
.nv-drive-bar{left:8px;right:8px;bottom:calc(6px + env(safe-area-inset-bottom));padding:8px 12px;}
.nv-drive-reroute{bottom:calc(78px + env(safe-area-inset-bottom));}
.nv-drive-traffic{bottom:calc(120px + env(safe-area-inset-bottom));}
.nv-avatar-picker{bottom:calc(84px + env(safe-area-inset-bottom));}
/* v237: in landscape the banner is the compact flex row (~64px
   tall) — the inset's top-right dock rides the line under it. */
.nv-drive-inset{top:calc(76px + env(safe-area-inset-top));}
}
/* v107: tile readability — a contrast/saturation lift on the map
   tiles themselves (the lever the OSM tile source leaves us).
   v124: the DRIVE map got a stronger lift of its own.
   v125: that lift is GONE from the drive map, and the drive
   basemap changed with it (Esri World Street Map, see
   driveTileSpec in navigator.js). OSM Carto colours motorways salmon pink; at
   saturate(1.42) they rendered as the garish HOT PINK of the
   owner's Oct 17 recording, and the filter additionally forced
   a full tile repaint on every composite — half the flicker.
   The Esri basemap ships calm and is drawn exactly as served:
   NO filter on drive tiles. The planning map is unchanged (OSM
   source + the gentler shared lift).
   v239 (NAV-MAP-COLORS-3D, owner, Oct 23, 2026: "the colours
   on our map are abysmal" — pale/washed against the Rand
   McNally benchmark): the no-filter rule is superseded for
   the DRIVE street layer only, on measurements taken from
   the live tiles (see nav-map-colors-v239-test.js): the
   street layer's tiles wear the nv-tile-street class and get
   contrast(1.06) saturate(1.38) — simulated mean saturation
   on his home tile 0.150 -> 0.202, downtown vivid share
   3.3% -> 9.0%. The v125 hot-pink failure was OSM's salmon
   motorways under a lift; Esri's palette has no such band.
   Satellite/hillshade layers (v240 classes) get NO lift —
   aerial photos are already true colour.
   v260 (NAV-VIBRANT-COLORS, owner, Oct 26, 2026 07:41 MDT:
   "You have to change the colors. Something more vibrant"):
   the drive street source is OSM standard again (see
   driveTileSpec — it is the only keyless source with real
   z18 detail over his areas), and OSM raw measures paler
   than Esri did (suburb mean saturation 0.052), so the lift
   is re-tuned for THIS palette, simulated in PIL on the
   live tiles before shipping: saturate(2.3)
   hue-rotate(11deg) contrast(1.16) — suburb mean
   saturation 0.052 -> 0.110, Deerfoot/Bow 0.111 -> 0.206;
   water #a7c6e4 -> #98d1ff (rich blue), park #c8facc ->
   #8fffbc (vivid green), trunk #f5ba97 -> #ffbe40 (bold
   orange), primary #fcd6a4 -> #ffe749 (bold yellow),
   motorway #e88ea0 -> #ff767e (bold coral-red — the
   +11deg rotation is what holds it out of the v124
   hot-pink band; larger rotations were simulated and
   rejected: +22deg washed the water to periwinkle and the
   parks to teal). Satellite/hillshade keep NO lift. */
.nv-mapzone .leaflet-tile{filter:contrast(1.08) saturate(1.18);}
.nv-tile-street{filter:saturate(2.3) hue-rotate(11deg) contrast(1.16);}
/* v240 (NAV-LAYERS-BUTTON): the layers control — the button
   docked bottom-right on the speedometer's own 96px line
   (the dock the inset vacated in v237), its panel floating
   above it. Plain chrome, no transform (v139 budget). The
   active layer wears the app's orange; Traffic's "soon" tag
   and the not-live-yet note live here too. */
.nv-layers-btn{position:absolute;right:10px;bottom:calc(96px + env(safe-area-inset-bottom));z-index:5;width:46px;height:46px;border:none;border-radius:14px;background:#1F3764;color:#fff;display:flex;align-items:center;justify-content:center;box-shadow:0 4px 14px rgba(0,0,0,.45);cursor:pointer;padding:0;}
.nv-layers-panel{position:absolute;right:10px;bottom:calc(152px + env(safe-area-inset-bottom));z-index:5;width:196px;background:#1F3764;color:#fff;border-radius:14px;padding:10px;display:flex;flex-direction:column;gap:6px;box-shadow:0 4px 18px rgba(0,0,0,.45);}
.nv-layers-title{font-size:11.5px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:#D9E1F3;padding:0 2px;}
.nv-layer-opt{display:flex;align-items:center;gap:8px;background:rgba(255,255,255,.08);color:#fff;border:2px solid transparent;border-radius:10px;padding:9px 10px;font-size:14.5px;font-weight:600;text-align:left;cursor:pointer;}
.nv-layer-opt.on{border-color:#e67e00;background:rgba(230,126,0,.18);}
.nv-layer-soon{margin-left:auto;font-size:10.5px;font-weight:700;background:rgba(255,255,255,.16);color:#D9E1F3;padding:2px 7px;border-radius:999px;}
.nv-layers-note{font-size:12.5px;line-height:1.35;color:#FFE1B3;background:rgba(230,126,0,.15);border-radius:8px;padding:7px 9px;}
/* v177 (F4): the Navigator's own settings menu — a full-width overlay
   panel inside the module (shell face or drive face; the node moves
   to whichever face opened it). */
.nv-settings{position:absolute;inset:0;z-index:50;background:rgba(13,21,38,.55);overflow-y:auto;padding:10px;}
.nv-drive .nv-settings{z-index:60;}
/* v177 (F4): speedometer + speed-limit overlays on the drive face,
   bottom-left above the bar; the toggles in the settings menu own
   their visibility. */
.nv-drive-speed{position:absolute;left:10px;bottom:calc(96px + env(safe-area-inset-bottom));z-index:4;display:flex;flex-direction:column;align-items:center;gap:6px;}
.nv-drive-speedo{background:rgba(13,21,38,.82);color:#fff;border-radius:14px;min-width:74px;text-align:center;padding:8px 10px;font-size:30px;font-weight:800;line-height:1;box-shadow:0 2px 10px rgba(0,0,0,.4);}
.nv-drive-speedo::after{content:'km/h';display:block;font-size:11px;font-weight:600;color:#D9E1F3;margin-top:3px;}
.nv-drive-limit{background:#fff;color:#c81e1e;border:4px solid #c81e1e;border-radius:50%;width:52px;height:52px;display:flex;align-items:center;justify-content:center;font-size:19px;font-weight:800;box-shadow:0 2px 10px rgba(0,0,0,.4);}
/* v178 (F2): the turn inset + the route-line chevrons. The inset is
   plain chrome (no transform, no promotion — v139 budget intact);
   the chevron divIcons carry no Leaflet default box. */
/* v193 (NAV-CUTOFF, owner, Oct 21, 2026 07:39 MDT, same drive):
   the inset hung at top:34% — in the chase view that is exactly
   where the road ahead runs, so the card sat ON the route line
   under the banner and cut it. Standing rule: nothing is cut
   off at the top of the map, and the chrome stacks without
   overlap — banner top, map clear beneath it, bar at the foot.
   The inset docked bottom-right (the v193 move).
   v237 (NAV-INSET-SMALL-TOPRIGHT, owner, Fri Oct 23, 2026
   11:36 MDT, drive video): the bottom-right card "takes HALF
   the screen and is mostly empty dark panel" — his order
   supersedes the v193 dock: the card becomes SMALL and moves
   to the TOP RIGHT. The v193 stack law survives the move: the
   card hangs BELOW the banner (198px + safe-area clears the
   banner's worst case — 3-line instruction — by a gap), on
   the right, plain chrome, and its foot (~215px tall) still
   clears the centred truck's box. Nothing inside was cut:
   junction drawing, lane strip and header all stay, drawn
   compact (junction capped at 140px, 27px lane cells, 38px
   header arrow) so the card reads at a glance. */
.nv-drive-inset{position:absolute;top:calc(146px + env(safe-area-inset-top));right:10px;z-index:4;background:#1F3764;color:#fff;border-radius:16px;padding:8px 10px;display:flex;flex-direction:column;gap:6px;box-shadow:0 4px 18px rgba(0,0,0,.45);width:auto;max-width:54vw;}
/* v280: the inset is the LANES-ONLY strip now (see navigator.js) —
   its dock follows the capped banner (146px) instead of the old
   198px worst case, and its width hugs the strip it carries. */
/* v277 (NAV-SCREEN-STILL-CUTTING): the away dock — when the
   maneuver bends RIGHT, the card rides the LEFT edge, so it
   never covers the bend it describes (his Oct 28 frame: the
   card sat on the Stoney Trail ramp it was announcing). The
   tick toggles the class with the inset's rebuild key. */
.nv-drive-inset.nv-inset-left{left:10px;right:auto;}
/* v195 (NAV-POPUP): the junction view inside the pop-up — the
   enlarged drawing of the upcoming turn (route highlighted),
   over the v178 header row (bent arrow + distance + words). */
.nv-inset-junction{background:rgba(255,255,255,.08);border-radius:10px;padding:4px;max-width:140px;width:100%;margin:0 auto;box-sizing:border-box;}
.nv-inset-junction svg{display:block;width:100%;height:auto;}
/* v196 (NAV-LANES): the lane strip inside the turn pop-up —
   one cell per OSM lane on the approach road; the route lane
   burns bright with the turn's arrow, the rest sit dim. Absent
   entirely when OSM carries no lane count for the road. */
.nv-inset-lanes{display:flex;gap:4px;justify-content:center;padding:2px 2px 4px;}
/* v238 (NAV-LANE-INFO-REAL): a dense real-lane strip (many
   lanes, several multi-arrow) may wrap inside the small card —
   order stays left-to-right, top row first. */
.nv-inset-lanes.wrap{flex-wrap:wrap;}
.nv-lane{display:flex;align-items:center;justify-content:center;width:27px;height:27px;border-radius:7px;background:rgba(255,255,255,.10);}
/* v238: a real-lane cell sizes to its arrows (a lane can allow
   two movements); single-arrow cells keep the 27px box. */
.nv-lane.nv-lane-arrows{width:auto;min-width:27px;padding:0 3px;gap:1px;}
.nv-lane.nv-lane-arrows svg{flex:none;}
.nv-lane svg{width:21px;height:21px;}
.nv-lane.on{background:#e67e00;box-shadow:0 0 0 2px #ffffff inset;}
.nv-inset-head{display:flex;align-items:center;gap:8px;}
.nv-inset-text{display:flex;flex-direction:column;min-width:0;}
.nv-inset-text b{font-size:17px;line-height:1.1;}
.nv-inset-text span{font-size:12.5px;color:#D9E1F3;line-height:1.25;}
/* v264 (NAV-LANE-INSET-EMPTY): the sign block under the inset's
   header — the exit number as a road-sign-green chip, the road
   the maneuver takes, and the destinations it points toward,
   all from the route response's own sign data. Present only
   when the response carries a sign for the maneuver. */
.nv-inset-sign{display:flex;align-items:center;gap:7px;background:rgba(255,255,255,.08);border-radius:9px;padding:5px 7px;}
.nv-inset-exit{flex:none;background:#1e7d32;color:#fff;font-weight:700;font-size:11.5px;border-radius:5px;padding:2px 6px;letter-spacing:.02em;}
.nv-inset-sign-text{display:flex;flex-direction:column;min-width:0;}
.nv-inset-sign-text b{font-size:12.5px;line-height:1.2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.nv-inset-sign-text span{font-size:11.5px;color:#D9E1F3;line-height:1.2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.nv-route-arrow{background:transparent;border:none;}

/* v279 (APP-PIP-POPUP-RENDER, owner, Oct 29, 2026): the app in a
   small floating pop-up window (his PiP ≈ 221×139; Samsung
   pop-up sizes vary). The base topbar's FIXED pieces — padding
   16+16, two 44px buttons, the 36px logo, the sync dot, three
   12px gaps — sum to ~228px, wider than the pop-up itself, so
   the bar was sawn off at the right edge ("Me…us") and its
   72px height left ~67px of body in a 139px window. This
   query gives small windows a compact bar: 36px buttons, a
   28px logo, tight gaps, and a title that shrinks with an
   ellipsis instead of pushing the bar off-screen; the
   dashboard pad and the trial banner trim so real content
   reaches the visible area. Full-screen rules are untouched. */
@media (max-width:340px), (max-height:240px){
  .topbar{padding:8px 10px;gap:6px;}
  .topbar .iconbtn{min-width:36px;min-height:36px;padding:4px;font-size:19px;}
  .topbar .logo{width:28px;height:28px;border-radius:8px;}
  .topbar .logo img{width:24px;height:24px;}
  .topbar h1{font-size:16px;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
  .syncdot{margin-left:0;padding:5px;}
  .pad{padding:12px;}
  .trialbar{margin:10px 10px 0;padding:8px 12px;}
}

/* v280 (NAV-DEMO-FLOATING-CONTROLS-OVER-MAP): the layers button
   IN the drive bar (navigator.js moves the node there) — a plain
   bar button, no absolute dock, no floating over the roads. Its
   panel keeps its absolute float above the bar, unchanged. */
.nv-layers-btn.inbar{position:static;flex:none;width:44px;height:44px;border-radius:12px;box-shadow:none;}
/* v280 (NAV-TRAFFIC-LIVE-ETA): the faster-route offer pill — the
   traffic stack's top line (reroute 96 / traffic 140 / offer 184),
   transient only while an offer is live. */
.nv-drive-offer{position:absolute;left:0;right:0;margin-left:auto;margin-right:auto;width:max-content;max-width:92%;bottom:calc(184px + env(safe-area-inset-bottom));background:#1F3764;color:#fff;font-weight:700;font-size:14.5px;padding:9px 16px;border:2px solid #f5a623;border-radius:999px;z-index:6;box-shadow:0 2px 8px rgba(0,0,0,.35);cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
