
:root{
  --teal:#019A9A; --ink:#0D1214; --panel:#151C1F; --raised:#1D262A; --line:#2A363B;
  --t1:#E8EDEE; --t2:#93A3A8; --t3:#6B7A7F;
  --moving:#34C77B; --idle:#E8A13D; --stopped:#7A8A8F; --offline:#55646A; --alarm:#E5484D; --nogps:#F2711C;
  --hair:rgba(232,237,238,.08);
}
/* ───── Pass M1: device tokens ─────
   BREAKPOINTS, documented here beside the palette. shared/device.js sets data-device on
   <html> from these widths (phone < 600 · tablet 600–1199 · desktop 1200–2559 · wall ≥ 2560
   or ?wall), data-pointer from (pointer: coarse), and data-layout — the device class, except
   that a fine-pointer window 1024–1199 px wide keeps the desktop layout. Every layout rule
   below keys on data-layout / data-pointer, never on a user agent. Media queries cannot read custom properties, so the
   numbers are also written in device.js — these are the record of what they mean.
   TYPE SCALE in rem on the unchanged 16px root: each step is its px value ÷ 16, exactly, so
   a desktop page renders to the same pixel after the shell moves onto the tokens (a test
   compares the desktop references before and after). Page bodies keep their px until their
   own pass (M2–M5) — a global find-and-replace is the regression the pixel check exists for.
   --tap is the touch floor every shell control meets on a coarse pointer. */
:root{
  --bp-tablet:600px; --bp-desktop:1200px; --bp-wall:2560px;
  --fs-10:.625rem; --fs-11:.6875rem; --fs-12:.75rem; --fs-12-5:.78125rem; --fs-13:.8125rem;
  --fs-14:.875rem; --fs-15:.9375rem; --fs-16:1rem; --fs-17:1.0625rem; --fs-18:1.125rem; --fs-20:1.25rem;
  --sp-2:.125rem; --sp-4:.25rem; --sp-6:.375rem; --sp-8:.5rem; --sp-10:.625rem; --sp-12:.75rem;
  --sp-14:.875rem; --sp-16:1rem; --sp-20:1.25rem; --sp-24:1.5rem; --sp-32:2rem;
  --tap:44px;
  --safe-top:env(safe-area-inset-top,0px); --safe-right:env(safe-area-inset-right,0px);
  --safe-bottom:env(safe-area-inset-bottom,0px); --safe-left:env(safe-area-inset-left,0px);
  --rail-w:220px; --rail-w-min:72px; --tabbar-h:56px;
}
@font-face{font-family:"IBM Plex Sans";font-style:normal;font-weight:450;font-display:swap;src:url("../vendor/fonts/ibm-plex-sans-450-normal.woff2") format("woff2")}
*{box-sizing:border-box;margin:0;padding:0}
/* The hidden attribute must WIN: author display rules (.btn's inline-flex, any flex
   container item) silently out-rank the UA's [hidden]{display:none}, so el.hidden=true
   left such elements visible (Alarms' bulk buttons, live). !important restores the
   attribute's meaning everywhere. */
