:root{
  --bg:#0b0d10; --bg2:#12151a; --bg3:#181c23; --line:#252a33; --line2:#333a46;
  --fg:#e8ecf3; --dim:#98a2b3; --dim2:#6b7483; --accent:#4c9aff;
  --ink:#fff;                     /* full-contrast marks: station dots, selected chips */
  --knob:#2b3242;
  --hover:rgba(255,255,255,.05);
  --shadow:rgba(0,0,0,.55);
  --rc:#6d6e71; --rt:#fff;
  --live:#3ecf8e; --livedim:rgba(62,207,142,.5); --late:#f0a23c;
  --live-line:#24413a; --live-head:rgba(62,207,142,.07);
  --late-chip:rgba(240,162,60,.16); --early-chip:rgba(76,154,255,.16);
  --live-chip:rgba(62,207,142,.16);
  --mono:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;
  --sans:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;
}
:root[data-theme="light"]{
  --bg:#f5f6f8; --bg2:#fff; --bg3:#eceef2; --line:#e0e3e9; --line2:#c7cdd7;
  --fg:#11151c; --dim:#5a6472; --dim2:#8b93a0; --accent:#0b5fd0;
  --ink:#11151c; --hover:rgba(0,0,0,.045); --shadow:rgba(0,0,0,.18); --knob:#fff;
  --live:#0a8f56; --livedim:rgba(10,143,86,.45); --late:#a8620a;
  --live-line:#bfe3d2; --live-head:rgba(10,143,86,.06);
  --late-chip:rgba(168,98,10,.14); --early-chip:rgba(11,95,208,.13);
  --live-chip:rgba(10,143,86,.13);
}
html{color-scheme:dark}
:root[data-theme="light"]{color-scheme:light}
*{box-sizing:border-box}
html,body{height:100%}
body{margin:0;background:var(--bg);color:var(--fg);font:14px/1.45 var(--sans);overflow:hidden}
h1,h2,h3{margin:0;font-weight:650}
button{font:inherit;color:inherit;background:none;border:0;padding:0;cursor:pointer}

/* ---------- header ---------- */
#top{display:flex;align-items:center;justify-content:space-between;gap:16px;
  padding:10px 16px;border-bottom:1px solid var(--line);background:var(--bg2);height:58px}
.brand{display:flex;align-items:center;gap:12px}
.brand-mark{display:block;flex:none}
#top h1{font-size:15px;letter-spacing:.2px}
#feedinfo{margin:0;font-size:11px;color:var(--dim2);font-family:var(--mono);
  max-width:44ch;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
@media (max-width:900px){#feedinfo{display:none}}
.topcontrols{display:flex;align-items:center;gap:10px}
.seg{display:flex;background:var(--bg3);border:1px solid var(--line);border-radius:8px;padding:2px;gap:2px}
.seg button{padding:5px 12px;border-radius:6px;font-size:12.5px;color:var(--dim);white-space:nowrap}
.seg button:hover{color:var(--fg)}
.seg button.on{background:var(--line2);color:var(--fg)}
.ghost{border:1px solid var(--line);border-radius:8px;padding:6px 10px;font-size:12px;
  color:var(--dim);font-family:var(--mono)}
.ghost:hover{color:var(--fg);border-color:var(--line2)}

/* ---------- shell ---------- */
#shell{display:flex;height:calc(100% - 58px)}
#lines{width:130px;flex:none;border-right:1px solid var(--line);background:var(--bg2);
  padding:12px 10px;overflow-y:auto;display:flex;flex-direction:column}
#lines h2,#side h2{font-size:10.5px;text-transform:uppercase;letter-spacing:.12em;color:var(--dim2);margin-bottom:8px}
#lineGrid{display:grid;grid-template-columns:repeat(3,32px);gap:7px;justify-content:center}
.linebtn{border-radius:50%;line-height:0;transition:transform .09s ease}
.linebtn:hover{transform:scale(1.12)}
.linebtn{opacity:.95}
#lineGrid.picked .linebtn{opacity:.8}
#lineGrid.picked .linebtn:hover{opacity:.95}
#lineGrid.picked .linebtn.on{opacity:1;transform:scale(1.06)}
.bul{display:block;flex:none;vertical-align:middle}
.conn .bul,.jn .bul{vertical-align:-4px}
.railfoot{margin-top:auto;padding-top:14px;font-size:10.5px;color:var(--dim2);font-family:var(--mono);line-height:1.6}

#side{width:264px;flex:none;border-right:1px solid var(--line);background:var(--bg2);
  overflow-y:auto;padding:14px 12px}
.hint{color:var(--dim2);font-size:12.5px}
.dirbtn{display:block;width:100%;text-align:left;padding:9px 10px;border-radius:8px;
  border:1px solid var(--line);background:var(--bg3);margin-bottom:6px}
