@font-face{font-family:Lato;src:url('/assets/fonts/lato-regular-latin.woff2');font-display:swap}@font-face{font-family:Lato;src:url('/assets/fonts/lato-bold-latin.woff2');font-weight:700;font-display:swap}@font-face{font-family:Lato;src:url('/assets/fonts/lato-black-latin.woff2');font-weight:900;font-display:swap}@font-face{font-family:Oswald;src:url('/assets/fonts/oswald-variable-latin.woff2');font-weight:500 700;font-display:swap}
/* Flame, the typeface Colophon Foundry drew for Burger King. Loaded for one theme, so it is only
   fetched when that theme asks for it -- the precache carries it, but nothing else references it. */
@font-face{font-family:Flame;src:url('/assets/fonts/bk-flame-latin.woff2');font-weight:400 700;font-display:swap}
/* --card is every raised surface: a departure card, a control, a panel. --sunken is the tint those
   take while pressed. Both exist so a dark theme is a change of palette rather than seventeen
   overrides of a hardcoded white, and both default to exactly what was written literally before, so
   the NYC Ferry livery is untouched. Two whites are deliberately NOT this variable: badge text on a
   saturated fill, and .route-badge-image, whose chip has to stay white because partner wordmarks are
   dark ink on transparency and vanish without it. */