[hidden]{display:none!important}
html,body{height:100%}
body{background:var(--ink);color:var(--t1);font:var(--fs-14)/1.45 "IBM Plex Sans",sans-serif;overflow:auto}
a{color:var(--teal);text-decoration:none} @media (hover:hover){a:hover{color:#26b3b3;text-decoration:underline}}
button{font:inherit;color:inherit;background:none;border:none;cursor:pointer}
.mono{font-family:"IBM Plex Sans",sans-serif;font-weight:600;font-variant-numeric:tabular-nums}
:focus-visible{outline:2px solid var(--teal);outline-offset:1px}
::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-thumb{background:var(--raised);border-radius:3px}::-webkit-scrollbar-track{background:transparent}
@media (hover:hover){.list:hover::-webkit-scrollbar,.erows:hover::-webkit-scrollbar,.sdrop:hover::-webkit-scrollbar{width:8px}}

/* ───── form-control baseline ─────
   The floor under every <input>/<select>/<textarea> no styled container claims: dark,
   themed, 32px — never browser-default white. color-scheme makes the NATIVE chrome
   follow too (select dropdown lists, date/time/datetime-local pickers, number spinners,
   scrollbars), which is why nothing here hides the spinners.
   Every input selector is wrapped in :where(...) so the whole block stays at ELEMENT
   specificity (0,0,1) — written bare, the seven :not([type=…]) terms would weigh
   (0,7,1) and silently out-rank every container rule in the platform (.aform input,
   .dwr-field input, .efield input, .csbox input, …). Containers must keep winning.
   Time (and datetime-local) inputs get every BOX property but not the font shorthand:
   Chromium detaches a native time control's keyboard segment editing when author CSS
   assigns any font-family (drawer.css and Admin's schedule editor document the bug), so
   they keep the UA family at the baseline size. */
:root{color-scheme:dark}
input:where(:not([type=range]):not([type=checkbox]):not([type=radio]):not([type=color]):not([type=file]):not([type=button]):not([type=submit])),
select,textarea{background:var(--panel);color:var(--t1);border:1px solid var(--line);border-radius:6px;height:32px;padding:0 10px}
input:where(:not([type=range]):not([type=checkbox]):not([type=radio]):not([type=color]):not([type=file]):not([type=button]):not([type=submit]):not([type=time]):not([type=datetime-local])),
select,textarea{font:var(--fs-13) "IBM Plex Sans",sans-serif}
input:where([type=time],[type=datetime-local]){font-size:var(--fs-13)}
textarea{height:auto;padding:8px 11px;line-height:1.4}
input:where(:not([type=range]):not([type=checkbox]):not([type=radio]):not([type=color]):not([type=file]):not([type=button]):not([type=submit])):focus,
select:focus,textarea:focus{outline:none;border-color:var(--teal);box-shadow:0 0 0 1px var(--teal)}
input:where(:not([type=range]):not([type=checkbox]):not([type=radio]):not([type=color]):not([type=file]):not([type=button]):not([type=submit]))::placeholder,
textarea::placeholder{color:var(--t3)}
input:where(:not([type=range]):not([type=checkbox]):not([type=radio]):not([type=color]):not([type=file]):not([type=button]):not([type=submit])):disabled,
select:disabled,textarea:disabled{opacity:.55}
input:where([type=range],[type=checkbox],[type=radio]){accent-color:var(--teal)}
/* No native white arrow box: a small chevron in --t2, drawn by the stylesheet. Containers
   that repaint a select's background must use background-COLOR (not the shorthand) or
   they erase this glyph — every container in the repo was audited to comply. */
select{appearance:none;-webkit-appearance:none;padding-right:28px;background-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="10" height="6" viewBox="0 0 10 6"><path d="M1 1l4 4 4-4" fill="none" stroke="%2393A3A8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>');background-repeat:no-repeat;background-position:right 9px center}
select option{background:var(--panel);color:var(--t1)}
/* Compact variant for dense grids (Admin geofence classes, and anywhere a 32px control
   is too tall for its row). */
input.sm,select.sm{height:28px;font-size:var(--fs-12-5)}
/* Pass M2a (item 2) — FIELDS THAT DO NOT ZOOM THE PAGE. iOS Safari zooms the whole page when
   a field under 16 px takes focus, and leaves it zoomed until the user pinches out. On a COARSE
   pointer (device.js's data-pointer — the pointer, never the width: a touch device of any size
   zooms) every text-entry field renders at 16 px (1rem): .sm, the time/datetime-local inputs
   (size only — never a font-family, see above) and whatever a container sets. !important
   because container rules out-rank this baseline by design (see the :where note above), and
   here the floor must win. Heights stay 32 / 28 px. A fine pointer is untouched. */
:root[data-pointer=coarse] :is(input:not([type=range]):not([type=checkbox]):not([type=radio]):not([type=color]):not([type=file]):not([type=button]):not([type=submit]):not([type=reset]):not([type=image]),select,textarea){font-size:1rem!important}

.glass{background:rgba(13,18,20,.82);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px);border:1px solid var(--hair);border-radius:10px;box-shadow:inset 0 1px 0 rgba(255,255,255,.06)}
.dock,.layers,.events,.edgetab,.pop,.ctxmenu,.toolstack{box-shadow:inset 0 1px 0 rgba(255,255,255,.06),0 8px 32px rgba(0,0,0,.45)}
/* Plate chips are WEIGHT 500 (Pass 9 D, Troy + Otto: the semi-bold read as "bolded") with the
   tracking widened by .02em so the condensed face keeps its legibility at the lighter weight;
   colour, box and heights unchanged. One rule for every chip size — .lg/.md/.sm/.xs inherit. */
.pchip{display:inline-flex;align-items:center;font-family:"Barlow Semi Condensed","IBM Plex Sans",sans-serif;font-weight:500;letter-spacing:.02em;text-transform:uppercase;line-height:1;color:#E8EDEE;white-space:nowrap;background:rgba(232,237,238,.03);border:none;border-left:1px solid rgba(232,237,238,.07);border-right:1px solid rgba(232,237,238,.07);border-radius:6px;box-shadow:inset 0 1px 0 rgba(255,255,255,.10),inset 0 -1px 0 rgba(0,0,0,.30)}
.pchip b{font-weight:500}
.pchip.lg{height:30px;padding:0 var(--sp-12);font-size:var(--fs-20)}
.pchip.sm{height:22px;padding:0 var(--sp-8);font-size:var(--fs-14)}
.pbare{display:inline-flex;font-family:"Barlow Semi Condensed","IBM Plex Sans",sans-serif;font-weight:600;letter-spacing:0;text-transform:uppercase;line-height:1.1;color:#E8EDEE;white-space:nowrap;font-size:15px}
.pbare b{font-weight:600}
/* Pass 17 (D1) — this badge was MEASURED and left alone. `line-height:1` crushes a badge's
   line box below its glyphs' own content area, and where such a badge is a direct FLEX
   ITEM of an align-items:center row that displaces the letters: it is what put the rail
   drawer's role tag 3.03 px off the word beside it (fixed at .tt-tag in shared/status.js).
   `.tt` is not in that position — it sits inline inside a cell — and the sweep found it on
   the line: dock header 0.50 px, Alarms 0.31 px, Assets 1.17 px, before and after. Filling
   its line box moved it a quarter of a pixel the WRONG way on all three, so the rule that
   was not broken keeps its shape. */
.tt{display:inline-flex;align-items:center;height:20px;padding:0 8px;border-radius:6px;font-family:"IBM Plex Sans",sans-serif;font-size:var(--fs-11);font-weight:600;letter-spacing:.05em;text-transform:uppercase;white-space:nowrap;line-height:1}
.toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%);z-index:3000;padding:9px 16px;font-size:12px;color:var(--t1)}

/* ───── Pass M1 (item 2): THE FRAME ─────
   The frame stops assuming 1280 px. The 1280 floor is a DESKTOP rule now, and only a desktop
   rule: phone and tablet are 100 % wide and 100dvh tall (100vh where dvh is unknown), and
   the document never scrolls sideways. The content column is minmax(0,1fr) on every device,
   so nothing inside a page can widen the frame — a page body that is too wide scrolls inside
   its own container until its pass (M2–M5) lays it out. Focus mode stays as it was. */
.app{display:grid;grid-template-columns:var(--rail-w) minmax(0,1fr);height:100vh;height:100dvh}
[data-layout=desktop] .app{min-width:1280px}
.app.navmin{grid-template-columns:var(--rail-w-min) minmax(0,1fr)}
body.focus .app{grid-template-columns:0 minmax(0,1fr)}
body.focus .rail{display:none}
[data-layout=phone] .app,[data-layout=tablet] .app{padding-left:var(--safe-left);padding-right:var(--safe-right)}
/* Phone: one column — the nav is the bottom tab bar (item 3), fixed, so the frame leaves it
   its height plus the home-indicator inset. Written after .navmin / body.focus on purpose:
   neither may put a rail column back on a phone. */
[data-layout=phone] .app,[data-layout=phone] .app.navmin,[data-layout=phone] body.focus .app{grid-template-columns:minmax(0,1fr)}
[data-layout=phone] .app{padding-bottom:calc(var(--tabbar-h) + var(--safe-bottom))}
[data-layout=phone] body.focus .app{padding-bottom:0}

/* ───── nav rail ───── */
.rail{background:var(--panel);border-right:1px solid var(--line);display:flex;flex-direction:column;min-height:0;overflow:hidden}
.logo{height:64px;display:flex;align-items:center;gap:10px;padding:0 16px;border-bottom:1px solid var(--line);flex:none;white-space:nowrap}
.navmin .logo{justify-content:center;padding:0}
.logo .lbar{width:2px;height:26px;background:var(--teal);border-radius:1px;flex:none}
.wordmark{font-size:var(--fs-14);font-weight:600;letter-spacing:.14em;color:var(--t1)}
.navmin .lbar,.navmin .wordmark{display:none}
.navitems{padding:8px;display:flex;flex-direction:column;gap:2px;flex:1;overflow-y:auto;overflow-x:hidden}
.nitem{position:relative;display:flex;align-items:center;gap:var(--sp-12);height:var(--tap);padding:0 var(--sp-16);border-radius:12px;color:var(--t2);font-size:var(--fs-14);text-align:left;white-space:nowrap;transition:background .15s ease-out,color .15s ease-out}
.navmin .nitem{justify-content:center;padding:0}
.navmin .nitem .nlabel{display:none}
.nitem svg{flex:none}
@media (hover:hover){.nitem:hover{background:var(--raised);color:var(--t1)}}
a.nitem{text-decoration:none}@media (hover:hover){a.nitem:hover{text-decoration:none}}
.nitem.active{color:var(--teal);background:rgba(1,154,154,.08)}
.nitem.active::before{content:"";position:absolute;left:0;top:10px;bottom:10px;width:3px;border-radius:2px;background:var(--teal)}
.nitem .badge{margin-left:auto;background:var(--alarm);color:#fff;font-size:var(--fs-11);font-weight:600;line-height:1;padding:3px 7px;border-radius:9px}
.navmin .nitem .badge{position:absolute;top:4px;right:6px;margin:0;padding:2px 5px;font-size:var(--fs-10)}
/* Pass 20 (D5). The rail is 220px and the account-menu button rendered at x=228→256 with a
   forty-character user name — 36px outside it, and 154px outside with the detector's seed.
   That button is the only way to Sign out, Change password or reach Alarm sounds, and it is
   the same rail on every page, so any user whose name ran past about fourteen characters lost
   all three everywhere: a test client could not sign out without a scripted click.
   `.utxt` had no CSS at all, so it sized to its content and pushed the button out of the rail.
   The name takes the REMAINING width (min-width:0 — a flex item's default `auto` minimum is
   exactly what let it refuse to shrink), stays on one line, ellipsizes, and keeps the whole
   name in `title`; the button is flex-none and right-aligned, so it is always inside. */
.userchip{height:56px;flex:none;border-top:1px solid var(--line);display:flex;align-items:center;gap:10px;padding:0 16px;white-space:nowrap;min-width:0}
.navmin .userchip{justify-content:center;padding:0}
.navmin .userchip .utxt{display:none}
.avatar{width:32px;height:32px;border-radius:50%;background:var(--raised);border:1px solid var(--line);display:flex;align-items:center;justify-content:center;color:var(--teal);font-size:var(--fs-13);font-weight:600;flex:none}
.utxt{flex:1 1 auto;min-width:0;overflow:hidden}
.uname{font-size:var(--fs-13);font-weight:500;line-height:1.2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.urole{font-size:var(--fs-12);color:var(--t2);line-height:1.2;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.navfoot{flex:none;border-top:1px solid var(--line);display:flex}
.navcollapse{flex:1;height:36px;display:flex;align-items:center;justify-content:center;color:var(--t2)}
@media (hover:hover){.navcollapse:hover{background:var(--raised);color:var(--t1)}}
.navcollapse svg{transition:transform .2s ease-out}
.navmin .navcollapse svg{transform:rotate(180deg)}

/* The critical-alarm banner (shared/alerts.js) is SHELL — it rides every page above the
   content — and at desktop widths it is one 56 px row. On a phone that row is wider than the
   screen and was widening the document; here it wraps instead, so the Acknowledge and Open
   buttons stay on screen (never clipped, never scrolled away) and meet the touch floor. */
[data-layout=phone] .stban,[data-layout=tablet] .stban{height:auto;min-height:56px;flex-wrap:wrap;row-gap:8px;padding-top:8px;padding-bottom:8px}
[data-layout=phone] .stban .bsp{flex:0 0 100%;height:0}
[data-layout=phone] .stban .bev,[data-layout=phone] .stban .bmeta{max-width:100%}
[data-pointer=coarse] .stban .bbtn{height:44px}

/* The content column CONTAINS what it cannot fit. minmax(0,1fr) stops a page from widening
   the grid track, but a descendant that overflows the track still spills into the document's
   scroll area — History's open asset picker overhung a phone by 11 px and scrolled the whole
   page. Below the desktop the column clips that overflow instead (overflow-x: clip creates no
   scroll container, so sticky and the page's own scrollers are untouched); page bodies that
   are too wide are laid out properly in their own pass (M2–M5). Desktop is left exactly as it
   was — the 1280 floor has always given it room. */
:is([data-layout=phone],[data-layout=tablet]) .main{overflow-x:clip}

/* ───── Pass M1 (item 2): the top bar below the desktop ─────
   On a phone or tablet, one row: the title sticks at the left edge and every control after it is a strip that
   scrolls sideways INSIDE the bar — the page never does. shared/nav.js marks the bar
   data-scroll="x", which is how the layout detector knows this one container is meant to
   scroll horizontally. (A title row over a separate strip would need a wrapper element in
   every page's markup; that is page work, and this is the shell.) The attribute selector
   out-ranks pages.css's .phead, which loads after this file. */
:is([data-layout=phone],[data-layout=tablet]) .phead[data-scroll=x]{height:auto;min-height:56px;padding:0 var(--sp-16) 0 0;gap:var(--sp-10);flex-wrap:nowrap;overflow-x:auto;overflow-y:hidden;overscroll-behavior-x:contain;scrollbar-width:none}
:is([data-layout=phone],[data-layout=tablet]) .phead[data-scroll=x]::-webkit-scrollbar{display:none}
:is([data-layout=phone],[data-layout=tablet]) .phead[data-scroll=x]>h1{position:sticky;left:0;z-index:2;flex:none;align-self:stretch;display:flex;align-items:center;padding:0 var(--sp-10) 0 var(--sp-16);background:var(--panel);font-size:var(--fs-18)}
:is([data-layout=phone],[data-layout=tablet]) .phead[data-scroll=x]>.spacer{flex:0 0 0}
/* A bar that anchors a popover (nav.js marks it data-bar="wrap") wraps instead of scrolling —
   a scrolling box would clip the popover hanging out of it. */
:is([data-layout=phone],[data-layout=tablet]) .phead[data-bar=wrap]{height:auto;min-height:56px;flex-wrap:wrap;row-gap:var(--sp-8);padding:var(--sp-8) var(--sp-16)}
:is([data-layout=phone],[data-layout=tablet]) .phead[data-bar=wrap]>h1{flex:1 0 100%;font-size:var(--fs-18)}
:is([data-layout=phone],[data-layout=tablet]) .phead[data-bar=wrap]>.spacer{display:none}
/* ...and so do the rows inside such a bar (History's own two-row header): each row wraps its
   controls onto as many lines as the width needs — nothing hangs off the right edge. */
html:is([data-layout=phone],[data-layout=tablet]) .phead[data-bar=wrap]>*{flex-wrap:wrap;height:auto;min-height:44px;max-width:100%;row-gap:var(--sp-8);padding-top:var(--sp-6);padding-bottom:var(--sp-6)}
html:is([data-layout=phone],[data-layout=tablet]) .phead[data-bar=wrap]>*>*{flex-wrap:wrap;max-width:100%}
/* On the desktop a bar that does not fit (Geofences' seven controls) scrolls inside itself
   rather than scrolling the page; a bar that fits is unchanged to the pixel. */
:is([data-layout=desktop],[data-layout=wall]) .phead[data-scroll=x]{overflow-x:auto;overflow-y:hidden;scrollbar-width:thin}

/* ───── main / strip ───── */
.main{display:flex;flex-direction:column;min-width:0;min-height:0}
.strip{height:56px;flex:none;background:rgba(13,18,20,.82);backdrop-filter:blur(14px);border-bottom:1px solid var(--hair);box-shadow:inset 0 1px 0 rgba(255,255,255,.06);display:flex;align-items:center;padding:0 16px;gap:16px;position:relative;z-index:1300}
.focuschip{display:none;align-items:center;gap:7px;font-size:var(--fs-12);color:var(--teal);border:1px solid rgba(1,154,154,.4);border-radius:6px;padding:4px 10px}
body.focus .focuschip{display:flex}
.blocks{display:flex;align-items:center;gap:32px}
.blk{position:relative;display:grid;grid-template-columns:8px auto;column-gap:8px;align-items:center;padding:4px 2px}
.blk .blab{grid-column:2;font-size:12px;color:var(--t2);line-height:1;margin-top:4px;text-align:left;transition:color .15s ease-out}
@media (hover:hover){.blk:hover .blab{color:var(--t1)}}
.blk.active::after{content:"";position:absolute;left:0;right:0;bottom:-8px;height:2px;border-radius:1px;background:var(--teal)}
.bdot{width:8px;height:8px;border-radius:50%;grid-row:1}
.bdot.hollow{background:transparent!important;border:1.5px dashed var(--offline)}
.bnum{font-family:"IBM Plex Sans",sans-serif;font-weight:600;font-variant-numeric:tabular-nums;font-size:18px;line-height:1;grid-row:1;text-align:left}
.bdot.alarmdot{position:relative}
.bdot.alarmdot::after{content:"";position:absolute;inset:-5px;border-radius:50%;border:1.5px solid var(--alarm);animation:ring 2.2s ease-out infinite}
.blk.alarmblk .bnum{animation:softpulse 2.2s ease-in-out infinite}
@keyframes softpulse{0%,100%{opacity:1}50%{opacity:.55}}
.clearchip{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--t1);background:rgba(1,154,154,.12);border:1px solid rgba(1,154,154,.45);border-radius:6px;padding:4px 10px;white-space:nowrap}
.clearchip i{font-style:normal;color:var(--t2)}
@media (hover:hover){.clearchip:hover i{color:var(--t1)}}
.stools{margin-left:auto;display:flex;align-items:center;gap:8px}
.pill{display:flex;flex-direction:column;align-items:flex-start;gap:3px;padding:6px 12px;border:1px solid var(--hair);border-radius:8px;background:rgba(13,18,20,.5)}
.pill .prow{display:flex;align-items:center;gap:7px;font-size:12px;color:var(--t1);white-space:nowrap;font-variant-numeric:tabular-nums}
.pill .pdot{width:7px;height:7px;border-radius:50%;background:var(--teal)}
.pill.warn .pdot{background:var(--idle)}
.pill.warn .prow{color:var(--idle)}
.hb{width:24px;height:2px;background:rgba(1,154,154,.22);position:relative;overflow:hidden;border-radius:1px;flex:none}
.hb::after{content:"";position:absolute;inset:0;background:var(--teal);transform:translateX(-110%)}
.hb.beat::after{animation:hbsweep .4s ease-out}
@keyframes hbsweep{0%{transform:translateX(-110%)}55%{transform:translateX(0)}100%{transform:translateX(110%)}}
.sbtn{width:36px;height:36px;display:flex;align-items:center;justify-content:center;border-radius:8px;color:var(--t2);transition:background-color .15s ease-out,color .15s ease-out}
@media (hover:hover){.sbtn:hover{background:var(--raised);color:var(--t1)}}
.sbtn.on{color:var(--teal);background:rgba(1,154,154,.1)}
.seg{display:flex;border:1px solid var(--hair);border-radius:8px;overflow:hidden}
.seg button{font-size:12px;color:var(--t2);padding:8px 12px;transition:background-color .15s ease-out,color .15s ease-out}
@media (hover:hover){.seg button:hover{color:var(--t1)}}
.seg button.on{background:var(--raised);color:var(--t1)}
@media (max-width:1366px){ .blocks{gap:18px} .stools{gap:6px} .strip{gap:10px} }

/* ───── content / vehicle rail ───── */
.content{flex:1;display:flex;min-height:0;position:relative}
.vlist{width:360px;flex:none;background:var(--panel);border-right:1px solid var(--line);display:flex;flex-direction:column;min-height:0;position:relative;transition:width .2s ease-out,opacity .2s ease-out;overflow:hidden}
.railhidden .vlist{width:0;border-right:none;opacity:0;pointer-events:none}
body.focus .vlist{width:0;border-right:none;opacity:0;pointer-events:none}
.vinner{width:360px;display:flex;flex-direction:column;min-height:0;height:100%}
.railhandle{position:absolute;right:0;top:50%;transform:translateY(-50%);width:14px;height:64px;display:flex;align-items:center;justify-content:center;color:var(--t2);z-index:6;border-radius:6px 0 0 6px}
@media (hover:hover){.railhandle:hover{background:var(--raised);color:var(--t1)}}
.searchwrap{padding:12px 12px 0;flex:none;position:relative}
.searchwrap .sdrop{left:12px;right:12px;top:52px}
.searchbox{position:relative}
.searchbox svg{position:absolute;left:12px;top:50%;transform:translateY(-50%);color:var(--t2);pointer-events:none}
.searchbox input{width:100%;height:40px;background:var(--raised);border:1px solid var(--line);border-radius:8px;color:var(--t1);font:14px "IBM Plex Sans",sans-serif;padding:0 40px 0 38px;transition:border-color .15s ease-out}
.searchbox input::placeholder{color:var(--t2)}
.searchbox input:focus{outline:none;border-color:var(--teal);box-shadow:0 0 0 1px var(--teal)}
.kbd{position:absolute;right:10px;top:50%;transform:translateY(-50%);font:500 11px "IBM Plex Sans",sans-serif;color:var(--t2);border:1px solid var(--line);border-radius:5px;padding:2px 7px;background:var(--panel)}
.chiprow{display:flex;align-items:center;gap:4px;padding:10px 10px;flex:none;border-bottom:1px solid var(--line);overflow-x:auto;scrollbar-width:none}
.chiprow::-webkit-scrollbar{display:none}
.chip{display:flex;align-items:center;gap:4px;font-size:12px;color:var(--t2);border:1px solid var(--line);background:var(--panel);border-radius:6px;padding:4px 6px;white-space:nowrap;flex:none;transition:background-color .15s ease-out,color .15s ease-out,border-color .15s ease-out}
@media (hover:hover){.chip:hover{background:var(--raised);color:var(--t1)}}
.chip.active{border-color:var(--teal);color:var(--t1);background:rgba(1,154,154,.12)}
.chip .cdot{width:5px;height:5px;border-radius:50%;flex:none}
.chip .cdot.hollow{background:transparent;border:1px dashed var(--offline)}
.sortbtn{margin-left:auto;flex:none;width:32px;height:30px;display:flex;align-items:center;justify-content:center;border-radius:6px;color:var(--t2)}
@media (hover:hover){.sortbtn:hover{background:var(--raised);color:var(--t1)}}
.list{flex:1;overflow-y:auto;min-height:0;position:relative;padding:0 8px 8px}
.ghead{position:sticky;top:0;z-index:5;display:flex;align-items:center;gap:8px;height:32px;padding:0 6px;background:var(--panel);font-size:12px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:var(--t2);cursor:pointer;user-select:none;width:100%;border:none;margin-top:2px}
.ghead>svg{transition:transform .15s ease-out;flex:none}
.ghead.closed>svg{transform:rotate(-90deg)}
.ghead .gcount{margin-left:auto;font-family:"IBM Plex Sans",sans-serif;font-weight:600;font-variant-numeric:tabular-nums;font-size:11px;letter-spacing:0}
.geye{width:32px;height:28px;display:flex;align-items:center;justify-content:center;border-radius:6px;color:var(--t2);opacity:0;flex:none;transition:opacity .15s ease-out}
@media (hover:hover){.ghead:hover .geye{opacity:1}}
@media (hover:hover){.geye:hover{background:var(--panel);color:var(--t1)}}
.geye.off{opacity:1;color:var(--offline)}
.row{position:relative;display:grid;grid-template-columns:auto minmax(0,1fr) auto;grid-template-rows:auto auto;column-gap:10px;row-gap:3px;align-content:center;height:52px;width:100%;padding:0 12px 0 14px;margin:4px 0;text-align:left;cursor:pointer;user-select:none;background:rgba(232,237,238,.025);border-radius:8px;box-shadow:inset 0 1px 0 rgba(255,255,255,.05),inset 0 -1px 0 rgba(0,0,0,.22);transition:background .15s ease-out}
.row>.dots{grid-row:1/3;grid-column:1;align-self:center}
.row>.pchip{grid-row:1;grid-column:2;align-self:baseline;justify-self:start}
.row>.rspeed{grid-row:1;grid-column:3;justify-self:end;align-self:baseline}
.row>.model{grid-row:2;grid-column:2}
.row>.rage{grid-row:2;grid-column:3;justify-self:end}
.row:focus-visible{background:var(--raised)}@media (hover:hover){.row:hover{background:var(--raised)}}
.row.sel{background:var(--raised)}
.row.sel::before{content:"";position:absolute;left:0;top:8px;bottom:8px;width:3px;border-radius:2px;background:var(--teal)}
.row.isalarm::before{content:"";position:absolute;left:0;top:8px;bottom:8px;width:3px;border-radius:2px;background:var(--alarm)}
.row.hiddenrow{opacity:.5}
.dots{display:flex;align-items:center;gap:5px;flex:none}
.dot{width:8px;height:8px;border-radius:50%;flex:none}
.dot.hollow{background:transparent!important;border:1.5px dashed var(--offline)}
.igndot{width:4px;height:4px;border-radius:50%;flex:none;background:var(--t2)}
.igndot.off{background:transparent;border:1px solid var(--line)}
.rid{flex:1;min-width:0;display:flex;flex-direction:column;align-items:flex-start;gap:3px}
.model{font:450 12px "IBM Plex Sans",sans-serif;color:var(--t2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.2;max-width:100%;display:flex;align-items:center;gap:4px}
.model .vic{display:flex;flex:none;color:var(--t3)}
.ract{position:absolute;right:104px;top:50%;transform:translateY(-50%);display:flex;gap:2px;opacity:0;transition:opacity .15s ease-out}
@media (hover:hover){.row:hover .ract{opacity:1}}
.ract button{width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:6px;color:var(--t2)}
@media (hover:hover){.ract button:hover{background:var(--panel);color:var(--t1)}}
.ract button.eyeoff{color:var(--offline)}
.rmeta{text-align:right;flex:none;min-width:88px}
.rspeed{font-family:"IBM Plex Sans",sans-serif;font-weight:600;font-variant-numeric:tabular-nums;font-size:13px;color:var(--t1);line-height:1.3;white-space:nowrap}
.rspeed i{font-style:normal;font-size:10px;color:var(--t3);letter-spacing:0;margin-left:3px}
.rspeed i.pre{margin:0 3px 0 0}
.rspeed.quiet{color:var(--t2)}
.rage{font-size:11px;color:var(--t3);font-family:"IBM Plex Sans",sans-serif;font-weight:600;font-variant-numeric:tabular-nums;line-height:1.2}
.rage.st-a{color:var(--idle)}
.rage.st-r{color:var(--alarm)}
.sdrop{display:none;position:absolute;top:56px;left:12px;right:12px;z-index:1500;padding:6px;max-height:340px;overflow-y:auto}
.sdrop.open{display:block}
.sgh{font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--t2);padding:8px 8px 3px}
.sitem{display:flex;align-items:center;gap:9px;width:100%;padding:6px 8px;border-radius:6px;font-size:13px;color:var(--t1);text-align:left}
.sitem.cur{background:rgba(29,38,42,.85)}@media (hover:hover){.sitem:hover{background:rgba(29,38,42,.85)}}
.sitem .ssec{color:var(--t2);font-weight:450;font-size:12px;margin-left:auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.sitem mark{background:none;color:var(--teal)}
.snone{padding:12px 10px;font-size:13px;color:var(--t2)}
.emptystate{padding:32px 20px;text-align:center;color:var(--t2);font-size:13px;display:none}
.emptystate button{color:var(--teal);margin-top:6px}
@media (hover:hover){.emptystate button:hover{text-decoration:underline}}
.edgetab{display:none;position:absolute;left:12px;top:50%;transform:translateY(-50%);width:28px;padding:10px 0;flex-direction:column;align-items:center;gap:8px;color:var(--t2);z-index:1150}
.railhidden .edgetab{display:flex}
body.focus .edgetab{display:none}
@media (hover:hover){.edgetab:hover{color:var(--t1)}}
.edgetab .vcount{writing-mode:vertical-rl;font:600 11px "IBM Plex Sans",sans-serif;font-variant-numeric:tabular-nums;letter-spacing:.06em}

/* ───── map ───── */
.maparea{position:relative;flex:1;min-width:0;--floor:16px}
#map{position:absolute;inset:0;background:var(--ink)}
.leaflet-container{font:12px "IBM Plex Sans",sans-serif;background:var(--ink)}
.leaflet-control-attribution{background:rgba(13,18,20,.75)!important;color:#5c6b70!important;font-size:10px!important}
.leaflet-control-attribution a{color:#5c6b70!important}
.vicon,.cicon,.plicon{background:none;border:none}
.mk{position:relative;width:26px;height:26px;display:flex;align-items:center;justify-content:center}
.mk svg{transition:transform .9s ease-out}
body:not(.sat) .mk:not(.mk-alarm) svg{filter:saturate(.9)}
.mk-alarm::before{content:"";position:absolute;inset:-7px;border-radius:50%;border:2px solid rgba(229,72,77,.6);animation:ring 2.4s ease-out infinite;pointer-events:none}
.mk.dotmk.mk-alarm::before{inset:2px}
@keyframes ring{0%{transform:scale(.45);opacity:.9}100%{transform:scale(1.55);opacity:0}}
.vicon.selmk .mk::after{content:"";position:absolute;inset:1px;border:2px solid #fff;border-radius:50%;pointer-events:none}
.vicon.selmk .mk.dotmk::after{inset:6px}
.cluster{width:var(--cs,28px);height:var(--cs,28px);border-radius:50%;background:rgba(13,18,20,.82);backdrop-filter:blur(14px);border:1px solid rgba(232,237,238,.14);box-shadow:inset 0 1px 0 rgba(255,255,255,.06);color:var(--t1);font:600 12px "IBM Plex Sans",sans-serif;display:flex;align-items:center;justify-content:center;font-variant-numeric:tabular-nums;transition:width .12s ease-out,height .12s ease-out,margin .12s ease-out}
@media (hover:hover){.cluster:hover{width:calc(var(--cs,28px) + 4px);height:calc(var(--cs,28px) + 4px);margin:-2px}}
.leaflet-tooltip.vtt{background:rgba(13,18,20,.82);backdrop-filter:blur(14px);border:1px solid var(--hair);color:var(--t1);border-radius:6px;box-shadow:inset 0 1px 0 rgba(255,255,255,.06);padding:4px 8px}
.leaflet-tooltip.vtt::before{display:none}
.vtt .ttplate{display:inline-flex;align-items:center;font-family:"Barlow Semi Condensed",sans-serif;font-size:13px;font-weight:600;letter-spacing:0;text-transform:uppercase;line-height:1;color:#E8EDEE}
.ttplate b{font-weight:600}
.vtt .ttstat{font-size:11px;margin-left:6px}
.leaflet-tooltip.plabel{background:rgba(13,18,20,.82);backdrop-filter:blur(14px);border:1px solid rgba(232,237,238,.16);color:var(--t1);border-radius:4px;box-shadow:inset 0 1px 0 rgba(255,255,255,.06);padding:2px 7px;font:11px "IBM Plex Mono",monospace}
.leaflet-tooltip.plabel::before{display:none}
.leaflet-tooltip.plabel{letter-spacing:0;font-family:"Barlow Semi Condensed",sans-serif;font-weight:600;text-transform:uppercase;font-size:11px;display:flex}
.plabel b{font-weight:600}
.plabel b:last-child{margin-right:0}
.leaflet-tooltip.placelabel{background:none;border:none;box-shadow:none;color:var(--t2);font:11px "IBM Plex Sans",sans-serif;padding:0}
.leaflet-tooltip.placelabel::before{display:none}
.leaflet-tooltip.measlabel{background:rgba(13,18,20,.82);backdrop-filter:blur(14px);border:1px solid var(--hair);color:var(--t1);border-radius:6px;box-shadow:inset 0 1px 0 rgba(255,255,255,.06);padding:3px 8px;font:600 12px "IBM Plex Sans",sans-serif;font-variant-numeric:tabular-nums}
.leaflet-tooltip.measlabel::before{display:none}

.toolstack{position:absolute;right:16px;bottom:calc(var(--floor) + var(--dockh,0px) + 12px);display:flex;flex-direction:column;gap:8px;z-index:1150;transition:bottom .2s ease-out}
.tbtn{width:40px;height:40px;display:flex;align-items:center;justify-content:center;color:var(--t2);transition:color .15s ease-out}
@media (hover:hover){.tbtn:hover{color:var(--t1)}}

.layers{display:none;position:absolute;top:12px;right:16px;width:248px;padding:16px;z-index:1250}
.layers.open{display:block}
.lrow{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:7px 0;font-size:13px;color:var(--t1)}
.lrow.sub{padding-left:14px;font-size:12px;color:var(--t2)}
.sw{position:relative;width:28px;height:16px;border-radius:8px;background:var(--line);flex:none;transition:background .15s ease-out}
.sw::after{content:"";position:absolute;top:2px;left:2px;width:12px;height:12px;border-radius:50%;background:var(--t2);transition:left .15s ease-out,background-color .15s ease-out}
.sw.on{background:var(--teal)}
.sw.on::after{left:14px;background:#04211f}
.lseg{display:flex;gap:2px}
.lseg button{font:600 11px "IBM Plex Sans",sans-serif;font-variant-numeric:tabular-nums;color:var(--t2);padding:3px 7px;border-radius:6px;border:1px solid transparent}
@media (hover:hover){.lseg button:hover{color:var(--t1)}}
.lseg button.on{border-color:rgba(1,154,154,.5);color:var(--teal)}
.lsect{margin-top:6px;padding-top:6px;border-top:1px solid var(--hair)}

/* ───── events drawer ───── */
.events{display:none;position:absolute;left:16px;right:16px;bottom:16px;height:200px;z-index:1160;flex-direction:column;overflow:hidden}
.events.open{display:flex}
.ehead{display:flex;align-items:center;gap:10px;padding:10px 16px 8px;font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--t2)}
.ehead .ex{margin-left:auto;width:26px;height:26px;display:flex;align-items:center;justify-content:center;border-radius:6px;color:var(--t2)}
@media (hover:hover){.ehead .ex:hover{background:var(--raised);color:var(--t1)}}
.erows{flex:1;overflow-y:auto;min-height:0}
.erow{display:grid;grid-template-columns:64px 10px 1fr 112px 140px;gap:12px;align-items:center;width:100%;padding:5px 16px;font-size:13px;text-align:left;transition:background .15s ease-out}
.edot{width:8px;height:8px;border-radius:50%}
@media (hover:hover){.erow:hover{background:rgba(29,38,42,.7)}}
.etime{font:600 11px "IBM Plex Sans",sans-serif;font-variant-numeric:tabular-nums;color:var(--t3)}
.eplate{justify-self:start}
.eclient{font-weight:450;font-size:12px;color:var(--t2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.ename.crit{color:var(--alarm)}

/* ───── bottom dock ───── */
.dock{position:absolute;left:50%;transform:translateX(-50%);bottom:var(--floor);width:min(1120px,calc(100% - 32px));min-width:640px;height:52px;z-index:1200;transition:height .2s ease-out,bottom .2s ease-out,opacity .15s ease-out;overflow:hidden}
.dock[data-tier="2"]{height:140px}
.dock[data-tier="3"]{height:184px}
.dock.hiddendock{display:none}
body.focus .dock{display:none}
.dwrap{height:100%;display:flex;flex-direction:column;transition:opacity .15s ease-out}
.dwrap.fade{opacity:0}
.ro{display:flex;flex-direction:column;align-items:flex-start;gap:3px;white-space:nowrap}
.rl{font-size:10px;letter-spacing:.08em;text-transform:uppercase;color:var(--t2);line-height:1}
.rv{font-family:"IBM Plex Sans",sans-serif;font-weight:600;font-variant-numeric:tabular-nums;font-size:16px;line-height:1;color:var(--t1)}
.rv i{font-style:normal;font-size:11px;color:var(--t2);margin-left:4px}
.rv.xl{font-size:22px}
.rv.sm{font-size:14px}
.rv.red{color:var(--alarm)}
.rv.red i{color:var(--alarm);opacity:.95}
.rv.flash{animation:tflash .15s ease-out}
@keyframes tflash{0%,60%{color:var(--teal)}100%{}}
.rv.red.flash{animation:none}
.rv.sec{color:var(--t2)}
.rv .tt{transform:translateY(-2px)}
.devbtn{display:inline-flex;align-items:center;gap:4px}
.sharepop{width:248px;padding:12px}
.sharepop .shlink{font:500 12px "IBM Plex Sans",sans-serif;color:var(--t1);padding:8px 10px;border:1px solid var(--hair);border-radius:6px;background:rgba(232,237,238,.04);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.sharepop .shrow{display:flex;justify-content:space-between;align-items:center;margin-top:10px;font-size:12px;color:var(--t2);padding:0 2px}
.sharepop .shseg{display:flex;gap:2px}
.sharepop .shseg button{font:600 11px "IBM Plex Sans",sans-serif;font-variant-numeric:tabular-nums;color:var(--t2);padding:3px 7px;border-radius:6px;border:1px solid transparent}
.sharepop .shseg button.on{border-color:rgba(1,154,154,.5);color:var(--teal)}
.sharepop .shcopy{margin-top:10px;width:100%;height:30px;border-radius:8px;background:var(--teal);color:#04211f;font-size:12.5px;font-weight:500}
@media (hover:hover){.sharepop .shcopy:hover{background:#02aeae}}
.sigbars{display:inline-flex;align-items:flex-end;gap:2px;height:13px;margin-right:3px}
.sigbars i{width:3px;background:var(--line);border-radius:1px}
.sigbars i:nth-child(1){height:4px}.sigbars i:nth-child(2){height:7px}.sigbars i:nth-child(3){height:10px}.sigbars i:nth-child(4){height:13px}
.sigbars i.f{background:var(--t1)}
.dbtn{height:32px;padding:0 11px;border-radius:8px;font-size:12.5px;font-weight:500;color:var(--t1);white-space:nowrap;background:rgba(29,38,42,.55);border:1px solid var(--hair);transition:background .15s ease-out}
@media (hover:hover){.dbtn:hover{background:rgba(29,38,42,.85)}}
.dbtn.primary{background:var(--teal);color:#04211f}
@media (hover:hover){.dbtn.primary:hover{background:#02aeae}}
.dbtn.primary.on{background:#016d6d;color:#c9f5f2}
.dctl{width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:8px;color:var(--t2);font-size:14px;transition:background-color .15s ease-out,color .15s ease-out}
@media (hover:hover){.dctl:hover{background:rgba(29,38,42,.85);color:var(--t1)}}
.t1{height:52px;display:none;align-items:center;gap:12px;padding:0 16px}
.dock[data-tier="1"] .t1{display:flex}
.t1mid{flex:1;display:flex;align-items:center;gap:clamp(8px,2vw,32px);min-width:0;overflow:hidden;padding-left:6px}
.t1acts{flex:none;display:flex;align-items:center;gap:6px}
.t2{display:none;flex:1;flex-direction:column;padding:6px 20px 0;min-height:0}
.dock[data-tier="2"] .t2,.dock[data-tier="3"] .t2{display:flex}
.rowA{height:44px;flex:none;display:flex;align-items:center;justify-content:space-between;gap:24px}
.rowA .ida{display:flex;align-items:center;gap:10px;min-width:0}
.rowB{height:56px;flex:none;display:flex;align-items:center;justify-content:space-between;gap:8px;min-width:0}
.rowB .ro{flex:none}
.dactions{flex:none;display:flex;align-items:center;gap:8px}
.rowC{height:28px;flex:none;display:flex;align-items:center;gap:12px;min-width:0}
.rowC .meta{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font:450 12px "IBM Plex Sans",sans-serif;color:var(--t2)}
.rowC .meta b{font-weight:500}
.rowC .meta i{font-style:normal;color:var(--t3);margin:0 5px}
.devbtn{flex:none;color:var(--t2);font-size:12px;padding:2px 6px;border-radius:6px;white-space:nowrap}
@media (hover:hover){.devbtn:hover{color:var(--t1);background:rgba(29,38,42,.85)}}
.t3{display:none;align-items:center;gap:10px;padding:0 16px;height:40px;margin:0 0 4px;border-top:1px solid var(--hair);font-size:11.5px;color:var(--t2);white-space:nowrap;overflow:hidden}
.dock[data-tier="3"] .t3{display:flex}
.t3>span{flex:none;display:inline-flex;align-items:center;gap:4px;white-space:nowrap}
.t3>span.fw{flex:0 3 auto;min-width:24px;display:block;overflow:hidden;text-overflow:ellipsis}
.t3>span.din{flex:0 2 auto;min-width:90px;display:block;overflow:hidden;text-overflow:ellipsis}
.t3 .m{font-family:"IBM Plex Mono",monospace;color:var(--t1);font-size:11px}
.t3 .cpy{color:var(--t2);font-size:10.5px;padding:1px 4px;border-radius:5px}
@media (hover:hover){.t3 .cpy:hover{color:var(--teal);background:rgba(29,38,42,.85)}}

/* menus */
.pop{position:absolute;padding:4px;z-index:1400;min-width:150px}
.pop button{display:block;width:100%;text-align:left;padding:8px 12px;border-radius:6px;font-size:13px;color:var(--t1);white-space:nowrap}
@media (hover:hover){.pop button:hover{background:rgba(1,154,154,.12)}}
.pop button.on{color:var(--teal)}
.ctxmenu{position:fixed;display:none;padding:4px;z-index:2000;min-width:170px}
.ctxmenu button{display:block;width:100%;text-align:left;padding:8px 12px;border-radius:6px;font-size:13px;color:var(--t1)}
@media (hover:hover){.ctxmenu button:hover{background:rgba(1,154,154,.12)}}

@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{animation:none!important;transition:none!important}
}

/* Plate segmentation — design master §A4.1: separate spans, fixed gaps, never run-together.
   Canon (Pass 9 D): plates are weight 500, not 600 — the .pchip rule above is the one place
   the weight lives; every chip size and every page (livemap.html carries a byte-identical
   copy because it links no stylesheet) inherits it. */
.pchip{gap:4px}
.pchip.lg{gap:5px}
.pchip.sm{gap:3px}
/* The plate step-down (Pass 4c L2): a four-segment plate with a long tail can want a few
   pixels more than a fixed cell allows. A plate is an identity claim — it must never lose a
   character — so fitPlates() drops it to .xs before anything clips; .clip is the last resort
   for a length no real SA registration reaches. Both come AFTER the size classes so they win. */
.pchip.xs{padding:0 6px;font-size:11px;gap:2px}
.pchip.clip{overflow:hidden;text-overflow:ellipsis}
.pbare{gap:3px}
.vtt .ttplate{gap:3px}
.leaflet-tooltip.plabel{gap:3px}

/* Alarm-row plate chip (design §C2: 26px chip, 16px lettering, 10px padding) */
.pchip.md{height:26px;padding:0 10px;font-size:16px;gap:4px}

/* ───── map pins — the canon (Pass 9b, Item B) ─────
   One look on every map: History, the Live Map's History tab, whoever renders a stop next.
   Stop: a 10px --panel disc in a 1.5px teal ring, its duration in Plex Mono 11px --t2 to
   the right — always for a long stop, on hover for a short one (.hoverlbl); the highlighted
   stop fills teal (.sel). Parked cluster: 14px, the same ring, "Parked 6× · 42h". Alarm: a
   red ring around a red dot, never clustered, at every zoom. Event: a 6px --t3 dot, hover
   only. Start/End: the only two flags on the map, teal and white, a hairline drop shadow.
   (The order names the disc --bg1 and the trail casing --bg0: this theme's --panel and
   --ink, one and two steps up from black.) */
.stoppin{position:relative;width:10px;height:10px;border-radius:50%;background:var(--panel);border:1.5px solid var(--teal);box-shadow:0 0 0 1px rgba(13,18,20,.6);cursor:pointer}
.stoppin.sel{background:var(--teal)}
.stoppin .sd,.stopcluster .sd{position:absolute;left:100%;top:50%;transform:translateY(-50%);margin-left:6px;font:11px "IBM Plex Mono",monospace;color:var(--t2);white-space:nowrap;pointer-events:none;text-shadow:0 1px 2px rgba(13,18,20,.95),0 0 3px rgba(13,18,20,.85)}
.stoppin.hoverlbl .sd{display:none}
@media (hover:hover){.stoppin.hoverlbl:hover .sd{display:block}}
@media (hover:hover){.stoppin:hover,.stopcluster:hover{border-color:#26b3b3}}
.stopcluster{position:relative;width:14px;height:14px;border-radius:50%;background:var(--panel);border:1.5px solid var(--teal);box-shadow:0 0 0 1px rgba(13,18,20,.6),inset 0 0 0 2.5px rgba(1,154,154,.38);cursor:pointer}
.alarmpin{width:14px;height:14px;border-radius:50%;background:var(--panel);border:1.5px solid var(--alarm);box-shadow:0 0 0 1px rgba(13,18,20,.6);display:flex;align-items:center;justify-content:center;cursor:pointer}
.alarmpin::after{content:"";width:6px;height:6px;border-radius:50%;background:var(--alarm)}
.evdot{width:6px;height:6px;border-radius:50%;background:var(--t3);box-shadow:0 0 0 1px rgba(13,18,20,.5)}
.flagpin{display:block;width:14px;height:17px;filter:drop-shadow(0 1px 0 rgba(13,18,20,.9))}
.flagpin svg{display:block}

/* ══════ Pass M2a — TOUCH FEEL ══════════════════════════════════════════════════════════════
   Troy on an iPhone after M1: "every page and every action are laggy and don't feel right".
   Measured (OVERNIGHT-REPORT-20260925-M2a.txt): not JavaScript — the page reacting to a finger
   the way it reacts to a mouse. Every :hover rule now sits inside @media (hover: hover) where it
   was written (item 1a, every file); what follows is what a COARSE pointer gets instead. Every
   rule here is keyed on device.js's data-pointer (or, for scroll containment, data-layout), so a
   desktop with a mouse renders exactly what it rendered before, to the pixel. */

/* 1e — iOS inflates text in landscape unless told not to. */
html{-webkit-text-size-adjust:100%;text-size-adjust:100%}

/* 1b — A PRESSED STATE that shows within a frame. The iOS convention: a dim while the finger is
   down, no transition on the way in, gone the moment it lifts. The grey tap flash is off
   everywhere (the property inherits), text on controls is not selectable and a long-press does
   not open the callout — never on fields, never on rows and cards, whose plates and addresses a
   controller copies. touch-action:manipulation drops the double-tap-zoom wait on every control. */
:root[data-pointer=coarse]{-webkit-tap-highlight-color:transparent}
:root[data-pointer=coarse] :is(button,[role=button],a[href],a.tab,.nitem,.chip,.sbtn,.seg button,.dctl,.btn,.row,.card,.tab,.stban button){touch-action:manipulation}
:root[data-pointer=coarse] :is(button,[role=button],a.tab,.nitem,.chip,.sbtn,.seg button,.dctl,.btn,.tab,.stban button):not(.row):not(.card){-webkit-user-select:none;user-select:none;-webkit-touch-callout:none}
:root[data-pointer=coarse] :is(button,[role=button],a[href],a.tab,.nitem,.chip,.sbtn,.seg button,.dctl,.btn,.row,.card,.tab,.stban button):active{opacity:.7;transition:none}

/* 1d — SCROLL CONTAINMENT at the phone and tablet layouts. The document never scrolls there
   (every page scrolls an inner container), so the root and body refuse to rubber-band the whole
   app, and each shell scroller keeps its overscroll to itself: a list that reaches its end stops.
   On the root as well as body, because the viewport takes the ROOT's value. */
:is([data-layout=phone],[data-layout=tablet]),:is([data-layout=phone],[data-layout=tablet]) body{overscroll-behavior:none}
:is([data-layout=phone],[data-layout=tablet]) :is(.main,.page,.list,.erows,.navitems,.navsheet,.strip){overscroll-behavior:contain}

/* 3 — NO BLUR ON A TOUCH SCREEN. A backdrop-filter re-blurs everything under it on every frame
   the content beneath moves; on a phone that is the map, the list, the page. The same dark panel
   at 96 % instead of 82 % keeps the look without the blur. A design decision for touch devices,
   not a measured claim — the Chromium probe cannot see iOS's compositor. Desktop keeps the blur. */
:root[data-pointer=coarse] :is(.strip,.glass,.cluster,.leaflet-tooltip.vtt,.leaflet-tooltip.plabel,.leaflet-tooltip.measlabel,.stban,.sttoast,.stmore,.staudio.floating,.stsndpanel){backdrop-filter:none!important;-webkit-backdrop-filter:none!important;background-color:rgba(13,18,20,.96)}