.dirbtn:hover{border-color:var(--line2)}
.dirbtn.on{border-color:var(--rc);background:color-mix(in srgb,var(--rc) 22%,var(--bg3))}
.dirbtn .d1{font-size:12.5px;font-weight:650}
.dirbtn .d2{font-size:11px;color:var(--dim);margin-top:1px}
.patbtn{display:flex;gap:8px;align-items:flex-start;width:100%;text-align:left;padding:8px 9px;
  border-radius:8px;border:1px solid transparent;margin-bottom:3px}
.patbtn:hover{background:var(--bg3)}
.patbtn.on{background:var(--bg3);border-color:var(--rc)}
.patbtn .tick{width:3px;align-self:stretch;border-radius:2px;background:var(--rc);opacity:.35}
.patbtn.on .tick{opacity:1}
/* a group only some of whose patterns are selected — ⇧-clicking one of its
   members in detailed view and switching back gets you here */
.patbtn.part .tick{opacity:.6}
.patbtn.part{border-style:dashed}
.patbtn .p1{font-size:12.5px;font-weight:600;line-height:1.25}
.patbtn .p2{font-size:10.5px;color:var(--dim2);font-family:var(--mono);margin-top:2px}
/* Every section title sits the same distance above what it labels. It did not:
   the bare <h2> at the top of the panel carried its own 8px, while a title
   inside .sect carried that *and* the wrapper's, so the chip rows under
   "Origin" and "Destination" sat twice as far down as the buttons under
   "Direction" — and the h2's stray bottom margin pushed everything beside it in
   the flex row off centre with it. The gap belongs to the wrapper alone. */
.sect{margin:16px 0 8px}
.sect h2{margin-bottom:0}

/* ---------- view settings (the gear) ---------- */
.menuwrap{position:relative}
.gearbtn{display:flex;align-items:center;padding:6px 8px}
/* no chrome on this one: it sits outside the menu and reads as a glyph, not a button */
.iconbtn{display:flex;align-items:center;padding:6px 4px;color:var(--dim2)}
.iconbtn:hover{color:var(--fg)}
.menu{position:absolute;right:0;top:calc(100% + 8px);z-index:60;width:268px;
  background:var(--bg2);border:1px solid var(--line);border-radius:10px;padding:10px;
  box-shadow:0 14px 34px var(--shadow)}
.menu.hidden{display:none}
.menusep{border-top:1px solid var(--line);margin:9px -10px 9px}
.menuh{font-size:10.5px;text-transform:uppercase;letter-spacing:.12em;color:var(--dim2)}
.menuh + .menuopt,.menuh + .seg{margin-top:6px}
.seg.segsm button{flex:1;text-align:center;padding:5px 0;font-size:12px}
.menup{margin:5px 0 8px;font-size:11.5px;color:var(--dim);line-height:1.4}
.menuopt{display:flex;align-items:center;gap:8px;width:100%;text-align:left;
  padding:7px 9px;border-radius:8px;border:1px solid transparent}
.menuopt:hover{background:var(--bg3)}
.menuopt.on{background:var(--bg3);border-color:var(--rc)}
.menuopt .mo1{font-size:12.5px;font-weight:600}
.menuopt .mo2{font-size:10.5px;color:var(--dim2);margin-top:1px}
.menuopt .motick{margin-left:auto;color:var(--rc);font-size:12px;opacity:0}
.menuopt.on .motick{opacity:1}
.menuact .moic{width:16px;text-align:center;color:var(--dim2);font-size:13px}
.menuact:hover .moic{color:var(--fg)}
.menufoot{margin:8px 2px 0;font-size:10px;font-family:var(--mono);color:var(--dim2)}

/* ---------- main ---------- */
#main{flex:1;min-width:0;overflow-y:auto;padding:16px 20px 60px;position:relative}
#routeHead{margin-bottom:14px}
.rh{display:flex;gap:14px;align-items:center}
.rh h2{font-size:19px;line-height:1.18}
/* The branch name IS the bullet here, so it gets the presence a bullet would have had:
   one box in the branch colour, the name sitting on it. Text colour is picked by
   luminance the same way a chip's is, so it works over a Hempstead gold or a Far
   Rockaway brown without a second rule. */
.rh h2.rname{display:inline-block;font-size:23px;line-height:1.2;letter-spacing:-.005em;
  padding:5px 13px;border-radius:7px}
.rh .sub{color:var(--dim);font-size:12.5px;margin-top:0;line-height:1.35}
.stats{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}
.stat{background:var(--bg2);border:1px solid var(--line);border-radius:9px;padding:7px 11px;min-width:82px}
.stat b{display:block;font-size:15px;font-family:var(--mono);font-weight:600}
.stat span{font-size:10px;color:var(--dim2);text-transform:uppercase;letter-spacing:.08em}
.views{display:inline-flex;margin:14px 0 10px}
#filters{margin-bottom:12px;display:flex;gap:8px;flex-wrap:wrap;align-items:center}
/* Chips are pills of wildly different widths — a station name and a count —
   so they wrap a lot, and 4px made the wrapped rows read as one block of text
   rather than as rows of separate things. */