:root{--navy:#001d41;--blue:#00a1df;--warning:#ffd100;--ink:#102f47;--muted:#536e7f;--canvas:#f1f7fa;--line:#bfd8e4;--card:#fff;--sunken:#e9f2f7;--departures-shown:4;font-family:Lato,Arial,sans-serif;color:var(--ink);background:var(--canvas);font-synthesis:none}:root[data-departures-shown="1"]{--departures-shown:1}:root[data-departures-shown="2"]{--departures-shown:2}:root[data-departures-shown="3"]{--departures-shown:3}:root[data-departures-shown="4"]{--departures-shown:4}:root[data-departures-shown="5"]{--departures-shown:5}
*{box-sizing:border-box}[hidden]{display:none!important}html,body{width:100%;height:100%;margin:0;overflow:hidden}body{background:var(--canvas)}
.screen{width:100vw;height:100vh;height:100dvh;display:grid;grid-template-rows:minmax(0,1fr) auto}.content{display:grid;grid-template-columns:minmax(0,1fr);min-height:0}.board{display:flex;flex-direction:column;min-width:0;min-height:0;padding:.8vh 1vw .6vh}
.board-heading{display:flex;align-items:center;justify-content:space-between;gap:1vw;padding:0 .5vw .5vh}.board-heading h1{margin:0;margin-right:auto;color:var(--navy);font:650 clamp(20px,3vh,36px)/1 Oswald,sans-serif;text-transform:uppercase;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.board-state{display:flex;align-items:center;gap:.6vw}.route-count,.status{padding:.5vh .6vw;border:1px solid var(--line);border-radius:6px;background:var(--card);color:var(--muted);font-size:clamp(10px,1.5vh,14px);font-weight:900;white-space:nowrap}.status{display:flex;align-items:center;gap:.4vw}.status i{width:8px;height:8px;border-radius:50%;background:#35aa65;box-shadow:0 0 0 3px rgba(53,170,101,.14)}.clock{display:flex;align-items:baseline;gap:.5vw}.clock time{color:var(--navy);font:700 clamp(20px,3.2vh,36px)/1 Oswald,sans-serif}.clock span{color:var(--muted);font-size:clamp(10px,1.5vh,14px);font-weight:700;white-space:nowrap}
.column-head,.departure{display:grid;grid-template-columns:15% 19% 1fr;align-items:center}.column-head{padding:0 .6vw .4vh;color:var(--muted);font-size:clamp(9px,1.2vh,12px);font-weight:900;letter-spacing:.1em;text-transform:uppercase}.departures{flex:1;min-height:0;display:grid;--route-row:clamp(50px,7.4vh,76px);grid-auto-rows:var(--route-row);align-content:start;gap:.5vh;overflow-y:auto;overscroll-behavior:contain;scrollbar-width:thin;scrollbar-color:var(--line) transparent}.departures::-webkit-scrollbar{width:10px}.departures::-webkit-scrollbar-thumb{background:var(--line);border-radius:5px}.departures::-webkit-scrollbar-track{background:transparent}
.departure{--route-color:#004e72;--route-text:#fff;min-height:0;padding:0 .6vw;background:var(--card);border-left:.45vw solid var(--route-color);box-shadow:0 .2vh .6vh rgba(0,29,65,.08);border-radius:6px;overflow:hidden;animation:slide-in .28s ease both}.departure.route-AS{--route-color:#ff6b00;--route-text:#fff}.departure.route-ER,.departure.route-ER.variant-a,.departure.route-ER.variant-b,.departure.route-ER.variant-local{--route-color:#00839c;--route-text:#fff}.departure.route-GI{--route-color:#9795a0;--route-text:#fff}.departure.route-RES{--route-color:#00a1e1;--route-text:#001d41}.departure.route-RR{--route-color:#ff8672;--route-text:#001d41}.departure.route-RS{--route-color:#4e008e;--route-text:#fff}.departure.route-RWS{--route-color:#00a1e1;--route-text:#001d41}.departure.route-SB{--route-color:#ffd100;--route-text:#001d41}.departure.route-SG{--route-color:#d0006f;--route-text:#fff}
.route{min-width:0;display:flex;align-items:center;gap:.5vw}.route-badge{flex:0 0 auto;width:clamp(44px,3.4vw,66px);height:clamp(26px,4.4vh,46px);display:flex;align-items:center;justify-content:center;gap:.3vw;border:2px solid var(--route-color);border-radius:6px;background:var(--route-color);color:var(--route-text);box-shadow:inset 0 0 0 1px rgba(0,29,65,.08)}.route-badge b{font:700 clamp(14px,2.5vh,25px)/1 Oswald}.route-badge-image{width:clamp(78px,6.4vw,118px);padding:.15vw;background:#fff}.route-badge-logo{max-width:100%;max-height:100%;display:block;object-fit:contain}.route-variant{padding:.2em .35em;color:var(--route-color);background:var(--route-text);border-radius:4px;font-size:clamp(7px,1vh,10px);font-weight:950;letter-spacing:.04em;text-transform:uppercase}.route-name{min-width:0;color:var(--navy);font-size:clamp(9px,1.35vh,13px);font-weight:900;line-height:1.08;text-transform:uppercase;overflow-wrap:anywhere}.route-operator{display:block;margin-top:.2vh;color:var(--muted);font-size:clamp(7px,1vh,10px);font-weight:800;letter-spacing:.08em;text-transform:uppercase}.destination{min-width:0;padding-right:.6vw}.destination strong{display:block;color:var(--navy);font-size:clamp(14px,2.5vh,25px);line-height:1.05;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}/* Intermediate calls sit with the destination but must never compete with it: a rider wants the far
   end first and the stops on the way second. */
.approx{color:#b06a00;font-weight:900;text-decoration:none;padding-left:.1em}
/* The home port's standing note. Built like a departure card — same surface, same radius, same
   left edge — so it reads as part of the board rather than as chrome bolted above it, but in the
   star's own amber and only one row tall, because it is a legend and not a sailing. It sits above
   the list rather than in it, so it does not scroll away from the stars it explains. */
.board-note{flex:0 0 auto;margin:0 0 .5vh;padding:.5vh .7vw;color:#b06a00;background:var(--card);border-left:.45vw solid #b06a00;border-radius:6px;box-shadow:0 .2vh .6vh rgba(0,29,65,.08);font:800 clamp(10px,1.5vh,14px)/1.3 Lato,Arial,sans-serif}
.boat-name-predicted{color:var(--muted);font-style:italic;font-weight:800}
.via{display:block;color:var(--muted);font-size:clamp(9px,1.35vh,13px);font-weight:800;line-height:1.1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.destination span{display:block;margin-top:.2vh;color:var(--muted);font-size:clamp(8px,1.2vh,12px);font-weight:800;text-transform:uppercase;letter-spacing:.06em}
.departure-slots{height:100%;display:grid;grid-template-columns:repeat(var(--departures-shown),minmax(0,1fr));align-items:stretch;border-left:1px solid var(--line)}.departure-slot{min-width:0;min-height:0;display:flex;flex-direction:column;justify-content:center;gap:.3vh;padding:0 .5vw;border-right:1px solid var(--line)}.departure-slot:last-child{border-right:0}.slot-time-row{display:flex;align-items:baseline;gap:.4vw;white-space:nowrap;min-width:0;overflow:hidden}.slot-time-row time{color:var(--navy);font:650 clamp(15px,2.7vh,27px)/1 Oswald}.slot-relative{color:#00749e;font:900 clamp(10px,1.6vh,15px)/1 Lato,Arial,sans-serif;white-space:nowrap}
.departure-last-slot{display:flex;align-items:center;gap:4px;min-height:clamp(14px,2vh,22px);overflow:hidden;white-space:nowrap}.departure-last-badge{display:inline-flex;align-items:center;padding:2px 5px;color:var(--navy);background:var(--warning);border:2px solid #d89a26;border-radius:8px;font-size:clamp(9px,1.4vh,13px);font-weight:950;letter-spacing:.06em;line-height:1;white-space:nowrap}.vessel-delay-badge,.on-time-badge,.scheduled-badge{display:inline-flex;align-items:center;padding:2px 5px;color:#fff;border-radius:6px;font-size:clamp(9px,1.4vh,13px);font-weight:950;line-height:1;white-space:nowrap;unicode-bidi:isolate}.vessel-delay-badge{background:#b83224}.on-time-badge{background:#218a4b}.scheduled-badge{color:var(--navy);background:#e6eef2;border:1px solid var(--line)}.boat-assignment{flex:0 0 auto;display:inline-flex;align-items:center;padding:1px 4px;color:var(--navy);background:var(--card);border:1.5px solid var(--navy);border-radius:5px;font:900 clamp(9px,1.4vh,13px)/1 Oswald,Lato,Arial,sans-serif;letter-spacing:.03em;white-space:nowrap}/* Moves that carry no passengers. Deliberately not coloured like a status badge: NO PICKUP is not
   a running state the way ON TIME is, and an agent scanning the board should be able to tell the
   difference without reading. */
.no-pickup-badge{display:inline-flex;align-items:center;padding:2px 5px;color:#fff;background:#4a5b68;border-radius:6px;font-size:clamp(9px,1.4vh,13px);font-weight:950;line-height:1;white-space:nowrap;letter-spacing:.03em}
/* The question-mark form is the same badge, drained of confidence: an agent should be able to see
   at a glance which of these is inferred from a long gap rather than a boat plainly finishing. */
.drop-off-badge.drop-off-unsure{color:#5c5140;background:#f4efe4;border-style:dashed}
.time-range-dash{padding:0 .12em;font-weight:400}
/* An arriving boat, ending its run here. Green rather than the grey of NO PICKUP: the boat is in
   service and carrying people, it just cannot be joined. */
.arrival-badge{display:inline-flex;align-items:center;padding:2px 5px;color:#0b5c33;background:#dcf3e5;border:1px solid #35aa65;border-radius:6px;font-size:clamp(9px,1.4vh,13px);font-weight:950;line-height:1;white-space:nowrap;letter-spacing:.03em}
.drop-off-only-badge{display:inline-flex;align-items:center;padding:2px 5px;color:#0b5c33;background:var(--card);border:1px solid #35aa65;border-radius:6px;font-size:clamp(9px,1.4vh,13px);font-weight:950;line-height:1;white-space:nowrap;letter-spacing:.03em}
.drop-off-badge{display:inline-flex;align-items:center;padding:2px 5px;color:#7a4200;background:#ffe6bf;border:1px solid #d89a26;border-radius:6px;font-size:clamp(9px,1.4vh,13px);font-weight:950;line-height:1;white-space:nowrap;letter-spacing:.03em}
.boat-name{min-width:0;color:#00749e;font-size:clamp(9px,1.3vh,13px);font-weight:900;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.departure-slot.unavailable{color:var(--muted);font-size:clamp(9px,1.3vh,12px);font-weight:700}.empty{display:grid;place-items:center;background:var(--card);border:2px solid var(--line);border-radius:8px;text-align:center}.empty strong{display:block;color:var(--navy);font-size:2.5vw}.empty span{display:block;margin-top:1vh;color:var(--muted);font-size:1.15vw}
.screen.override-active{grid-template-rows:minmax(0,1fr)}.screen.override-active .content,.screen.override-active .service-alert-bar,.screen.override-active .landing-menu{display:none}.manual-override{min-height:0;display:grid;place-items:center;padding:clamp(28px,5vh,76px) clamp(36px,6vw,120px);background:linear-gradient(135deg,#001d41 0%,#003d63 100%)}.manual-override-box{position:relative;width:100%;height:100%;min-height:0;display:grid;grid-template-rows:auto minmax(0,1fr) auto;align-items:center;padding:clamp(28px,4vh,64px) clamp(40px,5vw,100px);overflow:hidden;background:var(--card);border:clamp(7px,.75vw,14px) solid var(--warning);border-radius:clamp(12px,1.2vw,24px);box-shadow:0 2vh 5vh rgba(0,15,37,.35);text-align:center}.manual-override-label{justify-self:center;padding:.55em 1.1em;color:var(--navy);background:var(--warning);border-radius:999px;font-size:clamp(18px,1.6vw,30px);font-weight:950;letter-spacing:.12em;line-height:1;text-transform:uppercase}.manual-override-box p{align-self:center;margin:clamp(18px,3vh,44px) 0;color:var(--navy);font:900 clamp(38px,5.2vw,98px)/1.08 Lato,Arial,sans-serif;letter-spacing:-.025em;white-space:pre-wrap;overflow-wrap:anywhere}.manual-override-box[data-size="medium"] p{font-size:clamp(30px,4.1vw,76px)}.manual-override-box[data-size="long"] p{font-size:clamp(22px,2.8vw,52px);line-height:1.14}.manual-override-box small{color:var(--muted);font-size:clamp(14px,1.25vw,23px);font-weight:800;letter-spacing:.04em}
.service-alert-bar{z-index:48;width:100%;min-height:0;display:grid;grid-template-columns:auto minmax(0,1fr) auto auto;align-items:center;font:inherit;text-align:left;border:0;cursor:pointer;gap:clamp(8px,1vw,16px);padding:5px clamp(12px,1.5vw,24px);color:var(--navy);background:#f5f9f8;border-top:3px solid #ffd100;box-shadow:0 -6px 18px rgba(0,29,65,.08);text-align:left}.service-alert-bar.active{background:#fff3c7}.service-alert-bar.stale{background:#f7f1df}.service-alert-bar.unavailable{color:#536b78;background:#edf2f2}.service-alert-bar:disabled{cursor:default}.service-alert-bar:focus-visible{outline:3px solid var(--blue);outline-offset:-3px}.service-alert-icon{width:clamp(24px,3.4vh,36px);height:clamp(24px,3.4vh,36px);overflow:visible;fill:#ffd100;stroke:var(--navy);stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round}.service-alert-copy{min-width:0}.service-alert-heading{display:flex;align-items:baseline;gap:clamp(6px,.7vw,12px);min-width:0}.service-alert-copy strong,.service-alert-copy small{display:block}.service-alert-copy strong{flex:0 0 auto;font-size:clamp(11px,1.6vh,15px);font-weight:950;letter-spacing:.04em;text-transform:uppercase}.service-alert-summary{display:-webkit-box;overflow:hidden;margin-top:1px;font-size:clamp(11px,1.6vh,15px);font-weight:800;line-height:1.12;-webkit-box-orient:vertical;-webkit-line-clamp:2}.service-alert-copy small{min-width:0;margin:0;color:var(--muted);font-size:clamp(8px,1.2vh,11px);font-weight:750;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.service-alert-count{min-width:34px;min-height:26px;display:grid;place-items:center;padding:3px 8px;color:#fff;background:var(--navy);border-radius:999px;font-size:clamp(11px,1.6vh,15px);font-weight:950}
.visually-hidden{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
.menu-button{flex:0 0 auto;display:grid;place-items:center;width:38px;height:38px;padding:0;background:var(--card);border:1px solid var(--line);border-radius:8px;cursor:pointer;-webkit-tap-highlight-color:transparent}.menu-button:active{background:var(--sunken)}.menu-button:focus-visible,.landing-option:focus-visible,.landing-menu-close:focus-visible{outline:3px solid var(--blue);outline-offset:2px}
.menu-icon,.menu-icon::before,.menu-icon::after{display:block;width:18px;height:2.5px;background:var(--navy);border-radius:2px}.menu-icon{position:relative}.menu-icon::before,.menu-icon::after{content:"";position:absolute;left:0}.menu-icon::before{top:-6px}.menu-icon::after{top:6px}
/* Both header controls are square and icon-only, so the heading fits a landing name, a map and a
   location fix on one line of a phone. The nearest button's label stays in the markup for the
   accessible name, and its state still reads through the icon's colour; see the states below. */
.nearest-button{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;padding:0;background:var(--card);border:1px solid var(--line);border-radius:8px;color:var(--navy);font:900 clamp(10px,1.5vh,14px)/1 Lato,sans-serif;cursor:pointer;-webkit-tap-highlight-color:transparent}.nearest-button:active{background:var(--sunken)}.nearest-button:focus-visible{outline:3px solid var(--blue);outline-offset:2px}.nearest-button:disabled{cursor:default}
.nearest-icon{flex:0 0 auto;width:16px;height:16px;fill:none;stroke:var(--navy);stroke-width:2.2;stroke-linecap:round}.nearest-label{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.map-button{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;padding:0;background:var(--card);border:1px solid var(--line);border-radius:8px;color:var(--navy);cursor:pointer;-webkit-tap-highlight-color:transparent}.map-button:active{background:var(--sunken)}.map-button:focus-visible{outline:3px solid var(--blue);outline-offset:2px}
.map-icon{flex:0 0 auto;width:17px;height:17px;fill:none;stroke:var(--navy);stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round}
/* A resolved fix is the one state worth colouring: it is the only one that does something when
   tapped. "here" stays quiet because the board already says where you are. */
/* "Calls at another Manhattan terminal on the way". Built on the SCHEDULED badge's shape so it
   reads as the same class of information, but in its own hues: this answers "can I get there on
   this boat", not "is it running to time", and the two must never be confused for one another.
   None of the three collides with a status colour — red for late, green for on time, yellow for
   LAST, amber for FINAL, grey for NO PICKUP — and each terminal keeps its own, so a row carrying
   two of them is still readable at a glance. */
.via-terminal-badge{display:inline-flex;align-items:center;padding:2px 5px;border-radius:6px;font-size:clamp(9px,1.4vh,13px);font-weight:950;line-height:1;white-space:nowrap;letter-spacing:.03em;unicode-bidi:isolate}
.via-terminal-bpc{color:#4a2597;background:#ede4fa;border:1px solid #9b74d8}
.via-terminal-p79{color:#8a1257;background:#fde3f1;border:1px solid #d472a6}
.via-terminal-pier11{color:#0d5c56;background:#ddf2f0;border:1px solid #5fb3aa}

/* Date stepper, sitting under the departures list.
   Browsing turns it amber: a board showing a day that is not today must never be mistaken for the
   live one, and the tint is the fastest way to say so. */
/* The bottom row: which day is on screen, and how its times are printed. */
.board-footer{flex:0 0 auto;display:flex;align-items:stretch;gap:.5vw;margin-top:.5vh}
.clock-toggle{flex:0 0 auto;min-width:4.5em;min-height:34px;padding:.4vh .6vw;border:1px solid var(--line);border-radius:8px;background:var(--card);color:var(--navy);font:900 clamp(11px,1.6vh,15px)/1 Lato,sans-serif;white-space:nowrap;cursor:pointer;-webkit-tap-highlight-color:transparent}
.clock-toggle:active{background:var(--sunken)}
.clock-toggle:focus-visible{outline:3px solid var(--blue);outline-offset:-3px}
/* Pressed means "not the default", which is worth being able to see at a glance on a crew board
   whose schedule and radio traffic are both in 24-hour. */
.clock-toggle[aria-pressed="true"]{border-color:var(--blue);background:#e8f5fc;color:#0b6c92}
.date-bar{flex:1 1 auto;display:flex;align-items:stretch;justify-content:center;gap:0;border:1px solid var(--line);border-radius:8px;background:var(--card);overflow:hidden}
.date-step,.date-current{border:0;background:transparent;color:var(--navy);font:900 clamp(11px,1.6vh,15px)/1 Lato,sans-serif;cursor:pointer;-webkit-tap-highlight-color:transparent}
.date-step{flex:0 0 auto;width:15%;min-width:44px;min-height:34px;padding:0;font-size:clamp(16px,2.4vh,22px);line-height:1;color:var(--muted)}
.date-step:active{background:var(--sunken)}
.date-step:disabled{opacity:.3;cursor:default}
.date-current{flex:1 1 auto;min-height:34px;padding:.4vh .6vw;border-inline:1px solid var(--line);white-space:nowrap;letter-spacing:.02em}
.date-current:active{background:var(--sunken)}
.date-step:focus-visible,.date-current:focus-visible{outline:3px solid var(--blue);outline-offset:-3px}
.date-bar[data-state="browsing"]{border-color:#b58900;background:#fff8e1}
.date-bar[data-state="browsing"] .date-current{border-inline-color:#e3c76a;color:#7a5c00}
.date-bar[data-state="browsing"] .date-step{color:#9a7500}
/* On a browsed day the list itself carries a matching edge, so the tint is not stranded at the
   very bottom of a long list that has been scrolled away from it. */
.screen.browsing-schedule .departures{border-left:3px solid #e3c76a;padding-left:.5vw}
.nearest-button[data-state="ready"]{border-color:var(--blue);background:#e8f5fc;color:#0b6c92}.nearest-button[data-state="ready"] .nearest-icon{stroke:#0b6c92}
.nearest-button[data-state="here"] .nearest-icon{stroke:#35aa65}
.nearest-button[data-state="error"]{border-color:#c8102e;color:#c8102e}.nearest-button[data-state="error"] .nearest-icon{stroke:#c8102e}
.nearest-button[data-state="locating"] .nearest-icon{animation:nearest-pulse 1s ease-in-out infinite}
@keyframes nearest-pulse{50%{opacity:.25}}
/* Filter button: bottom-left of the footer, beside the date stepper. It lights up only while
   something is actually being hidden, because a board that is not showing every boat has to say so
   without being asked. */
.filter-button{position:relative;flex:0 0 auto;display:flex;align-items:center;justify-content:center;min-width:44px;min-height:34px;padding:.4vh .5vw;border:1px solid var(--line);border-radius:8px;background:var(--card);color:var(--navy);cursor:pointer;-webkit-tap-highlight-color:transparent}
.filter-button:active{background:var(--sunken)}
.filter-button:focus-visible{outline:3px solid var(--blue);outline-offset:-3px}
.filter-icon{width:clamp(15px,2.1vh,19px);height:clamp(15px,2.1vh,19px);fill:none;stroke:currentColor;stroke-width:2;stroke-linejoin:round}
.filter-button.is-filtering{border-color:var(--blue);background:#e8f5fc;color:#0b6c92}
.filter-count{position:absolute;top:-6px;right:-6px;min-width:16px;height:16px;padding:0 3px;border-radius:8px;background:#c8102e;color:#fff;font:900 10px/16px Lato,Arial,sans-serif;text-align:center}
/* Bottom sheet rather than a side drawer: the button is at the bottom of the screen and the board
   it filters stays visible above it while operators are switched on and off. */
.filter-menu{position:fixed;inset:0;z-index:60;display:flex;align-items:flex-end}
.filter-menu-scrim{position:absolute;inset:0;background:rgba(0,15,37,.5)}
.filter-menu-panel{position:relative;display:flex;flex-direction:column;gap:10px;width:100%;max-height:80%;padding:14px 14px calc(14px + env(safe-area-inset-bottom));background:var(--canvas);border-radius:14px 14px 0 0;box-shadow:0 0 40px rgba(0,15,37,.4);animation:filter-in .18s ease both}
@keyframes filter-in{from{transform:translateY(12px);opacity:0}}
/* The trip view. Fullscreen: .filter-menu is already a fixed, full-viewport flex box anchored to
   the bottom, so stretching it and dropping the panel's rounded top is the whole difference.
   Everything below colours itself from the theme's own variables -- a literal grey here is what
   would force four more per-theme blocks and still look wrong on the dark ones. */
.trip-menu{align-items:stretch}
.trip-menu .filter-menu-panel{height:100%;max-height:100%;border-radius:0;padding-top:calc(14px + env(safe-area-inset-top))}
.trip-summary{margin:0;color:var(--muted);font:800 13px/1.35 Lato,Arial,sans-serif}
/* Under the summary and above the stops: the sheet answers "where does this boat go" below it, and
   this answers "and where is it now", which is the shorter question and the one you leave for. */
.trip-map-link{display:flex;align-items:center;justify-content:center;gap:8px;min-height:44px;padding:10px 12px;color:var(--navy);background:var(--card);border:1px solid var(--line);border-radius:8px;font:900 14px/1.2 Lato,Arial,sans-serif;text-align:center;text-decoration:none}
.trip-map-link:active{background:var(--sunken)}
.trip-map-link:focus-visible{outline:3px solid var(--blue);outline-offset:2px}
.trip-map-link .map-icon{width:16px;height:16px}
.trip-stops{gap:2px}
.trip-stop{display:flex;flex-direction:column;gap:4px;padding:6px 0}
/* Calls the boat has already made. Dimmed rather than hidden: on a mid-route boarding, where it
   has come from is context, not clutter. */
.trip-stop.is-past{opacity:.5}
.trip-stop-head{display:flex;align-items:baseline;justify-content:space-between;gap:8px;width:100%;padding:8px 10px;color:var(--navy);background:var(--card);border:1px solid var(--line);border-radius:8px;font:800 15px/1.2 Lato,Arial,sans-serif;text-align:left}
button.trip-stop-head{cursor:pointer}
/* A stop no landing on this board serves. Same shape, no affordance -- tapping it would go nowhere. */
.trip-stop-static{cursor:default}
.trip-stop.is-current .trip-stop-head{border-color:var(--blue);background:var(--sunken)}
.trip-stop-timing{display:flex;flex:0 0 auto;align-items:baseline;gap:7px;white-space:nowrap}
.trip-stop-time{flex:0 0 auto;color:var(--muted);font:650 14px/1 Oswald,sans-serif}
/* Rides beside whatever it follows — the destination in the timeline, the status line on the route
   board — so it is held to its own width and kept a size down from its neighbour. The rule carries
   its own separator, which disappears with it rather than leaving a stray bar on a row with no turn. */
.departure-layover{flex:0 0 auto;color:var(--navy);font:800 clamp(10px,1.5vh,14px)/1.15 Lato,Arial,sans-serif;white-space:nowrap}
.departure-layover::before{content:"|";margin:0 .35em;color:var(--line);font-weight:700}
.trip-stop-layover{padding:2px 5px;border-radius:4px;color:#fff;background:var(--blue);font:900 10px/1.2 Lato,Arial,sans-serif;letter-spacing:.02em}
.trip-stop-you{flex:0 0 auto;color:var(--blue);font:900 10px/1 Lato,Arial,sans-serif;letter-spacing:.06em;text-transform:uppercase}
.trip-conns{display:flex;flex-direction:column;gap:3px;margin:0;padding:2px 0 2px 10px;border-left:2px solid var(--line);list-style:none}
.trip-conn{display:flex;align-items:baseline;gap:8px;color:var(--navy);font:700 13px/1.3 Lato,Arial,sans-serif}
.trip-conn-badge{flex:0 0 auto;min-width:34px;padding:1px 5px;color:var(--route-text,#fff);background:var(--route-color,var(--navy));border-radius:4px;font:800 11px/1.4 Lato,Arial,sans-serif;text-align:center}
/* A partner wordmark needs its own paper to sit on, the same way .route-badge-image gives the
   departure rows a white plate instead of the route colour behind the logo. */
.trip-conn-badge-image{display:flex;align-items:center;justify-content:center;min-width:44px;padding:2px 4px;background:#fff;box-shadow:inset 0 0 0 1px var(--route-color,var(--navy))}
.trip-conn-badge-logo{display:block;max-width:52px;max-height:14px;object-fit:contain}
.trip-conn-time{flex:0 0 auto;font:650 14px/1 Oswald,sans-serif}
.trip-conn-where{flex:1 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--muted);font-weight:800}
.trip-conn-boat{flex:0 0 auto;color:var(--blue);font-weight:800}
.trip-conn-delay{flex:0 0 auto;color:#b3261e;font-weight:900}
.trip-conn-note{color:var(--muted);font:700 12px/1.4 Lato,Arial,sans-serif}
.filter-menu-head{display:flex;align-items:center;justify-content:space-between;gap:12px}
.filter-menu-head strong{color:var(--navy);font:650 20px/1 Oswald,sans-serif;text-transform:uppercase}
.filter-menu-close{padding:8px 14px;color:#fff;background:var(--navy);border:0;border-radius:8px;font:900 13px/1 Lato,Arial,sans-serif;letter-spacing:.04em;text-transform:uppercase;cursor:pointer}
.filter-list{flex:1 1 auto;overflow-y:auto;margin:0;padding:0;list-style:none}
.filter-option{display:flex;align-items:center;justify-content:space-between;gap:12px;width:100%;min-height:48px;margin-bottom:8px;padding:10px 12px;border:1px solid var(--line);border-radius:8px;background:var(--card);color:var(--muted);text-align:left;cursor:pointer;-webkit-tap-highlight-color:transparent}
.filter-option:focus-visible{outline:3px solid var(--blue);outline-offset:-3px}
.filter-option-name{font:900 15px/1.2 Lato,Arial,sans-serif}
.filter-option-state{flex:0 0 auto;font:900 11px/1 Lato,Arial,sans-serif;letter-spacing:.06em;text-transform:uppercase}
.filter-option.is-shown{border-color:var(--blue);background:#e8f5fc;color:#0b6c92}
.filter-reset{flex:0 0 auto;min-height:44px;border:1px solid var(--line);border-radius:8px;background:var(--card);color:var(--navy);font:900 13px/1 Lato,Arial,sans-serif;letter-spacing:.04em;text-transform:uppercase;cursor:pointer}
.filter-reset:disabled{opacity:.4;cursor:default}
.landing-menu{position:fixed;inset:0;z-index:60;display:flex}.landing-menu-scrim{position:absolute;inset:0;background:rgba(0,15,37,.5)}
.landing-menu-panel{position:relative;display:flex;flex-direction:column;width:min(360px,86vw);max-height:100%;margin-right:auto;background:var(--canvas);box-shadow:0 0 40px rgba(0,15,37,.4);animation:menu-in .18s ease both}
.landing-menu-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:calc(14px + env(safe-area-inset-top)) 14px 12px;background:var(--card);border-bottom:1px solid var(--line)}.landing-menu-head strong{color:var(--navy);font:650 20px/1 Oswald,sans-serif;text-transform:uppercase}
.landing-menu-close{padding:8px 14px;color:#fff;background:var(--navy);border:0;border-radius:8px;font:900 13px/1 Lato,Arial,sans-serif;letter-spacing:.04em;text-transform:uppercase;cursor:pointer}
/* Timeline view: one sailing per row, ordered by departure. Three lines — time and route, then
   destination, then operator/vessel and status — so a row stays compact enough that many fit on a
   phone. The list scrolls at a fixed row height instead of squishing: an unreadable row helps
   nobody.

   align-items is reset because .departure above centres its children for the kiosk grid, and in a
   column flex box that centres them *horizontally*: the lines shrink to their content and spill
   past both edges of the card instead of filling it. Stretching is what makes the left/right
   alignment below — and the ellipses — work at all. */
.departures[data-view="timeline"]{display:flex;flex-direction:column;gap:.5vh;overflow-y:auto;grid-template-rows:none}
.departure.timeline-row{display:flex;flex-direction:column;align-items:stretch;justify-content:center;gap:.35vh;flex:0 0 auto;padding:.7vh .7vw}
.tl-head{display:flex;align-items:center;gap:.7vw;min-width:0}
.tl-head time{flex:0 0 auto;color:var(--navy);font:650 clamp(17px,2.6vh,26px)/1 Oswald,sans-serif;white-space:nowrap}
.timeline-row .route-badge{flex:0 0 auto}
/* Pushed right, and the only thing on that edge of the top line, so countdowns form a column. */
.tl-relative{flex:0 0 auto;margin-left:auto;color:var(--route-accent,#00749e);font:900 clamp(11px,1.6vh,15px)/1 Lato,Arial,sans-serif;white-space:nowrap}
/* Destination and the turn it ends in, on one line. A row rather than a block so the name takes
   the squeeze on its own: it is the only part long enough to need truncating, and the layover is
   the part that would be meaningless half-shown. */
.tl-dest{display:flex;align-items:baseline;min-width:0;color:var(--navy);font:800 clamp(14px,2.1vh,21px)/1.15 Lato,Arial,sans-serif}
.tl-dest-name{flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tl-via{font-weight:700;color:var(--muted)}
.tl-meta{display:flex;align-items:center;gap:.5vw;min-width:0}
.tl-context{flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--muted);font:900 clamp(9px,1.3vh,12px)/1 Lato,Arial,sans-serif;letter-spacing:.04em;text-transform:uppercase}
/* Vessel name. Only NYC Ferry publishes one, so the separator lives on the element itself and
   disappears with it rather than leaving a stray dot on partner rows. */
.tl-boat{flex:0 1 auto;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#00749e;font:900 clamp(9px,1.35vh,13px)/1 Lato,Arial,sans-serif}
.tl-boat::before{content:"·";margin-right:.35vw;color:var(--line)}
/* A boat carrying nobody should not read as a departure you could take. Muted rather than hidden:
   staff still need it, riders scanning past it should not stop. */
.departure.timeline-row-oos{background:#eef3f6}
.timeline-row-oos .tl-dest{color:#4a5b68}
.tl-status{flex:0 0 auto;display:flex;align-items:center;gap:.35vw;margin-left:auto;white-space:nowrap}
.sort-toggle{display:flex;gap:8px;padding:12px 14px;background:var(--card);border-bottom:1px solid var(--line)}
.sort-option{flex:1 1 0;min-height:44px;padding:8px 10px;color:var(--muted);background:var(--canvas);border:1px solid var(--line);border-radius:8px;font:800 13px/1.15 Lato,Arial,sans-serif;letter-spacing:.03em;text-transform:uppercase;cursor:pointer;-webkit-tap-highlight-color:transparent}
.sort-option:active{background:var(--sunken)}
.sort-option[aria-pressed="true"]{color:#fff;background:var(--navy);border-color:var(--navy)}
.sort-option:focus-visible{outline:3px solid var(--blue);outline-offset:2px}
.landing-list{flex:1;min-height:0;overflow-y:auto;margin:0;padding:8px 0 calc(8px + env(safe-area-inset-bottom));list-style:none;-webkit-overflow-scrolling:touch}
.landing-row{display:flex;align-items:stretch}
.landing-star{flex:0 0 auto;display:grid;place-items:center;width:44px;padding:0 0 0 8px;background:transparent;border:0;color:var(--line);cursor:pointer;-webkit-tap-highlight-color:transparent}
.landing-star-icon{width:22px;height:22px;fill:currentColor}
.landing-star[aria-pressed="true"]{color:#f2b134}
.landing-star:active{color:var(--muted)}.landing-star[aria-pressed="true"]:active{color:#d99a1c}
.landing-star:focus-visible{outline:3px solid var(--blue);outline-offset:-3px}
.landing-option{display:flex;align-items:center;gap:10px;flex:1 1 auto;min-width:0;min-height:48px;padding:9px 14px 9px 10px;color:var(--ink);background:transparent;border:0;font:inherit;text-align:left;cursor:pointer}.landing-option:active{background:var(--sunken)}
.landing-option-name{min-width:0;font-size:15px;font-weight:800;line-height:1.15}
.landing-row:has(.landing-option.is-current){background:var(--card)}.landing-option.is-current .landing-option-name{color:var(--navy)}
body.menu-open{overflow:hidden}
@keyframes menu-in{from{transform:translateX(-12px);opacity:.4}to{transform:none;opacity:1}}
@keyframes slide-in{from{opacity:.25;transform:translateX(1.2vw)}to{opacity:1;transform:none}}@media(prefers-reduced-motion:reduce){.departure,.landing-menu-panel,.nearest-button[data-state="locating"] .nearest-icon{animation:none}}

/* Phone layout. The kiosk board fits a fixed 16:9 screen by squishing rows; a phone can't do
   that, so below this width each departure becomes a full-width row instead of a column and sizes
   switch from vh/vw to px so they stay legible regardless of how tall the handset is.

   The screen itself is the viewport, and only the list inside it scrolls — the same shape the
   tablet and the kiosk use. It used to be the whole page that scrolled, which meant the footer and
   the alert bar were wherever the scroll had left them: reaching the end of a 246-sailing day left
   a stretch of empty canvas with the controls stranded above it, and the alerts nowhere in sight
   until you scrolled for them. Controls that are always in the same place are worth more than
   controls that follow the content. */
@media(max-width:820px){
  /* Pinned to the viewport's own edges rather than measured against it.
     100dvh came up short of the screen on an installed iOS board — the bars sat where dvh said the
     bottom was and a strip of page background showed underneath them. Anchoring top and bottom
     removes the arithmetic: there is no unit to disagree with, the box is the viewport by
     construction. It costs nothing here because this shell never scrolls; only the list inside it
     does. (It also drops width:100vw, which is measured past a scrollbar on some browsers and is
     the other half of the same class of bug.) */
  .screen{position:fixed;inset:0;width:auto;height:auto}
  /* env() keeps the board clear of a notch and the home indicator. The bottom inset moves to
     whichever bar is genuinely last on screen — see the alert bar below. */
  .board{padding:0 max(10px,env(safe-area-inset-left)) 0 max(10px,env(safe-area-inset-right))}
  .board-heading{flex-wrap:wrap;gap:6px 10px;margin:0 -10px;padding:calc(10px + env(safe-area-inset-top)) 12px 8px;background:var(--canvas);border-bottom:1px solid var(--line)}
  /* Hamburger, landing name, the map and the nearest-landing button share the first line; the
     chips wrap onto the second. That wrap is what puts the pair in the top right corner on a phone. */
  .menu-button{order:-1;width:40px;height:40px}
  .nearest-button{height:40px;width:40px;padding:0;font-size:12px}
  .map-button{height:40px;width:40px}
  /* One line, ellipsised, sharing that line with the hamburger and the nearest-landing button.
     flex-basis is 0 rather than auto for a reason worth keeping: a wrapping flex container decides
     its line breaks from each item's hypothetical size, which for basis:auto is the full untruncated
     width of the text. "North Williamsburg / North 5th St" measures wider than the phone, so the
     button was bumped onto a line of its own before any shrinking happened, and the header stood
     three rows tall. At basis 0 the heading asks for nothing, never forces a break, and grows into
     whatever the other two leave — which is what the ellipsis is for.
     margin-right goes with it: an auto margin swallows free space before flex-grow ever sees it,
     so leaving it would have grown the margin and left the heading itself zero wide. */
  .board-heading h1{flex:1 1 0;min-width:0;margin-right:0;font-size:20px;line-height:1.1}
  .board-state{flex:1 1 100%;flex-wrap:wrap;justify-content:flex-start;gap:6px}
  .route-count,.status{padding:4px 7px;font-size:11px}
  /* Thumb-sized, and pinned to the foot of the screen rather than the foot of the list, so the
     controls are under the same thumb wherever the day has been scrolled to. */
  .board-footer{flex:0 0 auto;gap:8px;margin-top:10px}
  .clock-toggle{min-width:64px;min-height:48px;padding:6px 10px;font-size:14px}
  .filter-button{min-width:48px;min-height:48px;padding:6px 10px}
  .filter-icon{width:20px;height:20px}
  .date-step{width:22%;min-height:48px;font-size:22px}
  .date-current{min-height:48px;padding:6px 10px;font-size:14px}
  /* The list keeps its amber edge on a phone precisely because this bar scrolls away with the
     content — without it a long browsed day would look live once it was scrolled past. */
  .screen.browsing-schedule .departures{border-left:3px solid #e3c76a;padding-left:10px}
  /* The clock must never break across lines, so it is the one item that refuses to shrink. */
  .clock{flex:0 0 auto;margin-left:auto;gap:6px;white-space:nowrap}
  .clock time{font-size:20px;white-space:nowrap}
  .clock span{font-size:11px}
  /* Padding slots exist to keep the kiosk grid square. A scrolling phone list doesn't need them. */
  .departure-slot.unavailable{display:none}
  .column-head{display:none}
  .board-note{margin:10px 0 0;padding:8px 10px;border-left-width:5px;border-radius:8px;font-size:12px}
  /* The one thing that scrolls. */
  .departures{flex:1;min-height:0;display:flex;flex-direction:column;gap:10px;padding-top:10px;overflow-y:auto;-webkit-overflow-scrolling:touch}
  /* Each route direction is a card: header on top, departures listed beneath. */
  .departure{display:block;padding:0;border-left-width:5px;border-radius:8px;overflow:hidden}
  .route{gap:8px;padding:9px 10px 0}
  .route-badge{width:52px;height:32px}.route-badge b{font-size:19px}
  .route-name{font-size:11px}.route-operator{font-size:9px;margin-top:2px}
  .destination{padding:2px 10px 9px}
  .destination strong{font-size:19px;white-space:normal}
  .destination span{margin-top:2px;font-size:10px}
  .departure-slots{display:flex;flex-direction:column;height:auto;border-left:0;border-top:1px solid var(--line)}
  /* One departure per row: time and countdown on the first line, status/assignment/vessel below. */
  .departure-slot{gap:3px;padding:8px 10px;border-right:0;border-bottom:1px solid var(--line)}
  .departure-slot:last-child{border-bottom:0}
  .slot-time-row{justify-content:flex-start;gap:8px}
  .slot-time-row time{font-size:21px}
  .slot-relative{font-size:13px}
  .departure-last-slot{gap:5px;min-height:0;flex-wrap:wrap;white-space:normal}
  .departure-last-badge,.vessel-delay-badge,.on-time-badge,.scheduled-badge,.boat-assignment{font-size:12px;padding:3px 7px}
  .boat-name{font-size:13px}
  /* Timeline rows are deliberately fixed and compact: the list scrolls, so the win is fitting
     more sailings per screen rather than stretching each one. */
  .departures[data-view="timeline"]{gap:8px;padding-top:10px}
  .departure.timeline-row{gap:3px;padding:8px 11px}
  .tl-head{gap:8px}
  .tl-head time{font-size:21px}
  /* A crew shuttle window is two clock times in a row, so it needs to be able to shrink. */
  .timeline-row .tl-head time{font-size:clamp(16px,4.6vw,21px)}
  .timeline-row .route-badge{width:46px;height:27px}
  /* Partner marks are wordmarks, not letters, and go unreadable at the plain badge's width. */
  .timeline-row .route-badge-image{width:66px}
  .timeline-row .route-badge b{font-size:16px}
  .timeline-row .route-variant{font-size:9px}
  .tl-relative{font-size:13px}
  .tl-dest{font-size:17px}
  .tl-meta{gap:6px}
  .tl-context{font-size:10px}
  .tl-boat{font-size:11px}
  .tl-boat::before{margin-right:4px}
  .tl-status{gap:5px}
  .timeline-row .departure-last-badge,.timeline-row .vessel-delay-badge,.timeline-row .on-time-badge,
  .timeline-row .scheduled-badge,.timeline-row .boat-assignment{font-size:11px;padding:2px 6px}
  .empty{padding:28px 16px}.empty strong{font-size:20px}.empty span{font-size:13px}
  /* The last thing on the screen, so it is the one that owes the home indicator its clearance —
     but the inset is the floor of that clearance, not something to add to it. Adding put 44px of
     empty bar under two lines of text on a notched phone and read as a gap rather than as the strip
     the indicator sits in. The bar's own background still runs to the bottom edge either way. */
  .service-alert-bar{gap:10px;padding:8px 12px max(8px,env(safe-area-inset-bottom))}
  .service-alert-icon{width:26px;height:26px}
  .service-alert-copy strong,.service-alert-summary{font-size:13px}
  .service-alert-copy small{font-size:10px;white-space:normal}
  .service-alert-count{min-width:28px;min-height:24px;font-size:13px}
  /* A service notice still takes over the whole screen. */
  .manual-override{padding:24px 16px}
  .manual-override-box{padding:22px 16px;border-width:6px}
  .manual-override-label{font-size:13px}
  .manual-override-box p,.manual-override-box[data-size="medium"] p,.manual-override-box[data-size="long"] p{margin:18px 0;font-size:26px}
  .manual-override-box small{font-size:12px}
}
@media(max-width:820px) and (orientation:landscape){
  /* Two cards side by side when a phone is turned sideways. The kiosk's fixed row count is
     cleared so cards keep their natural height and the list scrolls instead. */
  .departures{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));grid-template-rows:none;grid-auto-rows:auto;align-items:start;overflow-y:auto}
}

/* The roomy layout: anything wider than a phone that is not the kiosk.

   The kiosk board is drawn for a fixed 16:9 screen and sizes itself in vh, which is right for the
   one display it was made for and wrong for everything else. Above the phone breakpoint every
   clamp() pins to its maximum, the rows drift apart, and a column of dead space opens down the
   middle of each one. Fixed px sizing puts the rows back to a readable density, and two columns put
   the width back to work.

   This used to be portrait-only and capped at 1200px, so a tablet turned sideways — and every
   desktop — fell back to the kiosk board. Orientation was never the thing that mattered: width is.

   Scoped to data-surface="app" so the signage display itself is untouched. The kiosk is served at
   the site root and the public board under /ferryTimesMobile/, the same split the service worker
   already relies on, and the kiosk keeps its whole screen.

   The two columns read left-to-right then down, which is the order a phone reads in and the order
   the DOM is already in. A time-ordered list down each column would read better still, but only if
   the list's height is known, and it isn't: it changes with the day, the filter and the landing. */
@media(min-width:821px){
  /* iPadOS draws its status bar over the top of a home-screen app. The kiosk has no notch to clear
     and so never made room for one, which is why the landing name sits under the clock and the
     battery. */
  :root[data-surface="app"] .board{padding:calc(4px + env(safe-area-inset-top)) 12px 8px}
  :root[data-surface="app"] .board-heading{gap:10px;padding-bottom:8px}
  /* "Governors Island / Yankee Pier" ellipsised down to "GOVERNO…" once the rail took its width.
     A landing name that cannot be read is not worth the single line it was holding out for. */
  :root[data-surface="app"] .board-heading h1{font-size:23px;line-height:1.08;white-space:normal}
  :root[data-surface="app"] .board-state{gap:8px}
  :root[data-surface="app"] .route-count, :root[data-surface="app"] .status{padding:5px 8px;font-size:12px}
  :root[data-surface="app"] .clock{gap:7px}:root[data-surface="app"] .clock time{font-size:27px}:root[data-surface="app"] .clock span{font-size:12px}
  :root[data-surface="app"] .menu-button, :root[data-surface="app"] .nearest-button, :root[data-surface="app"] .map-button{height:40px}
  :root[data-surface="app"] .menu-button, :root[data-surface="app"] .nearest-button, :root[data-surface="app"] .map-button{width:40px}
  :root[data-surface="app"] .nearest-button{padding:0;font-size:13px}
  :root[data-surface="app"] .departures{gap:8px;padding-top:8px;--route-row:clamp(56px,8vh,72px)}
  /* Wrapping flex rather than a grid. A grid sizes its rows by an algorithm that has the container's
     own height as an input, and at a landing with 246 sailings the rows came out a fraction of the
     height their contents need — every card clipped through the middle of its own text. A flex line
     is as tall as the tallest card on it and has no opinion about the box it is in. */
  :root[data-surface="app"] .departures[data-view="timeline"]{display:flex;flex-direction:row;flex-wrap:wrap;align-content:flex-start;align-items:stretch;gap:8px;grid-template-rows:none}
  :root[data-surface="app"] .departure.timeline-row{flex:0 0 calc(50% - 4px);gap:4px;padding:9px 12px}
  /* 120 cards each running their own entry animation inside a finger-scrolled list is where iOS
     starts handing back half-painted rows. The animation is a nicety; a legible row is not. */
  :root[data-surface="app"] .departures .departure{animation:none}
  :root[data-surface="app"] .tl-head{gap:9px}
  :root[data-surface="app"] .tl-head time{font-size:23px}
  :root[data-surface="app"] .timeline-row .route-badge{width:54px;height:31px}
  :root[data-surface="app"] .timeline-row .route-badge b{font-size:19px}
  :root[data-surface="app"] .timeline-row .route-badge-image{width:76px}
  :root[data-surface="app"] .timeline-row .route-variant{font-size:10px}
  :root[data-surface="app"] .tl-relative{font-size:14px}
  :root[data-surface="app"] .tl-dest{font-size:19px}
  :root[data-surface="app"] .tl-meta{gap:7px}
  :root[data-surface="app"] .tl-context{font-size:11px}
  :root[data-surface="app"] .tl-boat{font-size:12px}
  :root[data-surface="app"] .tl-boat::before{margin-right:5px}
  :root[data-surface="app"] .tl-status{gap:5px}
  :root[data-surface="app"] .timeline-row .departure-last-badge, :root[data-surface="app"] .timeline-row .vessel-delay-badge, :root[data-surface="app"] .timeline-row .on-time-badge, :root[data-surface="app"] .timeline-row .scheduled-badge, :root[data-surface="app"] .timeline-row .boat-assignment, :root[data-surface="app"] .timeline-row .via-terminal-badge, :root[data-surface="app"] .timeline-row .no-pickup-badge, :root[data-surface="app"] .timeline-row .arrival-badge, :root[data-surface="app"] .timeline-row .drop-off-only-badge, :root[data-surface="app"] .timeline-row .drop-off-badge{font-size:12px;padding:2px 6px}
  /* The route board keeps one card per row: its departure slots have nowhere to go in half a
     screen's width.

     Its columns stop being percentages here. 15% of a kiosk is enough for a route badge and its
     name; 15% of a tablet that has just given a quarter of itself to the landing rail is about
     twenty pixels, and a partner wordmark plus overflow-wrap:anywhere turns "HOBOKEN" into a
     column of single letters. A fixed first column is the only one that cannot collapse. */
  :root[data-surface="app"] .column-head, :root[data-surface="app"] .departure{grid-template-columns:172px minmax(0,1fr) minmax(0,2.4fr)}
  :root[data-surface="app"] .route-badge{width:56px;height:34px}:root[data-surface="app"] .route-badge b{font-size:20px}
  :root[data-surface="app"] .route-badge-image{width:66px}
  :root[data-surface="app"] .route-name{font-size:11px}:root[data-surface="app"] .route-operator{font-size:9px}
  /* Three sailings ahead instead of five. The five were overflowing the card and getting sliced
     down their right-hand edge, which is worse than showing fewer and showing them whole. */
  :root[data-surface="app"] .departure-slots{grid-template-columns:repeat(3,minmax(0,1fr))}
  :root[data-surface="app"] .departure-slot:nth-child(n+4){display:none}
  :root[data-surface="app"] .departure-slot{padding:0 8px}
  :root[data-surface="app"] .destination strong{font-size:19px}:root[data-surface="app"] .destination span{font-size:10px}
  :root[data-surface="app"] .via{font-size:11px}
  :root[data-surface="app"] .slot-time-row time{font-size:20px}:root[data-surface="app"] .slot-relative{font-size:13px}
  :root[data-surface="app"] .boat-name{font-size:11px}
  /* Footer controls stay thumb-sized: this is a touchscreen, whatever its width. */
  :root[data-surface="app"] .board-footer{gap:8px;margin-top:8px}
  :root[data-surface="app"] .filter-button{min-width:48px;min-height:48px}
  :root[data-surface="app"] .filter-icon{width:20px;height:20px}
  :root[data-surface="app"] .date-step{min-height:48px;font-size:22px}
  :root[data-surface="app"] .date-current{min-height:48px;font-size:15px}
  :root[data-surface="app"] .clock-toggle{min-width:68px;min-height:48px;font-size:15px}
  :root[data-surface="app"] .service-alert-copy strong, :root[data-surface="app"] .service-alert-summary{font-size:14px}

}

/* Docked landing rail. A tablet is wide enough to keep the landing list on screen permanently, so
   on one the hamburger stops meaning "open a drawer" and starts meaning "hide the rail" — the list
   is there by default and the button takes it away, rather than the other way round.

   Only the panel changes; it is the same markup and the same open/closed state as the phone's
   drawer, which is why picking a landing, Escape and the focus handling all keep working. */
body.sidebar-docked{--rail:clamp(240px,26vw,320px)}
body.sidebar-docked .screen{padding-left:var(--rail)}
/* Width, not inset:0. Left as a full-viewport flex container the menu keeps covering the board even
   with its scrim switched off, and every tap on a departure lands on an invisible sheet instead. */
body.sidebar-docked .landing-menu{z-index:40;right:auto;width:var(--rail)}
/* Nothing behind the rail is blocked, so there is nothing to dim and nothing to tap through. */
body.sidebar-docked .landing-menu-scrim{display:none}
body.sidebar-docked .landing-menu-panel{width:var(--rail);height:100%;max-height:100%;animation:none;box-shadow:none;border-right:1px solid var(--line)}
body.sidebar-docked .landing-menu-head{padding-top:calc(14px + env(safe-area-inset-top))}
/* A docked rail scrolls its own list. The page behind it must stay scrollable. */
body.sidebar-docked.menu-open{overflow:visible}

/* East River runs three variants and the badge is one fixed width, sized for a route letter and a
   single-character chip beside it. A and B fit. LOCAL is five characters and was being cut off
   against the badge's own edge, so only that variant is allowed to widen to what it is naming —
   the others keep their width, and the column of badges beside them stays a column.

   Three classes deep on purpose: the phone and tablet layers both set a fixed .route-badge width,
   and this has to outrank them without being repeated inside each one. */
.departure.variant-local .route-badge{width:auto;padding:0 5px;gap:4px}

/* The alert bar's own affordance: a caret that only appears when there is in fact more behind it.
   It sits after the count so the two read as one control — how many, and "there is more". */
.service-alert-chevron{width:clamp(16px,2.2vh,20px);height:clamp(16px,2.2vh,20px);fill:none;stroke:currentColor;stroke-width:2.6;stroke-linecap:round;stroke-linejoin:round}

/* The full list of alerts. Rows are readable prose rather than switches, so unlike the filter and
   theme sheets these are not buttons and are allowed to be as tall as their text needs. */
.alert-list{display:flex;flex-direction:column;gap:14px}
.alert-group{display:flex;flex-direction:column;gap:8px}
/* Whose service this is. Sticky so it stays overhead while a long run of subway closures scrolls
   under it — the one question this heading answers is the one you lose track of first. */
.alert-group-name{position:sticky;top:0;z-index:1;display:flex;align-items:center;gap:8px;margin:0;padding:4px 0;background:var(--canvas);color:var(--navy);font:900 12px/1 Lato,Arial,sans-serif;letter-spacing:.1em;text-transform:uppercase}
.alert-group-name span{display:grid;place-items:center;min-width:20px;height:20px;padding:0 6px;color:#fff;background:var(--navy);border-radius:999px;font-size:11px}
.alert-group-list{display:flex;flex-direction:column;gap:8px;margin:0;padding:0;list-style:none}
.alert-item{display:flex;flex-direction:column;gap:6px;padding:12px 14px;background:var(--card);border:1px solid var(--line);border-left:4px solid var(--warning);border-radius:8px}
.alert-item-header{color:var(--navy);font:900 15px/1.25 Lato,Arial,sans-serif}
/* The dates a planned closure covers. Sits between the headline and the chips because it is read
   as part of the headline: "no L trains" is only half a fact without "and when". */
.alert-when{color:var(--muted);font:700 13px/1.3 Lato,Arial,sans-serif}
.alert-chips{display:flex;flex-wrap:wrap;gap:6px}
.alert-chip{display:inline-flex;align-items:center;padding:2px 7px;color:var(--navy);background:var(--sunken);border:1px solid var(--line);border-radius:999px;font:900 11px/1.4 Lato,Arial,sans-serif;letter-spacing:.04em;text-transform:uppercase}
/* The one place on the board where a wall of feed text is the point, so it wraps and stays put
   rather than being clamped to two lines the way the bar clamps it.
   pre-wrap because the crew notice board arrives as sections with real line breaks in them, and
   collapsing those runs three headings and eight notices into one paragraph. Every other alert is a
   single run of text with no newlines in it, so nothing else renders differently for this. */
.alert-item-body{margin:0;color:var(--ink);font:800 14px/1.4 Lato,Arial,sans-serif;overflow-wrap:anywhere;white-space:pre-wrap}
.alert-link{align-self:flex-start;color:var(--blue);font:900 13px/1 Lato,Arial,sans-serif;text-decoration:underline}
.alert-link:focus-visible{outline:3px solid var(--blue);outline-offset:2px}
.alert-empty{padding:14px;color:var(--muted);font:800 14px/1.4 Lato,Arial,sans-serif;text-align:center}

/* Theme picker. Borrows the filter sheet's shape and rows wholesale — same button size, same
   bottom sheet, same option — because it sits next to it and does the same class of thing. The
   only new part is the second line under each name, which is what a theme needs and an operator
   switch does not. */
.theme-button .filter-icon{fill:none}

/* The change log button, in the landing drawer's header beside its title. The site's own kitty
   rather than a drawn glyph, because it is the one thing in that drawer that is not a landing.
   The auto margin is what keeps it next to the title: the header is space-between, and without it
   three children would spread evenly and leave the kitty stranded in the middle. */
.changelog-button{flex:0 0 auto;position:relative;display:grid;place-items:center;margin-right:auto;padding:0;background:none;border:0;cursor:pointer;-webkit-tap-highlight-color:transparent}
.changelog-button:focus-visible{outline:3px solid var(--blue);outline-offset:3px}
.changelog-icon{width:26px;height:26px;display:block}
/* The mark that says there is something unread. Sits where the filter's count sits, in the same
   place and at the same size, because it means the same kind of thing. */
.changelog-bang{position:absolute;top:-6px;right:-6px;min-width:16px;height:16px;padding:0 3px;border-radius:8px;background:#c8102e;color:#fff;font:900 11px/16px Lato,Arial,sans-serif;text-align:center}
.changelog-list{display:flex;flex-direction:column;gap:12px}
.changelog-entry{display:flex;flex-direction:column;gap:6px;padding:12px 14px;background:var(--card);border:1px solid var(--line);border-left:4px solid var(--blue);border-radius:8px}
.changelog-entry-head{color:var(--navy);font:900 14px/1.25 Lato,Arial,sans-serif;letter-spacing:.02em}
/* No bullets. This log is written by hand and printed as written: whoever writes it marks their own
   lines, and a disc in front of a line that already starts with a dash reads as a mistake. */
.changelog-notes{display:flex;flex-direction:column;gap:5px;margin:0;padding:0;list-style:none}
.changelog-notes li{color:var(--ink);font:800 14px/1.4 Lato,Arial,sans-serif;overflow-wrap:anywhere}
.theme-option .filter-option-name{display:flex;flex-direction:column;gap:3px}
.theme-option-note{font:800 11px/1.2 Lato,Arial,sans-serif;color:var(--muted);letter-spacing:.02em}
.theme-option.is-shown .theme-option-note{color:inherit;opacity:.8}

/* ============================================================
   Hello Kitty. The palette and the chunky ink-outlined cards are lifted from juliet.nyc, retuned
   to the one thing this board must never lose: a departure has to stay readable across a terminal
   at a glance. Only the chrome is repainted — route colours, status badges and every layout rule
   above are left exactly as they are, because a rider reading ON TIME green or a LAST yellow is
   reading service information, not decoration.
   ============================================================ */
:root[data-theme="hello-kitty"]{
  --pink-100:#fff5f8;--pink-200:#ffe3ec;--pink-300:#ffc9db;--pink-400:#ff9dbb;--pink-500:#ff6f9c;
  /* Standing in for --navy, so every heading, badge and pressed control comes through together.
     Deeper than juliet's #e8455f: that is a headline colour on a big screen, and this has to hold
     up at 11px on a phone in daylight. */
  --navy:#8c1038;
  --blue:#d81e5b;
  --ink:#3b2b33;
  --muted:#7a626c;
  --canvas:#fff5f8;
  --line:#ffc9db;
  --sunken:#ffe3ec;
  font-family:"Trebuchet MS","Segoe UI",Verdana,sans-serif;
}
/* Oswald is the signage face and belongs to the ferry livery. Everywhere it is set above, the
   pink board uses the same rounded stack as the rest of itself. */
:root[data-theme="hello-kitty"] .board-heading h1,
:root[data-theme="hello-kitty"] .clock time,
:root[data-theme="hello-kitty"] .route-badge b,
:root[data-theme="hello-kitty"] .slot-time-row time,
:root[data-theme="hello-kitty"] .tl-head time,
:root[data-theme="hello-kitty"] .filter-menu-head strong,
:root[data-theme="hello-kitty"] .landing-menu-head strong,
:root[data-theme="hello-kitty"] .boat-assignment{font-family:"Trebuchet MS","Segoe UI",Verdana,sans-serif}
/* The polka dots from the site's body, pinned behind the board. Fixed so a scrolling phone list
   slides over a still background rather than dragging the pattern with it. */
:root[data-theme="hello-kitty"] body{
  background:
    radial-gradient(circle at 12% 8%,#fff 0 5px,transparent 6px) 0 0/64px 64px,
    radial-gradient(circle at 62% 42%,#fff 0 4px,transparent 5px) 0 0/64px 64px,
    linear-gradient(180deg,var(--pink-200),var(--pink-100) 420px) fixed;
}
/* Cards get the site's outline-and-drop-shadow treatment. The route colour keeps the left edge:
   it is the fastest read on the card and nothing decorative is allowed to take it. */
:root[data-theme="hello-kitty"] .departure{
  border:2px solid var(--ink);border-left:.45vw solid var(--route-color);border-radius:14px;
  box-shadow:0 3px 0 var(--pink-300);
}
:root[data-theme="hello-kitty"] .empty{border-color:var(--ink);border-radius:14px;box-shadow:0 3px 0 var(--pink-300)}
/* Controls: rounder, outlined in ink, and sitting on a pink shadow the way the site's buttons do. */
:root[data-theme="hello-kitty"] .menu-button,
:root[data-theme="hello-kitty"] .nearest-button,
:root[data-theme="hello-kitty"] .clock-toggle,
:root[data-theme="hello-kitty"] .filter-button,
:root[data-theme="hello-kitty"] .date-bar,
:root[data-theme="hello-kitty"] .filter-option,
:root[data-theme="hello-kitty"] .filter-reset,
:root[data-theme="hello-kitty"] .sort-option{border:2px solid var(--ink);border-radius:12px}
:root[data-theme="hello-kitty"] .filter-menu-close,
:root[data-theme="hello-kitty"] .landing-menu-close{border-radius:999px;box-shadow:0 3px 0 var(--pink-400)}
:root[data-theme="hello-kitty"] .route-count,
:root[data-theme="hello-kitty"] .status{border-color:var(--pink-400);border-radius:999px}
:root[data-theme="hello-kitty"] .filter-menu-panel,
:root[data-theme="hello-kitty"] .landing-menu-panel{border:2px solid var(--ink)}
:root[data-theme="hello-kitty"] .filter-menu-panel{border-radius:18px 18px 0 0}
:root[data-theme="hello-kitty"] .filter-menu-scrim,
:root[data-theme="hello-kitty"] .landing-menu-scrim{background:rgba(59,43,51,.45)}
/* Pressed states come free from --sunken above. Selected ones read pink rather than the ferry's
   cyan. */
:root[data-theme="hello-kitty"] .clock-toggle[aria-pressed="true"],
:root[data-theme="hello-kitty"] .filter-button.is-filtering,
:root[data-theme="hello-kitty"] .filter-option.is-shown,
:root[data-theme="hello-kitty"] .nearest-button[data-state="ready"]{border-color:var(--blue);background:var(--pink-200);color:var(--navy)}
:root[data-theme="hello-kitty"] .nearest-button[data-state="ready"] .nearest-icon{stroke:var(--navy)}
/* Countdowns and vessel names are the board's own accent colour; on this theme they follow the
   pink rather than staying the one blue thing left on the card. */
:root[data-theme="hello-kitty"] .slot-relative,
:root[data-theme="hello-kitty"] .tl-relative,
:root[data-theme="hello-kitty"] .boat-name,
:root[data-theme="hello-kitty"] .tl-boat{color:#b3164a}
:root[data-theme="hello-kitty"] .scheduled-badge{background:var(--pink-200);border-color:var(--pink-400)}
:root[data-theme="hello-kitty"] .departure.timeline-row-oos{background:var(--pink-200)}
:root[data-theme="hello-kitty"] .timeline-row-oos .tl-dest{color:var(--muted)}
:root[data-theme="hello-kitty"] .service-alert-bar{background:var(--pink-100);border-top-color:var(--pink-400)}
:root[data-theme="hello-kitty"] .manual-override{background:linear-gradient(135deg,#8c1038 0%,#e8455f 100%)}
/* The rail's own edge, and the sticky phone heading, both sit on the polka dots: they need the
   board's flat canvas under them or the pattern shows through the type. */
:root[data-theme="hello-kitty"] .board-heading{background:var(--canvas)}
:root[data-theme="hello-kitty"] .landing-menu-head,
:root[data-theme="hello-kitty"] .sort-toggle{background:var(--pink-100)}
:root[data-theme="hello-kitty"] .landing-star[aria-pressed="true"]{color:var(--pink-500)}

/* ============================================================
   The simple themes. Palette only — no borrowed borders or drop shadows, because the outlined card
   is Hello Kitty's own signature and repeating it would make four themes that all look like one.
   Each is a --card, a --canvas and a heading colour; everything else falls out of the variables.
   ============================================================ */

/* Night. The one theme that needed --card to exist: a dark board with white departure cards is not
   a night mode, it is a lightbulb in the wheelhouse. Blue-grey rather than black, because a pure
   black panel in a dark wheelhouse haloes every white glyph on it. */
:root[data-theme="night"]{
  --navy:#7fd4ff;--blue:#4db8e8;--ink:#dbe7ef;--muted:#8ba3b4;
  --canvas:#0d1b26;--card:#14242f;--line:#2a4050;--sunken:#1d3341;
}
/* The card's lift came from a shadow darker than the page. On a dark page nothing is darker, so the
   edge does the work instead. */
:root[data-theme="night"] .departure{box-shadow:none;border:1px solid var(--line);border-left:.45vw solid var(--route-color)}
:root[data-theme="night"] .scheduled-badge{background:#22384a;border-color:var(--line)}
:root[data-theme="night"] .board-note,
:root[data-theme="night"] .approx{color:#e8b45c}
:root[data-theme="night"] .board-note{border-left-color:#e8b45c;box-shadow:none;border:1px solid var(--line);border-left:.45vw solid #e8b45c}
:root[data-theme="night"] .slot-relative,
:root[data-theme="night"] .tl-relative,
:root[data-theme="night"] .boat-name,
:root[data-theme="night"] .tl-boat{color:#6fc7ef}
:root[data-theme="night"] .departure.timeline-row-oos{background:#101f2a}
:root[data-theme="night"] .timeline-row-oos .tl-dest{color:var(--muted)}
:root[data-theme="night"] .service-alert-bar{color:var(--ink);background:#152a33;border-top-color:#8a7320}
:root[data-theme="night"] .service-alert-bar.active{color:#3a2c00;background:#e8cf7a}
:root[data-theme="night"] .service-alert-bar.stale{background:#1e3038}
:root[data-theme="night"] .service-alert-bar.unavailable{color:var(--muted);background:#16252e}
:root[data-theme="night"] .manual-override{background:linear-gradient(135deg,#08131b 0%,#123047 100%)}
/* The browsed-day amber has to stay obviously not-live, but the daylight tint is a lantern here. */
:root[data-theme="night"] .date-bar[data-state="browsing"]{background:#3a2f10;border-color:#8a7320}
:root[data-theme="night"] .date-bar[data-state="browsing"] .date-current{color:#ffd98a;border-inline-color:#6d5a1a}
:root[data-theme="night"] .date-bar[data-state="browsing"] .date-step{color:#c9a94a}
/* The rail and the sticky phone heading sit on --canvas, which is now darker than the cards. */
:root[data-theme="night"] .landing-menu-head,
:root[data-theme="night"] .sort-toggle{background:var(--card)}
:root[data-theme="night"] .sort-option{background:var(--canvas)}
:root[data-theme="night"] .filter-menu-scrim,
:root[data-theme="night"] .landing-menu-scrim{background:rgba(0,0,0,.62)}
:root[data-theme="night"] .empty{background:var(--card)}
/* Route badges keep their livery colours, but the navy-text ones (SB yellow, RES/RWS cyan) were
   drawn against a white card and their inset hairline now reads as a scratch. */
:root[data-theme="night"] .route-badge{box-shadow:none}

/* Cinnamoroll. The calmest of the four: white cards, sky over them. */
:root[data-theme="cinnamoroll"]{
  --navy:#2b5f8f;--blue:#7ec8f0;--ink:#2f4356;--muted:#6d8296;
  --canvas:#f2f9ff;--line:#cfe8fa;--sunken:#e3f2fd;
}
:root[data-theme="cinnamoroll"] .clock-toggle[aria-pressed="true"],
:root[data-theme="cinnamoroll"] .filter-button.is-filtering,
:root[data-theme="cinnamoroll"] .filter-option.is-shown,
:root[data-theme="cinnamoroll"] .nearest-button[data-state="ready"]{border-color:#7ec8f0;background:#e3f2fd;color:var(--navy)}
:root[data-theme="cinnamoroll"] .landing-star[aria-pressed="true"]{color:#7ec8f0}

/* Pompompurin. Butter yellow with the beret's brown carrying the type — the yellow itself is far
   too pale to read against a white card. */
:root[data-theme="pompompurin"]{
  --navy:#7a4a12;--blue:#d99a1c;--ink:#4a3517;--muted:#8a7355;
  --canvas:#fffaf0;--line:#ffe08a;--sunken:#fff2cc;
}
:root[data-theme="pompompurin"] .clock-toggle[aria-pressed="true"],
:root[data-theme="pompompurin"] .filter-button.is-filtering,
:root[data-theme="pompompurin"] .filter-option.is-shown,
:root[data-theme="pompompurin"] .nearest-button[data-state="ready"]{border-color:#d99a1c;background:#fff2cc;color:var(--navy)}
:root[data-theme="pompompurin"] .landing-star[aria-pressed="true"]{color:#e8b422}
/* The LAST badge is the board's own yellow and disappears into this one. Its border is what keeps
   it a badge rather than a smudge, so it gets a darker one here. */
:root[data-theme="pompompurin"] .departure-last-badge{border-color:#8a5a12}

/* Kuromi. Purple and a pink that is allowed to be loud, because it is only ever an accent. */
:root[data-theme="kuromi"]{
  --navy:#4a2d6b;--blue:#b06fd6;--ink:#2e2338;--muted:#736683;
  --canvas:#f6f2fa;--line:#d9c9ec;--sunken:#ece0f7;
}
:root[data-theme="kuromi"] .clock-toggle[aria-pressed="true"],
:root[data-theme="kuromi"] .filter-button.is-filtering,
:root[data-theme="kuromi"] .filter-option.is-shown,
:root[data-theme="kuromi"] .nearest-button[data-state="ready"]{border-color:#b06fd6;background:#ece0f7;color:var(--navy)}
:root[data-theme="kuromi"] .slot-relative,
:root[data-theme="kuromi"] .tl-relative,
:root[data-theme="kuromi"] .boat-name,
:root[data-theme="kuromi"] .tl-boat{color:#8e3fb0}
:root[data-theme="kuromi"] .landing-star[aria-pressed="true"]{color:#ff6fae}

/* Burger King. The 2021 rebrand's own palette -- flame red, flaming orange, BBQ brown on a mayo
   cream board -- with Flame set on everything the ferry livery sets in Oswald. The brown carries
   the headings rather than the red: red at 11px on cream is a shout, and this board is read in
   daylight by somebody with a boat to catch. */
:root[data-theme="burger-king"]{
  --navy:#502314;
  --blue:#D62300;
  --ink:#3B1B0F;
  --muted:#8A6A4F;
  --canvas:#F5EBDC;
  --card:#FFFDF8;
  --line:#E7D3B8;
  --sunken:#F7E4CC;
  --warning:#FFAA00;
  font-family:Flame,"Trebuchet MS",Verdana,sans-serif;
}
/* Flame everywhere the board would otherwise set Oswald, plus the headings it sets in Lato Black.
   Same list the pink board overrides, for the same reason: those are the signage faces. */
:root[data-theme="burger-king"] .board-heading h1,
:root[data-theme="burger-king"] .clock time,
:root[data-theme="burger-king"] .route-badge b,
:root[data-theme="burger-king"] .slot-time-row time,
:root[data-theme="burger-king"] .tl-head time,
:root[data-theme="burger-king"] .tl-dest,
:root[data-theme="burger-king"] .trip-conn-time,
:root[data-theme="burger-king"] .trip-stop-time,
:root[data-theme="burger-king"] .filter-menu-head strong,
:root[data-theme="burger-king"] .landing-menu-head strong,
:root[data-theme="burger-king"] .boat-assignment{font-family:Flame,"Trebuchet MS",Verdana,sans-serif}
/* The flame-grill gradient off the top of the board, the way the bag and the cups carry it. Fixed
   so a scrolling list slides over it rather than dragging it along. */
:root[data-theme="burger-king"] body{
  background:linear-gradient(180deg,#FF8732 0,#F5EBDC 38%) fixed;
}
/* Rounder than the default board and softer at the edge -- the rebrand's whole shape language is
   the bun. */
:root[data-theme="burger-king"] .departure{border-radius:14px;box-shadow:0 2px 0 #E7D3B8}
:root[data-theme="burger-king"] .empty{border-radius:14px}
:root[data-theme="burger-king"] .menu-button,
:root[data-theme="burger-king"] .nearest-button,
:root[data-theme="burger-king"] .clock-toggle,
:root[data-theme="burger-king"] .date-bar,
:root[data-theme="burger-king"] .filter-button,
:root[data-theme="burger-king"] .sort-toggle,
:root[data-theme="burger-king"] .filter-menu-panel,
:root[data-theme="burger-king"] .landing-menu-panel{border-radius:14px}
:root[data-theme="burger-king"] .scheduled-badge{background:#F7E4CC;border-color:#E7D3B8}
/* Countdowns and vessel names in the flame red, the way the other themes take their own accent. */
:root[data-theme="burger-king"] .slot-relative,
:root[data-theme="burger-king"] .tl-relative,
:root[data-theme="burger-king"] .boat-name,
:root[data-theme="burger-king"] .tl-boat{color:#D62300}

/* ============================================================
   Windows XP. Luna: the beige-grey control face, the blue title bar, the 3px rounded buttons with
   their dark blue outline. Tahoma is the face XP actually shipped its UI in and is still present on
   every Windows machine; the fallbacks are the nearest thing elsewhere.
   ============================================================ */
:root[data-theme="windows-xp"]{
  --navy:#0a246a;--blue:#316ac5;--ink:#000;--muted:#4d4d4d;
  --canvas:#ece9d8;--card:#fff;--line:#aca899;--sunken:#d4d0c8;
}
/* Two type elements deep so it outranks the base rules' `font:` shorthand, which resets the family
   along with everything else and would otherwise win on specificity. */
html[data-theme="windows-xp"] body *{font-family:Tahoma,"Segoe UI",Verdana,Geneva,sans-serif}
/* The Luna button: a dark blue hairline, 3px corners, and a face that lifts from white at the top
   to the control grey at the bottom. */
html[data-theme="windows-xp"] body .menu-button,
html[data-theme="windows-xp"] body .nearest-button,
html[data-theme="windows-xp"] body .clock-toggle,
html[data-theme="windows-xp"] body .filter-button,
html[data-theme="windows-xp"] body .filter-reset,
html[data-theme="windows-xp"] body .sort-option,
html[data-theme="windows-xp"] body .date-bar{
  border:1px solid #003c74;border-radius:3px;background:linear-gradient(180deg,#fff 0,#f2f0e6 46%,#ece9d8 100%)
}
:root[data-theme="windows-xp"] .departure,
:root[data-theme="windows-xp"] .empty,
:root[data-theme="windows-xp"] .filter-option{border:1px solid var(--line);border-radius:3px;box-shadow:none}
:root[data-theme="windows-xp"] .departure{border-left:.45vw solid var(--route-color)}
/* Selection is Luna's blue with white on it, everywhere something is chosen. */
:root[data-theme="windows-xp"] .clock-toggle[aria-pressed="true"],
:root[data-theme="windows-xp"] .filter-button.is-filtering,
:root[data-theme="windows-xp"] .filter-option.is-shown,
:root[data-theme="windows-xp"] .nearest-button[data-state="ready"],
:root[data-theme="windows-xp"] .sort-option[aria-pressed="true"]{
  border-color:#003c74;background:linear-gradient(180deg,#4b96f3 0,#1c66d6 100%);color:#fff
}
:root[data-theme="windows-xp"] .nearest-button[data-state="ready"] .nearest-icon{stroke:#fff}
/* The title bar. This is the one flourish worth spending: nothing says XP faster. */
:root[data-theme="windows-xp"] .landing-menu-head,
:root[data-theme="windows-xp"] .filter-menu-head{
  background:linear-gradient(180deg,#0058ee 0,#3f8cf3 8%,#0058ee 40%,#0054e3 88%,#003ec9 100%);border-bottom:0
}
:root[data-theme="windows-xp"] .landing-menu-head strong,
:root[data-theme="windows-xp"] .filter-menu-head strong{color:#fff;text-shadow:1px 1px 1px rgba(0,0,0,.45)}
/* The close button becomes the title bar's own red X button. */
:root[data-theme="windows-xp"] .landing-menu-close,
:root[data-theme="windows-xp"] .filter-menu-close{
  background:linear-gradient(180deg,#e8846b 0,#d75f42 42%,#b8341a 100%);border:1px solid #a02f14;border-radius:3px
}
:root[data-theme="windows-xp"] .filter-menu-panel,
:root[data-theme="windows-xp"] .landing-menu-panel{border:1px solid #003c74;border-radius:0}
:root[data-theme="windows-xp"] .sort-toggle{background:var(--canvas)}
:root[data-theme="windows-xp"] .landing-star[aria-pressed="true"]{color:#f0b400}

/* ============================================================
   Hacker. Green phosphor on black. Fixedsys is the ask and is named first, but it is a bitmap face
   that ships with Windows and cannot be webfonted here, so it only resolves on a machine that has
   it — everywhere else this falls through to the nearest terminal faces rather than silently
   landing on Arial.
   ============================================================ */
:root[data-theme="hacker"]{
  --navy:#00ff41;--blue:#00ff41;--ink:#2be82b;--muted:#1f9c33;
  --canvas:#000;--card:#050d05;--line:#0f3d0f;--sunken:#0a2410;
}
html[data-theme="hacker"] body *{
  font-family:Fixedsys,"Fixedsys Excelsior","Courier New",Consolas,"Lucida Console",monospace
}
/* A CRT does not cast a shadow, it bleeds light. */
:root[data-theme="hacker"] .departure{box-shadow:none;border:1px solid var(--line);border-left:.45vw solid var(--route-color)}
:root[data-theme="hacker"] .board-heading h1,
:root[data-theme="hacker"] .clock time{text-shadow:0 0 8px rgba(0,255,65,.45)}
:root[data-theme="hacker"] .empty{background:var(--card)}
:root[data-theme="hacker"] .scheduled-badge{color:var(--ink);background:#06220c;border-color:var(--line)}
:root[data-theme="hacker"] .board-note,
:root[data-theme="hacker"] .approx{color:#e8e83a}
:root[data-theme="hacker"] .board-note{border-left-color:#e8e83a;box-shadow:none;border:1px solid var(--line);border-left:.45vw solid #e8e83a}
:root[data-theme="hacker"] .slot-relative,
:root[data-theme="hacker"] .tl-relative,
:root[data-theme="hacker"] .boat-name,
:root[data-theme="hacker"] .tl-boat{color:#19c93a}
:root[data-theme="hacker"] .departure.timeline-row-oos{background:#040a04}
:root[data-theme="hacker"] .timeline-row-oos .tl-dest{color:var(--muted)}
:root[data-theme="hacker"] .service-alert-bar{color:var(--ink);background:#04140a;border-top-color:#1f9c33}
:root[data-theme="hacker"] .service-alert-bar.active{color:#001a06;background:#00e03a}
:root[data-theme="hacker"] .service-alert-bar.stale{background:#08180c}
:root[data-theme="hacker"] .service-alert-bar.unavailable{color:var(--muted);background:#060f06}
:root[data-theme="hacker"] .manual-override{background:linear-gradient(135deg,#000 0,#04200f 100%)}
/* The browsed-day tint has to stay obviously not-live without becoming the only warm thing on a
   black screen, so it says so in the same phosphor, dimmed and outlined instead. */
:root[data-theme="hacker"] .date-bar[data-state="browsing"]{background:#1a1a04;border-color:#8a8a20}
:root[data-theme="hacker"] .date-bar[data-state="browsing"] .date-current{color:#e8e83a;border-inline-color:#6d6d18}
:root[data-theme="hacker"] .date-bar[data-state="browsing"] .date-step{color:#b8b830}
:root[data-theme="hacker"] .landing-menu-head,
:root[data-theme="hacker"] .sort-toggle{background:var(--card)}
:root[data-theme="hacker"] .sort-option{background:var(--canvas)}
:root[data-theme="hacker"] .filter-menu-scrim,
:root[data-theme="hacker"] .landing-menu-scrim{background:rgba(0,0,0,.78)}
:root[data-theme="hacker"] .route-badge{box-shadow:none}
:root[data-theme="hacker"] .landing-star[aria-pressed="true"]{color:#00ff41}