.chipbar{display:flex;gap:6px;flex-wrap:wrap}
.fchip{padding:4px 10px;border-radius:999px;border:1px solid var(--line);font-size:11.5px;
  color:var(--dim)}
.fchip:hover{color:var(--fg)}
.fchip.on{background:var(--rc);border-color:var(--rc);color:var(--rt)}
.empty{display:grid;place-items:center;height:50vh;color:var(--dim2);text-align:center}
.empty .big{font-size:40px;display:block;margin-bottom:8px}

/* ---------- station list ---------- */
.stationlist{border-left:0}
.st{display:grid;grid-template-columns:34px 1fr;gap:0;position:relative}
.strip{position:relative;width:34px}
.strip::before{content:"";position:absolute;left:15px;top:0;bottom:0;width:5px;background:var(--rc);opacity:.85}
.st:first-child .strip::before{top:20px}
.st:last-child .strip::before{bottom:calc(100% - 20px)}
.dot{position:absolute;left:9px;top:12px;width:17px;height:17px;border-radius:50%;
  background:var(--bg);border:4px solid var(--ink);z-index:2}
.st.skip .dot{border-color:var(--dim2);width:11px;height:11px;left:12px;top:15px;border-width:3px}
.stbody{padding:10px 0 12px 10px;border-bottom:1px solid var(--line);min-width:0}
.st:last-child .stbody{border-bottom:0}
.sthead{display:flex;align-items:center;gap:10px;flex-wrap:wrap;min-height:20px}
.stname{font-size:14px;font-weight:650}
.st.skip .stname{color:var(--dim)}
.conn{display:flex;gap:4px;align-items:center;flex-wrap:wrap}
.conn.xfer{margin-left:0}
/* the ↔ sits 10px from the bullets on both sides: 10px flex gap on its left,
   4px .conn gap + 6px margin on its right */
.conn .x{color:var(--dim2);font-size:11px;margin-right:6px;line-height:1}
.stmeta{margin-left:auto;font-family:var(--mono);font-size:10.5px;color:var(--dim2);
  display:flex;gap:10px;white-space:nowrap}
.stmeta b{color:var(--dim);font-weight:600}
.times{display:flex;flex-wrap:wrap;gap:3px;margin-top:7px}
.t{font-family:var(--mono);font-size:11px;padding:2px 5px;border-radius:4px;background:var(--bg2);
  border:1px solid var(--line);color:var(--dim);cursor:pointer;white-space:nowrap}
.t:hover{border-color:var(--ink);color:var(--ink)}
.t.rush{background:var(--bg3);color:var(--fg)}
.t.on{background:var(--ink);color:var(--bg);border-color:var(--ink);font-weight:700}
.nx{color:#f0a;font-size:.7em;vertical-align:super;line-height:0;font-weight:700}
.t.on .nx{color:#a0006e}
.skipnote{margin-top:6px;font-size:11px;color:var(--dim2);font-style:italic}
.more{font-size:11px;color:var(--accent);padding:2px 6px}

/* ---------- timetable ---------- */
.ttwrap{overflow:auto;max-height:calc(100vh - 260px);border:1px solid var(--line);border-radius:10px}
table.tt{border-collapse:separate;border-spacing:0;font-family:var(--mono);font-size:11px}
table.tt th,table.tt td{padding:3px 7px;white-space:nowrap;border-bottom:1px solid var(--line)}
table.tt thead th{position:sticky;top:0;background:var(--bg3);z-index:3;font-weight:600;
  color:var(--dim);border-bottom:1px solid var(--line2);cursor:pointer}
table.tt thead th:hover{color:#fff}
/* The cell is sticky but TRANSPARENT: only the name block is opaque, so a short
   name masks only its own width and the timetable keeps running right beside it
   — no column-wide dead space padded out to the longest station name. */
table.tt tbody th.stn{position:sticky;left:0;background:transparent;z-index:2;text-align:left;
  padding:2px 8px 2px 6px;border-right:0}
table.tt thead th.stn{position:sticky;left:0;background:var(--bg3);z-index:4}
/* the visible block is the name itself, sized to the name */
.stnbox{display:inline-block;max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  font-family:var(--sans);font-size:12px;font-weight:600;background:var(--bg2);
  border-radius:7px;padding:3px 9px}
table.tt thead th.stn{z-index:4}
/* Left, not right. Every time here is exactly HH:MM, so for a plain cell the two
   are indistinguishable — but a cell also carrying a ⁺¹ or a delay chip is a
   different width, and right-alignment pays for that out of the time's own
   position, shoving it out of line with the column it belongs to. Aligned left,
   every time in a column starts on the same pixel and the annotations trail off
   the end where they belong. */
table.tt td{color:var(--dim);text-align:left}
table.tt thead th:not(.stn){text-align:left}
table.tt td.no{color:var(--line2)}
/* Nothing lights up under the cursor here. A station is a thing you read, and
   the row band, the name block and the map dot were three different ways of
   saying "you are pointing at this", which is something the pointer already
   says. The cursor stays a pointer where a click does something. */
table.hm tr:hover th.stn{background:var(--bg)}
table.tt col.on,table.tt td.on{background:color-mix(in srgb,var(--rc) 30%,transparent);color:var(--fg);font-weight:600}
table.tt th.on{background:var(--rc);color:var(--rt)}

/* ---------- frequency ---------- */
.freqgrid{overflow:auto;border:1px solid var(--line);border-radius:10px;max-height:calc(100vh - 300px)}
table.hm{border-collapse:separate;border-spacing:0;font-family:var(--mono);font-size:10.5px;width:100%}
table.hm th.stn{position:sticky;left:0;background:var(--bg);text-align:left;
  padding:2px 10px 2px 6px;z-index:2;white-space:nowrap}
table.hm thead th{position:sticky;top:0;background:var(--bg3);color:var(--dim2);padding:3px 0;z-index:3;font-weight:600}
table.hm thead th.stn{z-index:4}
table.hm td{text-align:center;padding:0;height:20px;font-weight:700}
/* Two label bands live outside the bars — the count 14px above its bar, the hour 16px
   below the baseline — so both need to be gutters of the chart's own box. At 10px of
   padding the tallest bar's count broke out through the top border. Height grows with
   the gutters so the plot area itself stays ~100px. */
.bars{display:flex;align-items:flex-end;gap:3px;height:146px;padding:24px 8px 20px;background:var(--bg2);
  border:1px solid var(--line);border-radius:10px}
.bars .b{flex:1;background:var(--rc);border-radius:3px 3px 0 0;min-height:2px;position:relative}
.bars .b span{position:absolute;bottom:-16px;left:0;right:0;text-align:center;font-size:9px;color:var(--dim2);font-family:var(--mono)}
.bars .b b{position:absolute;top:-14px;left:0;right:0;text-align:center;font-size:9px;color:var(--dim);font-family:var(--mono)}
.barwrap{margin-bottom:16px}   /* the hour labels are inside the chart now */

/* ---------- map ---------- */
.mapwrap{background:var(--bg2);border:1px solid var(--line);border-radius:12px;padding:8px;position:relative}
.mapwrap svg{width:100%;height:auto;display:block}
.mapstn{fill:var(--bg);stroke:var(--ink);stroke-width:2.2;cursor:pointer}
.maplbl{fill:var(--dim);font-size:5.2px;font-family:var(--sans);pointer-events:none}
.train{fill:#ffd400;stroke:#000;stroke-width:.8}
.slider{display:flex;align-items:center;gap:12px;margin:12px 0 4px}
.slider input{flex:1;accent-color:var(--rc)}
.slider .now{font-family:var(--mono);font-size:15px;min-width:76px}

/* ---------- drawer ---------- */
#drawer{width:340px;flex:none;border-left:1px solid var(--line);background:var(--bg2);
  overflow-y:auto;padding:16px 14px}
#drawer.hidden{display:none}
.dhead{display:flex;gap:10px;align-items:center;margin-bottom:10px}
.tripid{font-family:var(--mono);font-size:10px;color:var(--dim2);word-break:break-all;margin-top:6px}
.journey{margin-top:12px}
.jr{display:grid;grid-template-columns:52px 14px 1fr;align-items:center;gap:6px;padding:3px 0;position:relative}
.jr .jt{font-family:var(--mono);font-size:11.5px;color:var(--dim);text-align:right}
.jr.first .jt,.jr.last .jt{color:var(--fg);font-weight:600}
.jr .jl{position:relative;height:100%;min-height:22px}
.jr .jl::before{content:"";position:absolute;left:5px;top:0;bottom:0;width:4px;background:var(--rc)}
.jr.first .jl::before{top:50%}
.jr.last .jl::before{bottom:50%}
.jr .jd{position:absolute;left:2px;top:50%;transform:translateY(-50%);width:10px;height:10px;
  border-radius:50%;background:var(--bg2);border:3px solid var(--ink)}
.jr .jn{font-size:12px;display:flex;align-items:center;gap:6px;flex-wrap:wrap}
.jr .el{font-family:var(--mono);font-size:9.5px;color:var(--dim2)}
.navbtns{display:flex;gap:6px;margin-top:10px}
.navbtns button{flex:1;border:1px solid var(--line);border-radius:7px;padding:6px;font-size:11.5px;color:var(--dim)}
.navbtns button:hover{color:var(--fg);border-color:var(--line2)}
.kv{display:flex;justify-content:space-between;font-size:11.5px;padding:3px 0;border-bottom:1px solid var(--line)}
.kv span{color:var(--dim2)}
.kv b{font-family:var(--mono);font-weight:600}
.loading{color:var(--dim2);padding:20px;font-family:var(--mono);font-size:12px}

/* ---------- narrow windows ---------- */
@media (max-width:1100px){
  #side{width:210px}
  #drawer{position:fixed;right:0;top:58px;bottom:0;z-index:30;box-shadow:-16px 0 40px var(--shadow)}
}
@media (max-width:820px){
  #lines{width:118px}
  #side{width:180px}
  #main{padding:12px}
  .stmeta{margin-left:0;width:100%;order:9}
}

/* branch labelling */
.patbtn .p1 .orig{color:var(--dim);font-weight:500}
.patbtn .p1 .arr{color:var(--dim2)}
.legend{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:12px;
  background:var(--bg2);border:1px solid var(--line);border-radius:9px;padding:8px 11px}
.legend .lgl{font-size:11.5px;color:var(--dim2)}
.legend .lgi{font-size:11.5px;font-weight:600;padding:1px 0 1px 8px;border-left:4px solid var(--dim2)}

/* express variants: one bullet in the key, ◆ marker on the express trips */
.linebtn{position:relative}
.patbtn .p1{display:flex;align-items:center;gap:5px;flex-wrap:wrap}
.pbul{flex:none}
.t.exp{border-color:var(--line2)}
.t.exp::after{content:"◆";font-size:7px;margin-left:3px;vertical-align:1px;opacity:.85}
.t.exp.on::after{opacity:1}
table.tt thead th.exp::after{content:" ◆";font-size:7px}
.legend .t{pointer-events:none}

/* station view */
/* Keyboard focus only. The same treatment used to be on :hover as well, and it
   is the one place it still earns its keep: tabbing needs to show where it has
   got to, where a mouse pointer is already sitting on the answer. */
.stlink{cursor:pointer;text-align:left;color:inherit;
  transform-origin:center;transition:color .2s ease,transform .2s ease}
.stlink:focus-visible{color:var(--ink);transform:scale(1.02);outline:none}
.stnav{margin-bottom:10px}
.stnav .ghost{display:inline-flex;align-items:center;gap:5px}
.stlines{gap:5px;margin-top:8px}
.stlines .linebtn{padding:0}
.linesum{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:14px}
.lsrow{display:flex;align-items:center;gap:8px;background:var(--bg2);border:1px solid var(--line);
  border-radius:9px;padding:6px 11px}
.lsn{font-size:12px;font-weight:650}
.lsm{font-family:var(--mono);font-size:10.5px;color:var(--dim2)}
/* ---------- live trains (rt.js) ----------
   Green is the site's only "this is happening now" colour and nothing else
   uses it, so a live time never reads as one more schedule figure. */
.livepanel:empty{display:none}
.livepanel{margin-bottom:14px}
.livehead{display:flex;align-items:baseline;gap:10px;margin-bottom:8px}
.livehead h3{font-size:12.5px;display:flex;align-items:center;gap:7px}
.livehead h3::before{content:"";width:7px;height:7px;border-radius:50%;background:var(--live);
  box-shadow:0 0 0 0 var(--livedim);animation:livepulse 2.4s ease-out infinite}
@keyframes livepulse{70%{box-shadow:0 0 0 7px transparent}100%{box-shadow:0 0 0 0 transparent}}
@media (prefers-reduced-motion:reduce){.livehead h3::before{animation:none}}
.livestamp{font-family:var(--mono);font-size:10px;color:var(--dim2)}
.livenote{font-size:11.5px;color:var(--dim2);font-style:italic;padding:2px 0 10px}
.livecols{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:14px}
.livecol{background:var(--bg2);border:1px solid var(--live-line);border-radius:11px;overflow:hidden}
.livecolhead{padding:9px 12px;border-bottom:1px solid var(--line);background:var(--live-head)}
.livecolhead h4{margin:0;font-size:12.5px;font-weight:650}
.liverow{display:flex;align-items:center;gap:8px;padding:4px 12px;border-top:1px solid var(--line)}
.liverow:first-of-type{border-top:0}
.livemin{font-family:var(--mono);font-size:11.5px;font-weight:600;color:var(--live);
  min-width:52px;font-variant-numeric:tabular-nums}
.livedest{flex:1 1 auto;min-width:0;font-size:11.5px;color:var(--dim);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.liveextra{flex:none;margin-left:8px;font-size:9.5px;font-family:var(--mono);white-space:nowrap;
  color:var(--dim2);border:1px solid var(--line2);border-radius:5px;padding:0 4px;cursor:help}
/* the platform, at a complex with more than one station name. Long ones
   (42 St-Port Authority Bus Terminal) must give way, not push the row wide. */
.liverow .depat{margin-left:8px;flex:0 1 auto;min-width:0;max-width:11ch;
  overflow:hidden;text-overflow:ellipsis}

/* "● now" — jumps the grid to the trains that are running. Without it the live
   marks open several screen-widths off to the right of the 01:41 columns. */
.ttnow{padding:1px 8px;font-size:10.5px;color:var(--live);border-color:var(--live-line);
  vertical-align:1px}
.ttnow::before{content:"→";margin-right:4px;opacity:.7}
.ttnow:hover{background:var(--live-head)}

/* ---- the Live times group in the gear menu ----
   Two switches rather than one either/or, with a worked example between them
   that turns into whatever the grid is about to look like. The example is the
   argument for the control, so it is the thing that moves. */
.rtgroup .rtseg{margin-top:6px}
.rtseg button{display:flex;flex-direction:column;align-items:center;gap:1px;padding:5px 0 4px}
.rtsl{font-size:11.5px}
.rtst{font-family:var(--mono);font-size:11px;color:var(--dim2);
  transition:color .2s ease}
.rtseg button.on .rtst{color:var(--fg)}
.rtseg button[data-v="live"].on .rtst{color:var(--live)}

.rtsuprow{display:flex;align-items:center;gap:10px;margin-top:9px}
.rtsuptxt{min-width:0}
.rtprev{margin-left:auto;font-family:var(--mono);font-size:12.5px;white-space:nowrap;
  padding:2px 7px;border:1px solid var(--line);border-radius:7px;background:var(--bg3)}
.rtprev.islive .pvt{color:var(--live);font-weight:600}
/* the superscript arrives with a small overshoot and leaves without one, which
   is the difference between a thing appearing and a thing being deleted */
.pvs{display:inline-block;transform-origin:0 100%;
  transition:transform .26s cubic-bezier(.16,1.5,.4,1),opacity .16s ease}
.supoff .pvs{transform:scale(.2) translateY(5px);opacity:0;transition-duration:.14s,.1s}

/* the pill, built like the theme toggle so the menu has one switch idiom */
.suptog{position:relative;width:40px;height:22px;flex:none;border-radius:999px;
  border:1px solid var(--line);background:var(--bg3);
  transition:background .2s ease,border-color .2s ease}
.suptog:hover{border-color:var(--line2)}
.suptog.on{background:var(--live);border-color:var(--live)}
.suptog .knob{position:absolute;top:2px;left:2px;width:16px;height:16px;border-radius:50%;
  background:var(--knob);box-shadow:0 1px 3px var(--shadow);
  transition:transform .22s cubic-bezier(.3,1.3,.5,1)}
.suptog.on .knob{transform:translateX(18px);background:#fff}
@media (prefers-reduced-motion:reduce){
  .pvs,.suptog .knob,.rtst{transition:none}
}

/* ---- timetable: how late the train actually is ----
   A signed minute count set as a superscript on the scheduled time, so the grid
   still reads as a grid. Tabular numerals and a fixed left margin stop the
   columns twitching as +9 becomes +10. */
/* Drawn as a chip rather than as bare superscript text, because bare text is
   what broke it. A time past midnight already carries a pink ⁺¹ meaning "next
   day", and a train one minute late wants to say ⁺¹ meaning "one minute" — two
   signed superscripts, side by side, in the same cell, reading as one number.
   The tint and the gap make it unmistakably a separate token, and the boundary
   is legible even where the two sit together: 00:00⁺¹ [+7]. */
.rtd{font-size:8.5px;font-family:var(--mono);font-weight:700;margin-left:3px;
  padding:0 3px;border-radius:4px;vertical-align:super;line-height:0;
  font-variant-numeric:tabular-nums}
.rtd.late{color:var(--late);background:var(--late-chip)}
.rtd.early{color:var(--accent);background:var(--early-chip)}
.rtd.ontime{color:var(--live);background:var(--live-chip)}
/* on an inverted row the tint would fight the selection block */
.tt td.on .rtd,.dep.on .rtd{color:inherit;background:transparent}
/* the day marker is not part of the mark; give it room to be its own thing */
.nx + .rtd,.rtact + .rtd{margin-left:5px}



/* "When it will actually arrive" — the predicted time standing in for the
   scheduled one. Coloured, because a number that is not the timetable's must not
   look like one. */
.rtact{color:var(--live);font-weight:600}
.rtact.late{color:var(--late)}
.tt td.on .rtact,.dep.on .rtact{color:inherit}

/* A train that is running but is not in the timetable, given its own column.
   Marked on the header and by the colour of the times themselves — deliberately
   with no cell background. The feed drops each stop as the train leaves it, so a
   train near the end of its run has three times against thirty-four stations,
   and tinting the whole column made three numbers drag a full-height green
   stripe behind them. The skipped cells here are the same dot as anywhere else. */
.tt th.livecol{color:var(--live);border-bottom-color:var(--live)}
.tt th.livecol::before{content:"●";font-size:7px;vertical-align:middle;margin-right:3px}
.tt td.livecol{color:var(--live)}

/* A stop a scheduled train is making that its timetable row does not have — a
   reroute, or an express put onto the local track. The cell was a dot; the feed
   knows when the train gets there. Dotted underneath so it reads as a time the
   timetable does not vouch for, which is the one thing the number itself cannot
   say in a column of ordinary scheduled times. */
.tt td.livecall{text-decoration:underline dotted var(--livedim);text-underline-offset:3px}
/* and the same stop in the traced journey, where the row is an inserted one */
.jt.livecall{color:var(--live)}

/* the chip on a scheduled row, when that train is in the feed */
.deplive{margin-left:auto;font-family:var(--mono);font-size:10px;white-space:nowrap;
  font-variant-numeric:tabular-nums}
.deplive:empty{display:none}
.deplive.on{color:var(--live)}
.deplive.late{color:var(--late)}
.dep.on .deplive{color:var(--bg)}
.depat+.deplive,.deplive+.depat{margin-left:8px}

.depcols{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:14px}
.depcol{background:var(--bg2);border:1px solid var(--line);border-radius:11px;overflow:hidden}
.dephead{display:flex;align-items:baseline;justify-content:space-between;padding:9px 12px;
  border-bottom:1px solid var(--line);background:var(--bg3)}
.dephead h3{font-size:12.5px}
.depn{font-family:var(--mono);font-size:10.5px;color:var(--dim2)}
.depbody{max-height:calc(100vh - 360px);overflow-y:auto}
.dep{display:flex;align-items:center;gap:8px;width:100%;text-align:left;padding:4px 12px;
  border-bottom:1px solid var(--line)}
.dep:hover{background:var(--hover)}
.dep.on{background:var(--ink)}
.dep.on .dept,.dep.on .depd{color:var(--bg)}
.dept{font-family:var(--mono);font-size:11.5px;color:var(--fg);min-width:52px}
.depd{font-size:11.5px;color:var(--dim);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.dep.term .depd{font-style:italic;color:var(--dim2)}
.depat{margin-left:auto;font-size:9.5px;color:var(--dim2);font-family:var(--mono);white-space:nowrap}
.complexnote{color:var(--dim);font-size:12.5px;margin-top:7px}
/* Only the genuinely horizontal scrollers opt out of chaining on X — putting
   overscroll-behavior-x on #main would kill the back-swipe over the whole page,
   since overflow-y:auto makes it a scroll container on both axes. */
.ttwrap,.freqgrid{overscroll-behavior-x:contain}
#main,.depbody,#side,#lines{overscroll-behavior-y:contain}
/* Opening the drawer narrows #main, so the hint above the grid re-wraps and the
   header row changes height — and Chrome's scroll anchoring then "helpfully"
   adjusts scrollTop to keep whatever it picked as the anchor still, undoing the
   offset withScrollAnchor() has just put back and sliding the grid a few pixels
   under the cursor. The app restores both offsets itself on every rebuild (see
   withScrollAnchor in app.js), so the browser's second opinion is not wanted:
   its adjustment lands after the restore and always wins. */
#main{overflow-anchor:none}
#drawer{transition:width .18s ease}
.dhead{align-items:flex-start}
.dtitle{font-size:13.5px;font-weight:600;line-height:1.3}
.dhead .sub{font-size:12px;color:var(--dim);margin-top:2px}

/* light / dark toggle — one bare icon: sun in light mode, moon in dark mode */
.themetog{position:relative;width:30px;height:30px;flex:none;
  border:0;background:none;display:flex;align-items:center;justify-content:center}
.themetog .ic{width:16px;height:16px;display:none;color:var(--dim2);
  transition:color .12s ease}
.themetog:hover .ic{color:var(--fg)}
.themetog:active .ic{transform:translateY(1px)}
:root[data-theme="light"] .themetog .sun{display:block}
:root:not([data-theme="light"]) .themetog .moon{display:block}
/* The theme now flips instantly — no cross-fade. Colours come from CSS
   variables on :root, so one attribute change repaints the page in one go. */

/* ---------- home link on the brand mark ---------- */
.homelink{display:block;line-height:0;border-radius:8px;opacity:.9;
  transition:opacity .12s ease,transform .12s ease}
.homelink:hover{opacity:1;transform:scale(1.06)}
.homelink:focus-visible{outline:2px solid var(--accent);outline-offset:3px}

/* ---------- LIRR: named branches instead of bullet artwork ----------
   The LIRR has no bullet set, and its branches are names, not letters — a disc with
   a glyph in it would be an invention. Chips carry the feed's own route_color, with
   text colour chosen the same way the bullets do it, by luminance. */
.rchip{display:inline-flex;align-items:center;justify-content:center;flex:none;
  font-weight:700;letter-spacing:.01em;white-space:nowrap;line-height:1;
  font-family:var(--sans)}
/* A name is far wider than a disc, so the branch rail is a column, not a 3-up grid.
   168px is the longest name — City Terminal Zone, 137px of chip — plus the rail's own
   padding and the 2px the chip travels on hover. */
body[data-agency="lirr"] #lines{width:168px}
body[data-agency="lirr"] #lineGrid{display:flex;flex-direction:column;gap:5px;
  grid-template-columns:none;justify-content:flex-start}
body[data-agency="lirr"] .linebtn{border-radius:13px;line-height:0;width:100%;
  display:flex;justify-content:flex-start}
body[data-agency="lirr"] .linebtn:hover{transform:translateX(2px)}
body[data-agency="lirr"] #lineGrid.picked .linebtn.on{transform:none}
/* The quiet chip. Twelve solid pills down the rail is a colour chart, not a menu —
   so unpicked branches wear their colour as a wash and as ink, and only the picked
   one goes solid. --chip / --chipt come in from bullet(); the two mixes are what
   make a 6E3219 brown or a 4D5357 grey legible as text at either end of the theme. */
.rchip.quiet{font-weight:600;letter-spacing:.005em;
  background:color-mix(in srgb,var(--chip) 26%,transparent);
  color:color-mix(in oklab,var(--chip),#fff 40%);
  transition:background .12s ease,color .12s ease}
:root[data-theme="light"] .rchip.quiet{
  background:color-mix(in srgb,var(--chip) 22%,transparent);
  color:color-mix(in oklab,var(--chip),#000 30%)}
.linebtn:hover .rchip.quiet{background:color-mix(in srgb,var(--chip) 38%,transparent)}
.linebtn.on .rchip.quiet{background:var(--chip);color:var(--chipt)}
/* Colour carries the picked state on its own, so the rail stops dimming the rest —
   dimming a wash only makes it grey. */
body[data-agency="lirr"] #lineGrid .linebtn,
body[data-agency="lirr"] #lineGrid.picked .linebtn{opacity:1}
body[data-agency="lirr"] .conn .rchip,body[data-agency="lirr"] .jn .rchip{vertical-align:middle}
/* Chips inside a flowing row need to sit on the text baseline, not float above it. */
.stlines .rchip,.lsrow .rchip,.dep .rchip{vertical-align:-3px}
/* Connection marks. A subway bullet reads at 17px because the letter in the disc IS
   the name; a branch name is not, and nine of them wrapped across three lines and
   buried the times they annotate. Colour dot, name in the tooltip. */
.rdot{display:inline-block;flex:none;border-radius:50%;vertical-align:middle;
  box-shadow:0 0 0 1px var(--bg2)}
.conn .rdot{margin-right:1px}
.jn .rdot{margin-left:3px}

/* ---------- era control: which timetable book ---------- */
.seg.era{gap:2px}
.seg.era .seglbl{display:flex;align-items:center;padding:0 8px 0 6px;font-size:10.5px;
  text-transform:uppercase;letter-spacing:.1em;color:var(--dim2);white-space:nowrap}
.seg.era button{position:relative;font-family:var(--mono);font-size:11.5px}
.seg.era .livedot{position:absolute;top:3px;right:3px;width:5px;height:5px;border-radius:50%;
  background:#22C55E}
@media (max-width:1180px){.seg.era .seglbl{display:none}}
@media (max-width:900px){.seg.era button{font-size:10.5px;padding:5px 7px}}

/* ---------- peak trains ----------
   Peak is per-trip on the LIRR, where express is per-pattern on the subway, so it
   gets its own channel rather than borrowing the ◆ marks. That channel is WEIGHT:
   bold is a peak train, regular is not. Safe because the chips are monospace — bold
   has the same advance, so nothing re-wraps (the rule that bit us on the branch chips).
   An outline was the earlier answer and read as a selection box next to `.t.on`. */
.t.peak{font-weight:700}
/* Bold at --dim barely registers, so peak also takes full contrast — but not when
   selected: `.t.on` inverts to --bg on --ink, and --fg there would be invisible. */
.t.peak:not(.on){color:var(--fg)}
/* Weight now MEANS peak, so selection must stop borrowing it, or every selected
   train would read as peak. Inverted colours already carry the selection on their own. */
body[data-agency="lirr"] .t.on:not(.peak){font-weight:400}
th.peak{text-decoration:underline;text-underline-offset:2px}
.trainno{margin-left:8px;font-family:var(--mono);font-size:12px;color:var(--dim);
  font-weight:600}
.trainno.pk{color:var(--rc)}

/* ---------- scheduled hold (Jamaica) ----------
   Where a train holds for the cross-platform meets the feed gives two times; showing
   only the arrival would hide the wait, which is the thing a rider is deciding on. */
/* The time column is a fixed 52px, so arrive/depart stacks rather than sitting side
   by side. Make that deliberate: the hold reads as two times, the later one marked. */
.jt.holds{display:flex;flex-direction:column;align-items:flex-end;line-height:1.25}
.jt.holds .dep{color:var(--rc);font-weight:650;position:relative;padding-left:9px}
.jt.holds .dep::before{content:"↓";position:absolute;left:0;opacity:.7;font-size:9px;
  line-height:1.6}
