/* ============================================================================
   M/Y Friendship — design system
   A working instrument: log book / tide table / gauge panel. Precision + space,
   no ornament. Six colours, IBM Plex in three cuts, tabular figures everywhere.
   Loaded on every page by app-shell.js. Tokens map onto the legacy variable
   names too, so pages pick up the palette even before their markup is converted.
   ============================================================================ */

/* ---- fonts (self-hosted; work offline, faithful in print) ---- */
@font-face{font-family:'Plex Sans';src:url('/fonts/plex-sans-400.woff2') format('woff2');font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:'Plex Sans';src:url('/fonts/plex-sans-500.woff2') format('woff2');font-weight:500;font-style:normal;font-display:swap}
@font-face{font-family:'Plex Cond';src:url('/fonts/plex-cond-600.woff2') format('woff2');font-weight:600;font-style:normal;font-display:swap}
@font-face{font-family:'Plex Mono';src:url('/fonts/plex-mono-400.woff2') format('woff2');font-weight:400;font-style:normal;font-display:swap}

/* ---- tokens ---- */
:root{
  /* the six */
  --danger:#B3372B; --ink:#12171C; --paper:#F5F6F4; --surface:#FFFFFF; --slate:#5A646E; --rule:#DDE1DE; --marine:#0F5C58;
  /* status — only ever for status */
  --amber:#B4761A; --signal:#9E3320; --on-amber:#1a1200;   /* ink on an amber fill (CR-8) */
  /* Birthday theme palette. Tokens, not inline hex, so it lives in the same layer as every other
     colour. Deliberately no red anywhere: red is the alert/overdue state and must keep meaning that. */
  --bt-1:#2A8F88; --bt-2:#D69A47; --bt-3:#6E7FB8; --bt-4:#7FA88C; --bt-5:#B08BB8;
  /* derived */
  --marine-tint:#E7F0EF;      /* editable / selected wash */
  --marine-ink:#0C4A47;       /* pressed */
  --on-marine:#FFFFFF;        /* text on marine */
  --scrim:rgba(18,23,28,.45);
  /* status-bar cap: painted over env(safe-area-inset-top). black-translucent renders the iOS
     status-bar text WHITE, so the strip behind it must be dark even in the light theme. */
  --statusbar-cap:#12171C;

  /* legacy aliases so unconverted pages inherit the palette */
  --bg:var(--paper); --card:var(--surface); --line:var(--rule); --muted:var(--slate);
  --accent:var(--marine); --accent-ink:var(--marine-ink); --panel:var(--paper);
  --ok:var(--marine); --warn:var(--amber); --urgent:var(--signal); --high:var(--amber);
  --editable:var(--marine-tint); --lock:var(--slate); --work:var(--marine);

  /* geometry */
  --sp-1:8px; --sp-2:16px; --sp-3:24px; --sp-4:32px; --sp-6:48px;
  --pad-screen:20px; --gap-section:32px; --pad-card:20px;
  --r-card:12px; --r-ctrl:10px; --r-pill:999px;
  --tap:48px;
  --tabbar-h:56px;
  /* ---- THE Z-INDEX SCALE. Seven steps, one place. Every z-index in the codebase is one of these
     tokens, or a calc() offset from one where two things must sit strictly either side of a step
     (a sticky in-page bar is header - 1; a lightbox opened from inside a sheet is sheet + 1).
     Nothing is a bare number any more, so the next overlay cannot be added above the drawer by
     accident — it has to name the step it belongs to.
       map       the Leaflet box. Leaflet's own stylesheet puts its panes at 200-700 and its
                 controls at 800-1000; those are contained INSIDE an isolated map box (see
                 .pos-map, #mapwrap, .gmap) and never escape onto this scale. This is the fix for
                 the map painting over the captain menu: the map box was position:relative with no
                 z-index, so it created no stacking context, and Leaflet's 400/1000 joined the ROOT
                 context above the sticky header (50) that the menu lives inside.
       card      raised elements within a card or a container: swipe surfaces, sticky sheet bars,
                 the status-bar cap, a table's frozen corner.
       header    the sticky app header (and, at header - 1, sticky in-page bars: a totals bar, a
                 form's sticky head, a save bar — under the header, over the page).
       backdrop  full-screen layers that cover the page AND the header but stay under the drawer
                 and the tab bar: the birthday layer, a drawer scrim if one is ever drawn.
       drawer    the captain menu, the tab bar, the pull-to-refresh indicator.
       sheet     bottom sheets, modals, full-screen viewers, camera. Sheet + 1 is a viewer opened
                 from INSIDE a sheet (a photo lightbox), which must clear the sheet it came from.
       toast     toasts, the outbox overlay, the preview banner, the boot splash — above all.
     Inside an isolated map box the numbers are Leaflet's, not ours (its pane and control tiers),
     so the two values that sit between Leaflet tiers stay on that scale and say so. */
  --z-map:1; --z-card:2; --z-header:50; --z-backdrop:60; --z-drawer:70; --z-sheet:200; --z-toast:300;
  --safe-b:env(safe-area-inset-bottom,0px);
  --safe-t:env(safe-area-inset-top,0px);
  --font-sans:'Plex Sans',-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;
  --font-cond:'Plex Cond',var(--font-sans);
  --font-mono:'Plex Mono',ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
}
/* `.dark-surface` carries the SAME palette onto an element that is dark whatever the app theme is —
   a camera viewfinder, a photo lightbox. Those surfaces are black by nature, so in day theme they
   would otherwise draw dark-on-dark: --slate #5A646E and --marine #0F5C58 all but vanish against a
   viewfinder, and the lock signal the crew are watching for is the first thing lost. Declared on the
   element, so its subtree inherits the night values while the page around it stays in day. */
:root[data-theme="night"],:root[data-theme="dark"],.dark-surface{
  --danger:#D06055; --ink:#EAECE9; --paper:#12171C; --surface:#191F25; --slate:#9AA4AD; --rule:#2A323A; --marine:#2A8F88;
  --amber:#D69A47; --signal:#D0674F;
  /* Muted against the dark ground — these are softened, not brightened. Still no red. */
  --bt-1:#2F9A92; --bt-2:#C9923F; --bt-3:#6B7CB4; --bt-4:#79A386; --bt-5:#A886B0;
  --marine-tint:#123033; --marine-ink:#38A69E; --on-marine:#08110F; --scrim:rgba(0,0,0,.6);
  --statusbar-cap:#191F25;   /* == night --surface: seamless with the already-dark header */
}

/* ---- reset / base ---- */
*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0; background:var(--paper); color:var(--ink);
  font-family:var(--font-sans); font-size:17px; line-height:1.47; font-weight:400;
  font-variant-numeric:tabular-nums; -webkit-font-smoothing:antialiased; text-rendering:optimizeLegibility;
  padding-bottom:calc(var(--tabbar-h) + var(--safe-b) + var(--sp-2));
}
::selection{background:var(--marine-tint)}
h1,h2,h3,h4,p,ul,ol,figure{margin:0}
a{color:var(--marine); text-decoration:none}
b,strong{font-weight:500}
img{max-width:100%}
button{font:inherit; cursor:pointer}

/* figures that get scanned/compared */
.mono,time,.num,.figure{font-family:var(--font-mono); font-variant-numeric:tabular-nums; letter-spacing:0}

/* ---- type roles ---- */
.t-title{font-family:var(--font-cond); font-weight:600; letter-spacing:-.01em; font-size:28px; line-height:34px; text-wrap:balance}
.t-section{font-family:var(--font-cond); font-weight:600; letter-spacing:-.005em; font-size:19px; line-height:26px}
.t-body{font-size:17px; line-height:25px}
.t-secondary{font-size:15px; line-height:22px; color:var(--slate)}
.eyebrow{font-size:13px; line-height:18px; letter-spacing:.06em; text-transform:uppercase; color:var(--slate); font-weight:500}

/* ---- layout ---- */
main{max-width:880px; margin:0 auto; padding:var(--sp-1) var(--pad-screen) var(--sp-6)}
.stack{display:flex; flex-direction:column; gap:var(--gap-section)}
.stack-2{display:flex; flex-direction:column; gap:var(--sp-2)}
.stack-1{display:flex; flex-direction:column; gap:var(--sp-1)}
.hr{height:1px; background:var(--rule); border:0; margin:0}

/* ---- card ---- */
.card{background:var(--surface); border:1px solid var(--rule); border-radius:var(--r-card); padding:var(--pad-card)}
.card + .card{margin-top:var(--sp-2)}
.card-h{display:flex; align-items:baseline; gap:var(--sp-1); margin:0 0 var(--sp-2)}
.card-h .t-section{flex:1 1 auto}
.card-h .link{margin-left:auto}

/* ---- buttons ---- */
.btn{
  display:inline-flex; align-items:center; justify-content:center; gap:8px;
  min-height:var(--tap); padding:0 18px; border-radius:var(--r-ctrl);
  font-family:var(--font-sans); font-weight:500; font-size:17px;
  background:var(--marine); color:var(--on-marine); border:1px solid var(--marine);
}
.btn:active{background:var(--marine-ink); border-color:var(--marine-ink)}
.btn.secondary{background:var(--surface); color:var(--ink); border-color:var(--rule)}
.btn.secondary:active{background:var(--paper)}
.btn.ghost{background:transparent; color:var(--marine); border-color:transparent; padding:0 10px}
.btn.block{display:flex; width:100%}
.btn.sm{min-height:38px; font-size:15px; padding:0 12px}
.btn[disabled]{opacity:.45; pointer-events:none}
.btn .ic{flex:0 0 auto}

/* text link */
.link{color:var(--marine); font-weight:500; background:none; border:0; padding:0; font-size:15px}

/* ---- inputs ---- */
input,select,textarea{
  font-family:var(--font-sans); font-size:17px; color:var(--ink);
  background:var(--surface); border:1px solid var(--rule); border-radius:var(--r-ctrl);
  padding:11px 12px; min-height:var(--tap); width:100%;
}
textarea{min-height:96px; resize:vertical; line-height:1.5}
input[type=checkbox],input[type=radio]{min-height:0; width:22px; height:22px; accent-color:var(--marine)}
::placeholder{color:var(--slate); opacity:.8}
label.eyebrow{display:block; margin:0 0 6px}

/* ---- pills / status chips ---- */
.pill{display:inline-flex; align-items:center; gap:6px; height:26px; padding:0 10px; border-radius:var(--r-pill);
  font-size:13px; font-weight:500; background:var(--paper); color:var(--slate); border:1px solid var(--rule); white-space:nowrap}
.pill.amber{color:var(--amber); border-color:color-mix(in srgb,var(--amber) 45%,var(--rule)); background:color-mix(in srgb,var(--amber) 10%,transparent)}
.pill.signal{color:var(--signal); border-color:color-mix(in srgb,var(--signal) 45%,var(--rule)); background:color-mix(in srgb,var(--signal) 10%,transparent)}
.pill.marine{color:var(--marine); border-color:color-mix(in srgb,var(--marine) 45%,var(--rule)); background:var(--marine-tint)}
/* Overdue, and nothing else on the homepage. Built from the existing --danger token in the same
   shape as the amber and marine variants — no new colour and no hard-coded hex (§3). */
.pill.danger{color:var(--danger); border-color:color-mix(in srgb,var(--danger) 45%,var(--rule)); background:color-mix(in srgb,var(--danger) 10%,transparent)}

/* ---- shared collapsible header (waypoints / departures / arrivals) ---- */
.collhead{display:flex; align-items:center; gap:8px; width:100%; min-height:44px; padding:6px 0;
  background:none; border:0; color:var(--ink); text-align:left; cursor:pointer; -webkit-tap-highlight-color:transparent}
.collhead .coll-caret{color:var(--slate); flex:0 0 auto; transition:transform .12s ease}
.collhead .eyebrow{margin:0}
.collhead .coll-n{margin-left:auto; color:var(--slate); font-size:13px}
.collhead .coll-sub{color:var(--slate); font-size:13px; font-weight:400; text-transform:none; letter-spacing:0}
/* A section head that does not collapse (the planner's Operation): the same box and rhythm as the
   collapsible head, rendered as a div with no caret and no data-coll — so no pointer either. */
.collhead.fixed{cursor:default}
@media print{ .collhead{padding:0} .collhead .coll-caret{display:none} }

/* ---- shift segment editor (public/js/shift-segments.js) --------------------------------------
   The duty-segment rows, the derived break line, the Mon–Sun day picker and the segmented
   Applies control, shared by the Rosters screen and the planner's Working hours section. Tokens
   only; they lived inline in roster.html until a second screen edited the same rows. */
.segrow{display:flex; align-items:center; gap:10px; margin:6px 0}
.segrow .eyebrow{min-width:34px}
.segrow input[type=time]{min-height:40px}
.segrow .cgt-rm{min-width:36px; min-height:36px; border:0; background:none; color:var(--slate); font-size:18px; cursor:pointer}
.segbreak{color:var(--slate); font-size:13px; margin:2px 0 2px 44px}    /* derived, never an input */
.segerr{color:var(--signal); font-size:13px; margin:2px 0 2px 44px}
.sc-tot{color:var(--slate); font-size:13px; margin-top:8px; font-family:var(--font-mono)}
.dows{display:flex; gap:4px}
.dows button{width:32px; min-height:32px; border:1px solid var(--rule); border-radius:6px; background:var(--surface); color:var(--slate); font-size:12px}
.dows button.on{border-color:var(--marine); background:var(--marine-tint); color:var(--marine)}
.modeseg{display:flex; gap:4px; flex-wrap:wrap}
.modeseg button{min-height:32px; padding:0 10px; border:1px solid var(--rule); border-radius:6px; background:var(--surface); color:var(--slate); font-size:12px; white-space:nowrap}
.modeseg button.on{border-color:var(--marine); background:var(--marine-tint); color:var(--marine)}
/* the Day off tick (planner Working hours sheet, roster shift card): the native checkbox beside an
   eyebrow label, on the same row height as the sheet's other field labels */
.dayoff-tick{display:inline-flex; align-items:center; gap:10px; min-height:36px; margin:0; cursor:pointer}
.dayoff-tick .eyebrow{margin:0}

/* ---- list rows (swipe-ready) ---- */
.rows{display:flex; flex-direction:column}
.row{display:flex; align-items:center; gap:var(--sp-2); min-height:var(--tap); padding:12px 0; border-bottom:1px solid var(--rule)}
.row:last-child{border-bottom:0}
.row .row-main{flex:1 1 auto; min-width:0}
.row .row-fig{font-family:var(--font-mono); color:var(--slate)}
/* THE CERTIFICATE ROW (js/cert-rows.js) — the Me tab today, any screen that lists certificates as
   rows tomorrow. Five stacked parts 4px apart, no dividers inside the row: title, issuer, the
   Issued · Expires (· Survey) pairs, the note; the pill on the right with the snooze / in-progress
   actions beneath it. The expiry is the ONE emphasised value; its shade is the server's band
   (signal for expired or ≤30 days, amber for ≤90), never a fixed colour, and the pill beside it
   carries the word (§3.8). A part with no value is absent (§12).
   The kind/holder tags were removed on 5 Sep 2026 (declutter) — omitted from the DOM by the
   renderer, so there is no rule for them here to hide behind. The note line went with them and
   came back the same afternoon as a two-line clamp: 13px muted, 18px lines (1.4 would be 18.2 —
   see the whole-pixel rule below), the stored line breaks kept, opened by a tap on a note that
   does not fit (.certrow.open — the renderer's wire() toggles it) and never truncated with an
   ellipsis of its own beyond the clamp's.
   The row's own vertical padding is one step (--sp-1) more than the generic .row: the parts stack,
   and 12px read as cramped between two four-line cards. The hairline stays.
   EVERY LINE-HEIGHT IS A WHOLE PIXEL, deliberately (23 / 20 / 20px, not 1.2 and 1.4): the row sits
   on a .swipe-surface (will-change: transform, its own compositor layer) over the red Delete
   action, and a fractional row height leaves a one-pixel sliver of that red showing under the row
   where the two layers snap to different pixels — seen on 5 Sep 2026 at 375px and gone once the
   heights were integers. The pairs' height is pinned for the same reason. The title wraps as far
   as it needs (one line for most, two for a long one) and is never clamped or truncated. */
.certrow{padding:calc(12px + var(--sp-1)) 0}
.certrow .row-main{display:flex; flex-direction:column; gap:4px}
.certrow.mine .row-main{cursor:pointer}
.certrow .cnm{font-family:var(--font-cond); font-weight:600; font-size:19px; line-height:23px}
.certrow .cr-issuer{font-size:14px; line-height:20px; color:var(--slate)}
.certrow .cr-dates{display:flex; align-items:baseline; gap:4px 14px; flex-wrap:wrap}
.certrow .cr-pair{display:inline-flex; align-items:baseline; gap:6px; height:20px; white-space:nowrap}
.certrow .cr-lab{font-size:11px; line-height:14px; letter-spacing:.06em; text-transform:uppercase; color:var(--slate); font-weight:500}
.certrow .cr-val{font-size:15px; line-height:20px; color:var(--slate); font-variant-numeric:tabular-nums}
.certrow .cr-val.emph{color:var(--ink); font-weight:600}
.certrow .cr-val.emph.amber{color:var(--amber)}
.certrow .cr-val.emph.signal{color:var(--signal)}
.certrow .cr-note{font-size:13px; line-height:18px; color:var(--slate); white-space:pre-line; display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:2; line-clamp:2; overflow:hidden}
.certrow.open .cr-note{display:block; -webkit-line-clamp:unset; line-clamp:unset; overflow:visible}
/* The pill column: the status pill, then the Snooze / In progress boxes stacked beneath it
   (migration115), right-aligned. Each box is the PILL's size — 26px tall, 13px, the column's mono,
   0 10px — with SQUARE corners, so it reads as a control beside the chip, not as a second chip. The
   planner's segmented-button component (.modeseg) gives the colours and the .on highlight; only the
   geometry is set here. Stacked, not side by side, so the column is never wider than 'In progress'
   and a title's date is never squeezed at 375px. Whole-pixel heights like the rest of the row. */
.certrow .row-fig{flex:0 0 auto; display:flex; flex-direction:column; align-items:flex-end; gap:4px}
.certrow .certacts{display:flex; flex-direction:column; align-items:flex-end; gap:4px}
.certrow .certacts button{height:26px; min-height:26px; padding:0 10px; font-size:13px; line-height:24px; font-weight:500; border-radius:0; white-space:nowrap}
.swipe{position:relative; overflow:hidden}
.swipe-surface{position:relative; width:100%; box-sizing:border-box; background:var(--surface); transition:transform .18s ease; will-change:transform; z-index:var(--z-card)}
.swipe-actions{position:absolute; inset:0 0 0 auto; display:flex; align-items:stretch}
.swipe-actions button{border:0; color:var(--on-marine); background:var(--marine); padding:0 18px; font-weight:500; font-size:15px}
.swipe-actions button.danger{background:var(--signal)}

/* ---- icons ---- */
.ic{display:inline-flex; width:24px; height:24px; color:var(--slate); flex:0 0 auto; vertical-align:middle}
.ic svg{width:100%; height:100%; display:block; fill:currentColor}
.ic.ink{color:var(--ink)} .ic.marine{color:var(--marine)} .ic.amber{color:var(--amber)} .ic.signal{color:var(--signal)}
.ic.sm{width:20px;height:20px} .ic.lg{width:28px;height:28px}

/* ============================================================================
   Chrome injected by app-shell: header (large collapsing title + search) + bottom tabs
   ============================================================================ */
.appheader{position:sticky; top:0; z-index:var(--z-header); background:var(--surface); border-bottom:1px solid var(--rule);
  padding-top:constant(safe-area-inset-top);   /* iOS 11.0–11.2 */
  padding-top:env(safe-area-inset-top)}         /* modern: pushes the title/menu below the status bar,
                                                   as PADDING so the header background still runs to the top */
/* dark backdrop over exactly the status-bar strip, so the white iOS status text stays legible even
   on the light-theme (white) header. Sticks with the header, so it holds when scrolled. */
.appheader::before{content:''; position:absolute; top:0; left:0; right:0; z-index:var(--z-card); pointer-events:none;
  height:constant(safe-area-inset-top); height:env(safe-area-inset-top); background:var(--statusbar-cap)}
.appheader .ah-in{max-width:880px; margin:0 auto;
  /* clear the notch/Dynamic Island on the side in landscape */
  padding:0 calc(var(--pad-screen) + env(safe-area-inset-right,0)) 0 calc(var(--pad-screen) + env(safe-area-inset-left,0))}
.appheader .ah-top{display:flex; align-items:center; gap:12px; min-height:66px}
.appheader .ah-title{font-family:var(--font-cond); font-weight:600; letter-spacing:-.01em; font-size:28px; line-height:1;
  color:var(--ink); flex:0 0 auto; transition:font-size .18s ease}
/* vessel mark: grows to fill the space between the page title and the right-hand controls */
.appheader .ah-home{flex:1 1 auto; min-width:0; display:flex; align-items:center; justify-content:center; -webkit-tap-highlight-color:transparent}
.appheader .ah-logo{flex:1 1 auto; min-width:0; height:52px; width:auto; object-fit:contain; object-position:center; display:block; transition:height .18s ease}
:root[data-theme="night"] .appheader .ah-logo{filter:invert(1) brightness(1.35)}
.appheader.collapsed .ah-top{min-height:52px}
.appheader.collapsed .ah-logo{height:38px}
.appheader .ah-clock{font-family:var(--font-mono); font-size:13px; color:var(--slate); text-align:right; line-height:1.25; white-space:nowrap}
.appheader .ah-clock{display:grid; grid-template-columns:max-content max-content; column-gap:6px; align-items:baseline}
.appheader .ah-clock .t{text-align:right; font-variant-numeric:tabular-nums}
.appheader .ah-clock .z{color:var(--slate); opacity:.7; font-size:10px; letter-spacing:.04em; text-align:left}
.appheader .ah-btn{width:var(--tap); height:var(--tap); display:inline-flex; align-items:center; justify-content:center;
  border:0; background:transparent; color:var(--slate); margin-right:-10px}
.appheader .ah-btn:active{color:var(--ink)}
.appheader .ah-search{display:flex; align-items:center; gap:10px; margin:0 0 12px; padding:0 12px;
  background:var(--paper); border:1px solid var(--rule); border-radius:var(--r-ctrl); min-height:44px}
.appheader .ah-search .ic{color:var(--slate)}
.appheader .ah-search input{border:0; background:transparent; min-height:42px; padding:0; font-size:16px}
.appheader .ah-search input:focus{outline:none}
/* guest variant — the same header, fewer parts. The crew-only nodes are NOT rendered (see
   js/header.js), so there is nothing here to hide; this is spacing and the connection dot only. */
.appheader.guest .ah-in{padding-bottom:12px}
.appheader.guest .ah-dot{flex:0 0 auto; width:8px; height:8px; border-radius:999px; background:var(--marine)}
.appheader.guest .ah-dot.off{background:var(--slate)}
/* collapsed on scroll */
.appheader.collapsed .ah-title{font-size:19px}
.appheader.collapsed .ah-search{display:none}
.appheader.collapsed .ah-top{min-height:52px}

/* offline status pill, mounted in the header slot: a bare colour-coded dot on
   phones (full text on wider screens); its own colours come from offline.js */
.appheader .appbar{margin-right:2px}
.appheader .appbar .fops-pill{border:0 !important; background:transparent !important; padding:0 6px !important; width:auto !important; min-width:0 !important; height:var(--tap); display:inline-flex; align-items:center; gap:6px}
.appheader .appbar .fops-txt,.appheader .appbar .fops-num{display:none}
@media (min-width:560px){.appheader .appbar .fops-txt{display:inline; color:var(--slate); font-size:12px}}

/* captain menu (from header) */
/* SCROLLS WHEN IT IS TALLER THAN THE PHONE. The menu grew to four groups and a dozen rows; with
   overflow:hidden and no height cap, everything past the bottom of the screen was simply
   unreachable on a handset — the Setup group (People & roles, Features) could not be opened at all,
   while a laptop viewport was tall enough to show it. The cap is the viewport MINUS the header it
   hangs from and the bottom inset, so the last row always clears the home indicator.
   dvh, with vh first as the fallback for browsers that do not know it: on iOS Safari the toolbar
   makes 100vh taller than what you can actually see, which is exactly the bug being fixed here.
   overflow-x stays hidden so the rounded corners still clip; overscroll-behavior stops a flick at
   the end of the list scrolling the page behind it. */
.cap-pop{position:absolute; top:calc(56px + constant(safe-area-inset-top)); top:calc(56px + env(safe-area-inset-top));
  right:var(--pad-screen); z-index:var(--z-drawer); min-width:220px; background:var(--surface);
  border:1px solid var(--rule); border-radius:var(--r-card);
  overflow-x:hidden; overflow-y:auto; -webkit-overflow-scrolling:touch; overscroll-behavior:contain;
  max-height:calc(100vh - 56px - env(safe-area-inset-top) - env(safe-area-inset-bottom) - 16px);
  max-height:calc(100dvh - 56px - env(safe-area-inset-top) - env(safe-area-inset-bottom) - 16px)}
.cap-pop[hidden]{display:none}
.cap-pop .cap-hd{padding:12px 16px 6px; font-size:13px; letter-spacing:.06em; text-transform:uppercase; color:var(--slate); font-weight:500}
.cap-pop a{display:flex; align-items:center; gap:12px; padding:14px 16px; color:var(--ink); font-weight:500; border-top:1px solid var(--rule)}
.cap-pop a:active{background:var(--paper)}
.cap-pop a .ic{color:var(--slate)}
/* Sign out: the same row geometry as the links above it, but a button and visually separated —
   a heavier top rule and muted text, so it reads as leaving rather than as one more destination.
   Existing tokens only; not red, because signing out is not an alert state. */
.cap-pop .cap-act{display:flex; align-items:center; gap:12px; width:100%; padding:14px 16px;
  font:inherit; font-weight:500; text-align:left; cursor:pointer; background:transparent;
  color:var(--slate); border:0; border-top:2px solid var(--rule); min-height:44px}
.cap-pop .cap-act:active{background:var(--paper)}
.cap-pop .cap-act .ic{color:var(--slate)}

.tabbar{position:fixed; left:0; right:0; bottom:0; z-index:var(--z-drawer); background:var(--surface); border-top:1px solid var(--rule);
  height:calc(var(--tabbar-h) + var(--safe-b)); padding-bottom:var(--safe-b);
  display:flex; padding-left:env(safe-area-inset-left,0); padding-right:env(safe-area-inset-right,0)}
.tabbar a{flex:1 1 0; display:flex; flex-direction:column; align-items:center; justify-content:center; gap:2px;
  color:var(--slate); min-height:var(--tabbar-h); text-decoration:none}
.tabbar a .ic{width:26px; height:26px; color:inherit}
.tabbar a .tb-l{font-size:11px; line-height:1; font-weight:500; letter-spacing:.01em}
.tabbar a.active{color:var(--marine)}

/* ---- bottom sheet ---- */
.sheet-ov{position:fixed; inset:0; z-index:var(--z-sheet); background:var(--scrim); display:flex; align-items:flex-end; justify-content:center}
.sheet-ov[hidden]{display:none}
.sheet-ov{overscroll-behavior:contain}
.sheet{width:100%; max-width:560px; background:var(--surface); border-radius:var(--r-card) var(--r-card) 0 0;
  border:1px solid var(--rule); border-bottom:0; padding:8px var(--pad-screen) calc(var(--pad-screen) + var(--safe-b));
  max-height:88vh; /* fallback */
  max-height:min(88vh, calc(100vh - env(safe-area-inset-top) - 12px)); /* a tall sheet never tucks under the status bar */
  overflow:auto; overscroll-behavior:contain; transform:translateY(0); animation:sheet-up .22s ease}
/* the grab handle carries a generous, invisible drag target (content-box + padding) */
.sheet .grab{width:38px; height:4px; border-radius:999px; background:var(--rule); margin:8px auto 10px;
  box-sizing:content-box; padding:9px 34px; background-clip:content-box; touch-action:none; cursor:grab}
.sheet .sheet-h{font-family:var(--font-cond); font-weight:600; font-size:19px; margin:0 0 12px; text-align:center; touch-action:none}
.sheet .sheet-bar{display:flex; gap:12px; margin-top:16px}
.sheet .sheet-bar.sticky{position:sticky; bottom:calc(-1 * var(--safe-b)); background:var(--surface); padding:12px 0 2px; margin-top:8px; z-index:var(--z-card)}
.sheet .sheet-bar .btn{flex:1}
/* quick-set chips above the wheels */
.wheel-chips{display:flex; gap:8px; justify-content:center; margin:2px 0 14px}
.wheel-chips .chip{min-height:38px; padding:0 16px; border-radius:var(--r-pill); border:1px solid var(--rule);
  background:var(--surface); color:var(--ink); font-family:var(--font-sans); font-weight:500; font-size:15px}
.wheel-chips .chip:active{background:var(--marine-tint); border-color:var(--marine)}
@keyframes sheet-up{from{transform:translateY(100%)}to{transform:translateY(0)}}

/* ---- wheel picker ---- */
.wheels{display:flex; align-items:stretch; justify-content:center; gap:8px; position:relative; margin:4px 0}
.wheel{position:relative; height:220px; width:88px; overflow:hidden; -webkit-mask-image:linear-gradient(180deg,transparent,#000 24%,#000 76%,transparent);
  mask-image:linear-gradient(180deg,transparent,#000 24%,#000 76%,transparent)}
.wheel .wheel-scroll{height:100%; overflow-y:scroll; scroll-snap-type:y mandatory; scrollbar-width:none; -ms-overflow-style:none;
  touch-action:pan-y; overscroll-behavior:contain; -webkit-overflow-scrolling:touch}
.wheel .wheel-scroll::-webkit-scrollbar{display:none}
.wheel .wheel-item{height:44px; scroll-snap-align:center; display:flex; align-items:center; justify-content:center;
  font-family:var(--font-mono); font-size:26px; color:var(--slate); cursor:pointer}
.wheel .wheel-item.sel{color:var(--ink)}
.wheels .wheel-sep{align-self:center; font-family:var(--font-mono); font-size:26px; color:var(--ink)}
.wheels .wheel-line{position:absolute; left:0; right:0; top:50%; height:44px; transform:translateY(-50%);
  border-top:1px solid var(--rule); border-bottom:1px solid var(--rule); pointer-events:none}
.wheel-precision{display:flex; align-items:center; justify-content:center; gap:8px; margin-top:8px; color:var(--slate); font-size:15px}

/* ---- calendar sheet ---- */
.cal{--cell:40px}
.cal .cal-head{display:flex; align-items:center; justify-content:space-between; margin:4px 0 12px}
.cal .cal-head .mlabel{font-family:var(--font-cond); font-weight:600; font-size:19px}
.cal .cal-grid{display:grid; grid-template-columns:repeat(7,1fr); gap:2px}
.cal .dow{font-size:12px; color:var(--slate); text-align:center; padding:4px 0}
.cal .day{aspect-ratio:1/1; min-height:var(--cell); border:0; background:transparent; border-radius:999px;
  font-family:var(--font-mono); font-size:16px; color:var(--ink); display:flex; align-items:center; justify-content:center}
.cal .day.other{color:var(--slate); opacity:.5}
.cal .day.today{box-shadow:inset 0 0 0 1px var(--rule)}
.cal .day.sel{background:var(--marine); color:var(--on-marine)}

/* ---- notes block (heading above, body below; tap to edit in place) ---- */
.notes{border:1px solid var(--rule); border-radius:var(--r-card); overflow:hidden; background:var(--surface)}
.notes .n-head,.notes .n-body{width:100%; border:0; background:transparent; color:var(--ink); display:block; padding:14px 16px; font-family:inherit}
.notes .n-head{font-family:var(--font-cond); font-weight:600; font-size:19px; border-bottom:1px solid var(--rule)}
.notes .n-body{font-size:17px; line-height:25px; min-height:80px; resize:vertical}
.notes .n-head::placeholder,.notes .n-body::placeholder{color:var(--slate); opacity:.7}
.notes.editable .n-head:focus,.notes.editable .n-body:focus{outline:none; background:var(--marine-tint)}
.notes .n-saved{font-size:13px; color:var(--slate); padding:0 16px 10px; display:flex; align-items:center; gap:6px; min-height:0}

/* ---- toast (undo) ---- */
.toast-wrap{position:fixed; left:0; right:0; bottom:calc(var(--tabbar-h) + var(--safe-b) + 12px); z-index:var(--z-toast);
  display:flex; flex-direction:column; align-items:center; gap:8px; pointer-events:none; padding:0 12px}
.toast{pointer-events:auto; display:flex; align-items:center; gap:16px; max-width:520px; width:100%;
  background:var(--ink); color:var(--paper); border-radius:var(--r-ctrl); padding:12px 16px; font-size:15px;
  animation:toast-in .18s ease}
.toast .toast-msg{flex:1 1 auto}
.toast .toast-act{color:#8FD6CF; font-weight:600; background:none; border:0; padding:6px 4px}
@keyframes toast-in{from{transform:translateY(8px); opacity:0}to{transform:translateY(0); opacity:1}}

/* ---- skeleton ---- */
.skeleton{background:linear-gradient(90deg,var(--rule) 25%,color-mix(in srgb,var(--rule) 40%,var(--surface)) 37%,var(--rule) 63%);
  background-size:400% 100%; border-radius:6px; animation:sk 1.3s ease infinite; min-height:14px}
@keyframes sk{0%{background-position:100% 0}100%{background-position:-100% 0}}

/* ---- empty state ---- */
.empty{display:flex; flex-direction:column; align-items:flex-start; gap:12px; padding:8px 0 4px}
.empty .empty-line{color:var(--slate); font-size:15px}

/* ---- day rule (signature) ---- */
.dayrule{position:relative; padding:46px 4px 20px}
.dayrule .dr-line{position:relative; height:1px; background:var(--rule)}
.dayrule .dr-tick{position:absolute; top:-4px; width:1px; height:9px; background:var(--rule)}
.dayrule .dr-tick.major{height:13px; top:-6px; background:var(--slate)}
.dayrule .dr-hour{position:absolute; top:12px; transform:translateX(-50%); font-family:var(--font-mono); font-size:11px; color:var(--slate)}
/* --dr-row is set by day-rule.js when markers collide: row 0 sits on the line, each further row is
   lifted 22px and grows its stem to reach back down, so an offset marker still points at its time. */
/* BOTTOM-anchored, not top-anchored. The column is icon / caption / stem, and the stem must END on
   the rule. It was pinned at top:-27px against ~44px of content, so the whole marker sat 17px low:
   the caption landed in the tick row and the stem carried on through the line. bottom:0 makes the
   stem meet the line whatever the icon or font metrics turn out to be. */
.dayrule .dr-mark{position:absolute; --dr-row:0; bottom:calc(var(--dr-row) * 22px); top:auto;
  transform:translateX(-50%); display:flex; flex-direction:column; align-items:center; gap:3px;
  background:none; border:0; padding:0; line-height:1}
.dayrule .dr-mark .ic{width:18px; height:18px; color:var(--ink)}
.dayrule .dr-mark .dr-cap{font-family:var(--font-mono); font-size:10px; color:var(--slate)}
.dayrule .dr-mark .dr-stem{width:1px; height:calc(8px + var(--dr-row) * 22px); background:var(--slate)}
/* Too close to a neighbour to print a time without overstriking it. The icon stays; the time is in
   the list below. Kept in the DOM (not display:none) so the icon keeps its place. */
.dayrule .dr-mark.nocap .dr-cap{visibility:hidden}
/* the axis start when something sits before 06:00 */
.dayrule .dr-hour.early{font-family:var(--font-sans, inherit); letter-spacing:.02em}
.dayrule .dr-now{position:absolute; top:-14px; width:2px; height:24px; background:var(--marine); transform:translateX(-50%); border-radius:2px}
.dayrule .dr-now::after{content:''; position:absolute; top:-4px; left:50%; transform:translateX(-50%); width:8px; height:8px; border-radius:999px; background:var(--marine)}
/* filled segments — the rest-hours 24h bar restates the day rule as work/rest blocks
   sitting on the same ruled baseline (position left% + width% by minutes/1440) */
.dayrule.bars{padding:20px 4px 20px}
.dayrule .dr-block{position:absolute; top:-6px; height:13px; border-radius:2px}
.dayrule .dr-block.work{background:var(--marine)}
.dayrule .dr-block.rest{background:var(--marine-tint); box-shadow:inset 0 0 0 1px var(--rule)}
.dayrule .dr-block.gap{background:transparent}

/* ---- work/rest control ---- */
.wr-btn{width:100%; min-height:96px; border:1px solid var(--rule); border-radius:var(--r-card); background:var(--surface);
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:6px; padding:18px; color:var(--ink)}
.wr-btn .wr-state{font-family:var(--font-cond); font-weight:600; font-size:26px; letter-spacing:.01em}
.wr-btn .wr-sub{font-size:15px; color:var(--slate)}
.wr-btn.working{border-color:var(--marine); box-shadow:inset 0 0 0 1px var(--marine)}
.wr-btn.working .wr-state{color:var(--marine)}

/* focus visibility */
:focus-visible{outline:2px solid var(--marine); outline-offset:2px; border-radius:3px}

/* ---- vessel position: update sheet ---- */
.pos-opts{display:flex; flex-direction:column; gap:10px}
.pos-opts .btn{justify-content:flex-start}
.pos-detail:not(:empty){margin-top:16px; border-top:1px solid var(--rule); padding-top:16px}
.pos-detail .sf{margin:0 0 12px}

/* ---- reduced motion ---- */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.001ms !important; animation-iteration-count:1 !important; transition-duration:.001ms !important}
  .sheet{animation:none}
}

/* ---- print: black on white, no chrome ---- */
@media print{
  /* .noprint — anything that exists only to operate the screen: the Print control itself, filters,
     add buttons. Was declared per page (charters.html); shared here now that Day, Work and
     Provisions print too. */
  .appheader,.tabbar,.toast-wrap,.sheet-ov,.ah-btn,.ah-search,.noprint{display:none !important}
  body{background:#fff !important; color:#000 !important; padding:0 !important}
  .card{border-color:#000 !important; break-inside:avoid}
  *{box-shadow:none !important; color:#000 !important; background:#fff !important}
  a{color:#000 !important}
}

/* narrow screens */
@media (max-width:400px){
  :root{--pad-screen:16px}
  .t-title,.appheader .ah-title{font-size:25px}
  .appheader .ah-clock{display:none}
}

/* ---- vessel position + conditions card — SHARED (crew home + guest home) --------------------
   Rendered only by /js/conditions-card.js. Moved here from index.html when the guest home gained
   the same card: one card, one stylesheet, no per-screen chrome. */
  /* Merged date row: the whole row is the tap target for the day view. Sits above the location. */
  .cond .cond-date{display:flex; align-items:center; gap:8px; min-height:var(--tap); color:var(--ink); text-decoration:none;
    margin:-2px 0 6px; padding-bottom:6px}
  .cond .cond-date .cd-l{font-family:var(--font-cond); font-weight:600; font-size:19px}
  .cond .cond-date .cd-s{color:var(--slate); font-size:15px}
  .cond .cond-date .cardcaret{margin-left:auto}
  /* 'Next: Lunch 13:00' — one forward-looking line, mirroring .cond-date at the other end of the
     card. Existing tokens only; it is information, not an exception, so it takes no accent. */
  .cond .cond-next{display:flex; align-items:center; gap:8px; min-height:var(--tap); margin:6px 0 -2px;
    padding-top:6px; border-top:1px solid var(--rule); color:var(--ink); text-decoration:none}
  .cond .cond-next .cn-l{flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
  .cond .cond-next .cn-t{color:var(--slate)}
  .cond .cond-top{display:flex; align-items:center; gap:10px}
  .cond .cond-place{font-family:var(--font-cond); font-weight:600; font-size:19px; flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
  .cond .cond-top .link{flex:0 0 auto}
  /* The standalone position row (crew home, FSpos.renderSummary): place · fix time · source on ONE
     line, the whole line tapping through to /vessel-map.html. Update sits beside it as its own
     control — nesting a button inside that anchor is invalid and the two taps would swallow each
     other. Existing card, existing tokens; nothing new but the flex line itself. */
  .cond .cond-link{flex:1 1 auto; min-width:0; display:flex; align-items:center; gap:10px;
    min-height:var(--tap); color:var(--ink); text-decoration:none}
/* ---- the vessel marker, emitted by js/vessel-map-view.js -------------------------------------
   Lives HERE because the SHARED renderer draws it: it was defined identically in vessel-map.html
   and guest.html, and the crew home would have made a third copy. An arrow when the vessel reported
   a course, a ringed dot when it did not; dimmed once the fix is stale. */
  .vm-now{color:var(--marine)}
  .vm-now svg{display:block; width:30px; height:30px; filter:drop-shadow(0 1px 2px var(--scrim))}
  .vm-now.dim{opacity:.45}
  .vm-dot{width:16px; height:16px; border-radius:50%; background:var(--marine); border:3px solid var(--surface); box-shadow:0 1px 4px var(--scrim)}
  .vm-now.dim .vm-dot{background:var(--slate)}

  /* ---- the home screen's still map -----------------------------------------------------------
     Flush to the card's bottom corners (negative margins cancel the card padding), fixed height so
     it can never grow into the day's content, and covered by its own tap-through anchor: the map
     is a picture here, not a map you drive.
     THE MAP BOX IS ITS OWN STACKING CONTEXT (--z-map, isolated): Leaflet's panes and controls
     (200-1000, from its own stylesheet) are contained here and never reach the page. Without it
     they joined the root context and painted over the sticky header and the captain menu inside
     it. The tap layer's 750 is a LEAFLET-scale number, deliberately: inside the box it clears the
     marker/popup panes (600 / 700) but stays under the controls (800), so the OSM attribution
     link is still tappable. */
  .cond .pos-map{position:relative; isolation:isolate; z-index:var(--z-map); height:170px; margin:12px calc(var(--pad-card) * -1) calc(var(--pad-card) * -1);
    border-top:1px solid var(--rule); border-radius:0 0 var(--r-card) var(--r-card); overflow:hidden; background:var(--paper)}
  .cond .pos-map-canvas{position:absolute; inset:0}
  .cond .pos-map-tap{position:absolute; inset:0; z-index:750}
  .cond .leaflet-container{font-family:var(--font-sans); font-size:12px; background:var(--paper)}
  .cond .leaflet-control-attribution{background:var(--surface); color:var(--slate)}
  .cond .leaflet-control-attribution a{color:var(--marine)}

  .cond .cond-lines{flex:1 1 auto; min-width:0; display:flex; flex-wrap:wrap; align-items:baseline; gap:1px 10px}
  .cond .cond-lines .cond-place{flex:0 1 auto}   /* shares the line with the fix rather than taking it all */
  .cond .cond-link .cond-fix{flex:0 0 auto; font-size:13px; color:var(--slate)}
  .cond .cond-meta{font-size:13px; color:var(--slate); margin-top:4px}
  .cond .cond-meta .mono{color:var(--slate)}
  .cond .cond-stale{margin-top:8px; color:var(--amber); font-size:14px}
  .cond.stale{border-color:color-mix(in srgb,var(--amber) 45%,var(--rule))}
  .cond .cond-wx{display:flex; align-items:center; gap:12px; margin-top:12px; padding-top:12px; border-top:1px solid var(--rule); color:var(--ink); text-decoration:none}
  .cond .cond-wx > .wxrows{flex:1 1 auto; min-width:0}
  /* Two rows reading as ONE grouped block: the app's tight list gap, no divider between them.
     The whole block keeps the single tap target — the rows are not separately tappable. It carries
     NO chevron: the card's one chevron is on .cond-date above. */
  .cond .wxrows{display:flex; flex-direction:column; gap:var(--sp-1, 6px)}
  /* Icons in a fixed-width leading column so both rows' text starts on the same vertical line,
     whatever the glyph's own width. */
  .cond .wxrow{display:grid; grid-template-columns:24px 1fr; align-items:center; gap:10px}
  .cond .wxrow > .ic{width:24px; height:24px}   /* identical size for wind and waves */
  /* Anchor / Passage brief: a text link under the Conditions block, left-aligned with its heading.
     Existing link token and type scale; the tap target is the row, not the glyph run. */
  /* THE shared crew portrait (js/avatar.js). One circle, one initials fallback, every screen. */
  .fsav{border-radius:999px; object-fit:cover; background:var(--rule); display:inline-block}
  .fsav-init{display:inline-flex; align-items:center; justify-content:center; font-weight:600;
    color:var(--marine); background:color-mix(in srgb, var(--marine) 18%, transparent)}
  .brieflink{display:inline-flex; align-items:center; min-height:var(--tap); color:var(--marine); font-size:15px; font-weight:500}


/* ---- range picker (shared: /js/range-picker.js) ------------------------------------------------
   Start/end days: solid accent, rounded on their OUTER edge only; in-between days: low-opacity
   accent with square edges so the span reads as one continuous bar. Today: outline only. */
.rp .rp-field{width:100%; min-height:46px; font:inherit; text-align:left; color:var(--ink);
  background:none; border:1px solid var(--rule); border-radius:var(--r-ctrl,10px); padding:11px 12px; cursor:pointer}
.rp .rp-sum.rp-ph{color:var(--slate)}
.rp .rp-panel{border:1px solid var(--rule); border-radius:var(--r-ctrl,10px); margin-top:6px; padding:10px}
.rp .rp-head{display:flex; align-items:center; justify-content:space-between; margin-bottom:6px}
.rp .rp-title{font-weight:600}
.rp .rp-nav{font:inherit; font-size:20px; line-height:1; width:44px; height:44px; background:none;
  border:0; color:var(--ink); cursor:pointer}
.rp .rp-grid{display:grid; grid-template-columns:repeat(7,1fr)}
.rp .rp-dow span{text-align:center; font-size:11px; color:var(--slate); padding:4px 0}
.rp .rp-day{font:inherit; font-size:15px; min-height:44px; background:none; border:0; color:var(--ink);
  cursor:pointer; border-radius:0; font-variant-numeric:tabular-nums}
.rp .rp-day.in{background:color-mix(in srgb, var(--marine) 18%, transparent)}
.rp .rp-day.cap{background:var(--marine); color:var(--on-marine,#fff); font-weight:600}
.rp .rp-day.capL{border-radius:999px 0 0 999px}
.rp .rp-day.capR{border-radius:0 999px 999px 0}
.rp .rp-day.capL.capR{border-radius:999px}
.rp .rp-day.today:not(.cap){box-shadow:inset 0 0 0 1px var(--marine)}
.rp .rp-foot{display:flex; justify-content:flex-end; margin-top:6px}
.rp .rp-clear{font:inherit; font-size:14px; background:none; border:0; color:var(--marine);
  text-decoration:underline; cursor:pointer; padding:8px}

/* ---- guest request menu (public/js/request-menu.js) -------------------------------------------
   Existing tokens only: --rule for separators, --slate for muted, --marine for the selected state.
   No new colour, and no red — red stays the alert state. The add/withdraw controls are text-style
   `link` buttons so neither competes with a screen's Submit. */
.rmsec{border-top:1px solid var(--rule)}
.rmsec:first-child{border-top:0}
.rmsec.collapsed > .rmbody{display:none}
.rmsec > .collhead{min-height:44px}
.rmbody{padding:2px 0 10px}
/* Options are CHIPS that wrap. Five beers run to two rows on a phone and that is the intended
   outcome — the type is not shrunk and there is no horizontal scroller. */
.rmchips{display:flex;flex-wrap:wrap;gap:8px}
.rmitem{display:inline-flex;align-items:center;gap:8px;text-align:left;font:inherit;cursor:pointer;
  background:transparent;color:var(--ink);border:1px solid var(--rule);
  border-radius:var(--r-ctrl);padding:8px 12px;min-height:44px}
.rmitem.on{border-color:var(--marine);color:var(--marine)}
/* 28px square, cropped, corner radius matching the chip. No border, no shadow, no background plate
   — and no reserved box when there is no image, so a label-only chip has no leading gap. */
.rmchip-img{width:28px;height:28px;flex:0 0 28px;object-fit:cover;border-radius:calc(var(--r-ctrl) - 4px);display:block}
.rmchip-var{color:var(--slate)}
.rmcat{margin:0 0 var(--sp-3)}
.rmcath{margin:0 0 6px}
.rmsub{display:block;margin:10px 0 4px}
.rmrow{display:flex;align-items:center;justify-content:space-between;gap:10px;
  padding:9px 0;border-bottom:1px solid var(--rule);min-height:44px}
.rmrow-main{display:inline-flex;align-items:center;gap:8px;min-width:0}
.rmrow.off .rmrow-main{color:var(--slate);text-decoration:line-through}
.rmadd{padding:8px 0;display:inline-block;min-height:44px}
.rmnew{display:flex;gap:8px;align-items:center;padding:6px 0}
.rmnew input{flex:1 1 auto;min-width:0}

/* ---- a drinks ROUND: option, count, stepper -------------------------------------------------
   Existing tokens only. The whole left side is the add-one target; the stepper corrects a tap. */
.rmqrow{display:flex;align-items:center;gap:8px;border-bottom:1px solid var(--rule)}
.rmqrow:last-child{border-bottom:0}
.rmqmain{flex:1 1 auto;min-width:0;display:inline-flex;align-items:center;gap:10px;
  font:inherit;text-align:left;cursor:pointer;background:transparent;color:var(--ink);
  border:0;padding:8px 0;min-height:52px}
/* In the ROUND the picture leads: a product shot is what a guest recognises, so it gets real size
   here rather than the 28 px used in the compact crew list. The label stays beside it — two similar
   cans are only told apart by the words, and a picture alone reads as nothing to a screen reader. */
.rmqmain .rmchip-img{width:44px;height:44px;flex:0 0 44px;border-radius:var(--r-ctrl);
  background:#fff}
.rmqrow.on .rmqmain{color:var(--marine)}
.rmqstep{display:inline-flex;align-items:center;gap:2px;flex:0 0 auto}
.rmqbtn{font:inherit;font-size:18px;line-height:1;cursor:pointer;background:transparent;
  color:var(--ink);border:1px solid var(--rule);border-radius:var(--r-ctrl);
  width:36px;height:36px;display:inline-flex;align-items:center;justify-content:center}
.rmqbtn:disabled{color:var(--rule);cursor:default}
.rmqn{min-width:24px;text-align:center;font-family:var(--font-mono)}
.rmtotal{display:flex;align-items:center;justify-content:space-between;gap:12px;
  padding-top:12px;margin-top:4px;border-top:1px solid var(--rule)}
.rmtotal .btn{flex:0 0 auto}


/* ---- shared switch --------------------------------------------------------------------------
   Lifted out of position.html so the Vessel setup toggles use the SAME control, not a copy. */
.sw{width:56px; height:32px; border-radius:999px; border:1px solid var(--rule); background:var(--paper);
  position:relative; flex:0 0 auto; transition:background .15s; cursor:pointer}
.sw .knob{position:absolute; top:2px; left:2px; width:26px; height:26px; border-radius:999px;
  background:var(--surface); border:1px solid var(--rule); transition:left .15s}
.sw.on{background:var(--marine); border-color:var(--marine)}
.sw.on .knob{left:26px; border-color:var(--marine)}

/* ---- birthday theme overlay (public/js/birthday-theme.js) ------------------------------------
   Fixed, so it can never shift the layout. pointer-events:none on the layer AND its children, so
   it can never take a tap from the screen underneath. */
.bt-layer{position:fixed; inset:0; z-index:var(--z-backdrop); pointer-events:none; overflow:hidden;
  contain:layout style paint}
.bt-layer *{pointer-events:none}
.bt-balloon, .bt-confetti{position:absolute; opacity:.34; will-change:transform}
.bt-balloon{bottom:-14vh; width:22px; height:28px; border-radius:50% 50% 48% 48%;
  transform:scale(var(--scale,1));
  animation:bt-rise var(--dur,18s) linear var(--delay,0s) infinite,
            bt-sway calc(var(--dur,18s) / 3) ease-in-out var(--delay,0s) infinite alternate}
/* the string, drawn rather than a second element */
.bt-balloon::after{content:''; position:absolute; left:50%; top:100%; width:1px; height:16px;
  background:var(--rule); opacity:.5}
.bt-confetti{top:-6vh; width:6px; height:9px; border-radius:1px; opacity:.28;
  animation:bt-fall var(--dur,13s) linear var(--delay,0s) infinite,
            bt-sway calc(var(--dur,13s) / 4) ease-in-out var(--delay,0s) infinite alternate}
@keyframes bt-rise{ from{ transform:translateY(0) scale(var(--scale,1)); } to{ transform:translateY(-124vh) scale(var(--scale,1)); } }
@keyframes bt-fall{ from{ transform:translateY(0); } to{ transform:translateY(112vh) rotate(var(--spin,360deg)); } }
@keyframes bt-sway{ from{ margin-left:calc(var(--sway,20px) * -1); } to{ margin-left:var(--sway,20px); } }

/* PAUSED: hidden tab, or a nearly-flat battery. Nothing advances. */
.bt-layer.bt-paused *{animation-play-state:paused !important}

/* The reduced-motion version: a small still motif, bottom-left, no animation of any kind. */
.bt-static{position:absolute; left:14px; bottom:calc(64px + env(safe-area-inset-bottom)); display:flex; gap:6px}
.bt-static .bt-b{width:14px; height:18px; border-radius:50% 50% 48% 48%; opacity:.4; display:block}
@media (prefers-reduced-motion: reduce){
  .bt-balloon, .bt-confetti{animation:none !important}
}

/* NEVER ON PAPER. No print view, PDF or generated document carries confetti (§3.8). */
@media print{ .bt-layer{display:none !important} }


/* ---- guest request status badge (public/js/guest-requests.js) --------------------------------
   Lifted out of service.html so the homepage card shows the SAME badge rather than its own wording
   and its own colour. Text is uppercased here, which is why the shared mapping returns the stored
   status lowercase — the string is the data, the casing is the badge's.
   `delivered` has no modifier deliberately: it takes the neutral base, and red stays reserved for
   genuine alert and overdue states. */
.srpill{font-size:12px; letter-spacing:.04em; text-transform:uppercase; padding:3px 8px; border-radius:999px; border:1px solid var(--rule); color:var(--slate)}
.srpill.raised{color:var(--amber); border-color:color-mix(in srgb,var(--amber) 45%,var(--rule))}
.srpill.accepted{color:var(--marine); border-color:color-mix(in srgb,var(--marine) 45%,var(--rule))}
.srpill.cancelled{color:var(--slate)}
/* The accepter, beside the pill. Normal case on purpose — the pill is uppercased, a person's name
   should not be. Muted, because who answered is context, not the headline. */
.srby{font-size:13px; color:var(--slate); margin-left:6px; align-self:center}

/* ---- catalogue item visuals + tiles (public/js/request-menu.js) -------------------------------
   One square visual, three tiers: a photograph, a two-band swatch, or the category glyph on a
   neutral ground. Existing tokens throughout — no new colours, and nothing red. */
.rm-vis{display:block; border-radius:var(--r-ctrl); overflow:hidden; background:var(--paper);
  position:relative; flex:0 0 auto}
.rm-vis img{width:100%; height:100%; object-fit:cover; display:block}
.rm-vis-swatch{background:linear-gradient(180deg, var(--sw1) 0 50%, var(--sw2) 50% 100%); border-radius:999px}
.rm-vis-glyph{display:flex; align-items:center; justify-content:center; color:var(--slate)}
.rm-vis-glyph .ic{width:44%; height:44%}
.rm-vis-tile{width:100%; aspect-ratio:1/1}
.rm-vis-slot{width:44px; height:44px}

/* Guest tile grid. Every item is a tile whatever tier it resolved at, so a section never mixes
   shapes. Two up on a phone, more as the width allows. */
/* 120px, not 132: at a 320px viewport the content box is 272px, and 132*2+10 = 274 misses two
   columns by 2px — the grid collapsed to one full-width square tile per row. 120*2+10 = 250
   holds two columns down to the narrowest phone. Wider viewports are unchanged (auto-fill
   still fits the same column count and 1fr expands them to the same width). */
.rmtiles{display:grid; grid-template-columns:repeat(auto-fill, minmax(120px, 1fr)); gap:10px}
.rmtile{display:flex; flex-direction:column; border:1px solid var(--rule); border-radius:var(--r-ctrl);
  background:var(--surface); overflow:hidden}
.rmtile.on{border-color:var(--marine)}
.rmtile-main{display:flex; flex-direction:column; gap:2px; padding:0 0 6px; font:inherit; text-align:left;
  cursor:pointer; background:transparent; color:var(--ink); border:0}
.rmtile.on .rmtile-main{color:var(--marine)}
.rmtile-name{padding:6px 10px 0; font-weight:500}
.rm-fmt{padding:0 10px; font-size:12px; color:var(--slate)}
.rmtile .rmqstep{justify-content:space-between; padding:0 8px 8px}

/* Crew capture slot. Empty reads as an invitation, not as a gap. */
.rmslot{position:relative; display:inline-flex; flex-direction:column; align-items:center; gap:2px; cursor:pointer}
.rmslot.empty .rm-vis{border:1px dashed var(--rule); background:transparent}
.rmslot-add{font-size:11px; color:var(--slate); white-space:nowrap}

/* ---- guest Requests tab: sections + tab badge -------------------------------------------------
   Existing collapsible and card tokens; the badge uses the accent, never red — red stays for alert
   and overdue states. */
.gsec-req{border-top:1px solid var(--rule); padding-top:4px}
.gsec-req.collapsed > .gsec-body{display:none}
.gsec-req > .collhead{min-height:44px; width:100%}
.gsec-req .coll-n{margin-left:auto; color:var(--slate)}
.gsec-body{padding:4px 0 12px}
.gtab-badge{position:absolute; top:6px; left:50%; margin-left:6px; min-width:16px; height:16px;
  padding:0 4px; border-radius:999px; background:var(--marine); color:var(--on-marine);
  font-size:11px; line-height:16px; text-align:center; font-weight:600}
.tabbar a, .tabbar button{position:relative}

/* ---- segmented sub-navigation ----------------------------------------------------------------
   ONE definition. Used by Work (Jobs / Snags / Planned maintenance), Guests (Requests / Aboard /
   Cabins / Preferences) and Users (People / Visibility). It was inline in work.html until a second
   screen needed it. */
/* ---- pick-up / drop-off day banner (js/charter-day.js) ---------------------------------------
   ONE set of classes, two variants: the full card on home, `.cdy-sm` on the Day screen. Existing
   tokens only — the marine tint and the marine accent, the same wash used for editable and selected
   states. NO RED anywhere: red is reserved for genuine alert states (§12) and a pick-up day is not
   one. Colour is never the only channel either (§3.8) — the kind is carried by its own icon and by
   the wording, so the banner survives the print stylesheet's black-on-white even though it is
   dropped from print entirely.

   IT NEVER SHIFTS THE LAYOUT: an ordinary block in the flow with its own fixed height, and the
   motion layer absolutely positioned INSIDE it rather than over the page. */
.cdy{position:relative; overflow:hidden; display:flex; align-items:stretch; gap:0;
  background:var(--marine-tint); border:1px solid var(--marine); border-radius:var(--r-card);
  margin:0 0 var(--sp-2)}
/* The card variant is capped at ONE card height so it can never push the exception flags below the
   fold on a phone — the homepage answers "does anything need me?" first (§3.5). */
.cdy .cdy-main{flex:1 1 auto; min-width:0; display:flex; align-items:center; gap:12px;
  min-height:var(--tap); padding:14px var(--pad-card); text-decoration:none; color:var(--ink)}
.cdy-sm .cdy-main{min-height:40px; padding:8px 14px}
.cdy-ico{flex:0 0 auto; display:inline-flex; align-items:center; color:var(--marine)}
.cdy-txt{display:flex; flex-direction:column; min-width:0}
.cdy-ttl{font-family:var(--font-cond, inherit); font-weight:600; letter-spacing:.02em;
  font-size:19px; line-height:24px; color:var(--marine-ink)}
.cdy-sm .cdy-ttl{font-size:16px; line-height:20px}
.cdy-sub{font-size:14px; line-height:20px; color:var(--slate);
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.cdy-sm .cdy-sub{font-size:13px; line-height:18px}
.cdy-x{flex:0 0 auto; align-self:center; margin-right:10px; min-height:var(--tap); padding:0 8px;
  border:0; background:transparent; color:var(--slate); font-size:14px}

/* THE MOTION LAYER IS THE ONLY THING THAT IS NOT TAPPABLE. The banner itself is a link; this sits
   behind it and takes no pointer events, so it can never eat the tap. Eight slow marks, well inside
   the budget birthday-theme.js sets (14 balloons + 48 confetti). */
/* z-index 0 is the ground, not a step on the scale: the motion layer sits under the card's own content. */
.cdy-motion{position:absolute; inset:0; pointer-events:none; z-index:0; overflow:hidden}
.cdy .cdy-main, .cdy .cdy-x{position:relative; z-index:var(--z-card)}
.cdy-dot{position:absolute; bottom:-8px; width:6px; height:6px; border-radius:50%;
  background:var(--marine); opacity:.28;
  animation:cdy-drift var(--dur, 14s) linear var(--delay, 0s) infinite;
  transform:scale(var(--scale, 1))}
@keyframes cdy-drift{
  0%{transform:translateY(0) scale(var(--scale,1)); opacity:0}
  15%{opacity:.28}
  85%{opacity:.28}
  100%{transform:translateY(-80px) scale(var(--scale,1)); opacity:0}
}
/* Paused when the tab is hidden or the battery is nearly flat — same rule as the birthday overlay.
   The class sits on the banner ROOT, so ONE selector stops the drifting marks and the marquee
   alike; a second mechanism for the second animation is how one of them keeps running in a pocket. */
.cdy.cdy-paused .cdy-dot, .cdy.cdy-paused .cdy-track{animation-play-state:paused}
/* No motion layer is built at all under reduced motion; this is the belt to that pair of braces. */
@media (prefers-reduced-motion: reduce){ .cdy-dot{animation:none; opacity:.2} }

/* ---- the home gate (index.html) --------------------------------------------------------------
   The home screen is held invisible until every block has settled, then revealed in ONE frame —
   see the FSHomeGate comment in index.html for why. Only opacity moves: the layout is already
   built underneath, so the reveal cannot shift anything.

   NEITHER CLASS PRESENT = no gate, `.home` at its natural opacity. That is the back-out state
   (HOME_GATE = false) and it is also what every other screen sees, so this costs nothing anywhere
   the gate does not run. */
html.home-gated .home{opacity:0}
html.home-revealed .home{opacity:1; transition:opacity 150ms ease}
/* Reduced motion gets the same reveal as an instant swap — the page still appears complete in one
   frame, which is the point; only the fade goes. */
@media (prefers-reduced-motion: reduce){ html.home-revealed .home{transition:none} }

/* ---- the charter drop-off marquee — THE ONE LOUD ELEMENT IN THE APP --------------------------
   A full-bleed band on home carrying `DROP OFF DAY!!!` right-to-left on a continuous loop. It is
   the deliberate exception to this file's quiet register and it is narrow: CHARTER disembark, HOME
   only. An owner departure keeps the standard banner.

   LOUD THROUGH SIZE AND MOTION, NEVER THROUGH COLOUR. Same marine accent as the rest of the
   banner, on the existing surface token. No new hex, and no red — red is reserved for genuine
   alert states (§12) and a drop-off day is not one.

   FULL-BLEED WITHOUT A HORIZONTAL SCROLLBAR. The negative margin is exactly the screen padding, so
   the band reaches the edges of the content column and stops there. `100vw` would have been wider
   than the document on any platform that counts the scrollbar, and the body must never scroll
   sideways because of a decoration. overflow:hidden on the band is the second guarantee. */
.cdy-loud{display:block; position:relative; overflow:hidden;
  margin-left:calc(-1 * var(--pad-screen)); margin-right:calc(-1 * var(--pad-screen));
  border-left:0; border-right:0; border-radius:0;
  background:var(--surface); border-top:2px solid var(--marine); border-bottom:2px solid var(--marine)}
/* The band's own height. Capped at a quarter of the viewport AND at 140px, whichever is smaller —
   it may be the loudest thing on the page but it may not own the page. */
.cdy-loud .cdy-main{display:flex; align-items:center; padding:0;
  height:clamp(72px, 25vh, 140px); max-height:140px; min-height:0}
.cdy-band{display:block; width:100%; overflow:hidden; white-space:nowrap}
/* The track holds the string FOUR TIMES (MARQUEE_COPIES in charter-day.js) and travels exactly
   -25% — one copy width — so at the moment it wraps the next copy is standing where the last began.
   THE TRAVEL DISTANCE IS NOT THE WHOLE CONDITION: the band also has to stay full, which needs
   (copies - 1) to be at least as wide as the band. One copy of this string is 7.66em, which is
   490px against an 880px band at the 64px cap — so two copies emptied the right of the band over
   the back of every cycle and snapped full at the wrap. Four clears the worst case (1.80) twice over.
   THE TRAVEL DISTANCE COMES FROM THE JS (--cdy-travel = -100%/copies), so the count is stated once;
   the -25% below is only the fallback if the property never arrives.
   10s per traverse: the middle of the 8-12s the brief allows. Faster reads as an alarm, slower
   reads as broken. */
.cdy-track{display:inline-block; will-change:transform; animation:cdy-marquee 10s linear infinite}
@keyframes cdy-marquee{ from{transform:translateX(0)} to{transform:translateX(var(--cdy-travel, -25%))} }
/* The largest the type scale carries: Plex Cond, its only weight (600). Sized up for the band, not
   restyled — same family, same weight, same letter-spacing discipline as .t-title. */
.cdy-word{display:inline-block; padding-right:.6em;
  font-family:var(--font-cond); font-weight:600; letter-spacing:-.01em;
  font-size:clamp(34px, 9vw, 64px); line-height:1; color:var(--marine)}
/* The dismiss control floats over the band rather than taking a column from it, so the words run
   the full width. It keeps the app's tap token: it had been cut to 36px, which made the only
   control on the app's largest banner its smallest target — and every pixel around it is a link to
   the day, so a near miss navigated away instead of dismissing. 48px still fits inside the band's
   72px minimum. */
.cdy-loud .cdy-x{position:absolute; top:0; right:0; margin:0; padding:0 12px; z-index:calc(var(--z-card) + 1);
  min-height:var(--tap); background:var(--surface); border-bottom-left-radius:var(--r-ctrl)}

/* REDUCED MOTION: the SAME WORDS, the SAME SIZE, standing still and centred. Not a smaller banner,
   not a hidden one. Done here rather than with a second markup path, so the two cannot diverge —
   the duplicate copy is simply dropped and the track stops travelling. */
@media (prefers-reduced-motion: reduce){
  .cdy-track{animation:none; display:block; text-align:center}
  .cdy-track .cdy-word[aria-hidden="true"]{display:none}
  .cdy-word{padding-right:0}
}

/* ---- in-app notices (home "For you" card) ----------------------------------------------------
   A notice is a row plus its own dismiss. Existing tokens only; no new colour. */
.ntc{display:flex; align-items:center; gap:8px; border-top:1px solid var(--rule)}
.ntc:first-of-type{border-top:0}
.ntc > .row, .ntc > a.row{flex:1 1 auto; min-width:0}
.ntc .ntc-x{flex:0 0 auto; min-height:var(--tap); padding:0 4px; font-size:14px; color:var(--slate)}

/* The bar scrolls when the labels do not fit, and a scrolled bar has to SAY it scrolls. Without
   this the last tab was simply cut mid-word at 375px and read as a rendering fault. The fade is a
   mask, so it costs no colour value and works on either theme; `scroll-padding` keeps a tab from
   coming to rest underneath it. Labels are still kept short (see work.html) — this is the fallback
   for a vessel whose role names are longer than Friendship's. */
.subtabs{display:flex; gap:4px; margin:0 0 var(--sp-2); background:var(--surface); border:1px solid var(--rule);
  border-radius:var(--r-pill); padding:4px; overflow-x:auto; scrollbar-width:none;
  scroll-padding-inline:18px;
  -webkit-mask-image:linear-gradient(90deg,#000 calc(100% - 22px),transparent);
  mask-image:linear-gradient(90deg,#000 calc(100% - 22px),transparent)}
.subtabs::-webkit-scrollbar{display:none}
.subtabs button{flex:1 1 auto; white-space:nowrap; min-height:40px; padding:0 16px; border:0; background:transparent;
  color:var(--slate); font-weight:500; font-size:15px; border-radius:var(--r-pill)}
.subtabs button.on{background:var(--marine-tint); color:var(--marine)}
/* The same strip as LINKS (7 Sep 2026): the Day tab's three horizons — Day, Week, Month — are three
   screens, so the horizon control is the sub-tab component with an <a> per horizon rather than a
   button that switches a card. Same geometry, same `.on`; a link keeps middle-click and the
   precached page load that a button would not. */
.subtabs a{flex:1 1 auto; white-space:nowrap; min-height:40px; padding:0 16px; border:0; background:transparent;
  color:var(--slate); font-weight:500; font-size:15px; border-radius:var(--r-pill); text-decoration:none;
  display:inline-flex; align-items:center; justify-content:center}
.subtabs a.on{background:var(--marine-tint); color:var(--marine)}

/* ---- guest order bar --------------------------------------------------------------------------
   Pinned to the bottom of the Requests tab, above the tab bar. It replaces a Send button that sat
   mid-page and a scroll-to-send jump: the guest can add from any section and the total stays in
   view. Absent, not disabled, with nothing selected. Existing tokens only. */
.gorder{position:fixed; left:0; right:0; bottom:var(--tabbar-h, 64px); z-index:calc(var(--z-header) - 1);
  padding:10px 16px calc(10px + env(safe-area-inset-bottom, 0px));
  background:var(--surface); border-top:1px solid var(--rule);
  display:flex; flex-direction:column; gap:8px}
.gorder-top{display:flex; align-items:center; gap:8px; min-height:24px}
.gorder-n{font-weight:600}
.gorder-clear{margin-left:auto; background:none; border:0; font:inherit; font-size:14px;
  color:var(--slate); min-height:var(--tap); padding:0 4px}
.gorder[hidden]{display:none}
.gorder input{width:100%; box-sizing:border-box; font:inherit; font-size:16px; padding:10px;
  border:1px solid var(--rule); border-radius:var(--r-ctrl); background:var(--bg); color:var(--ink)}
/* The bar overlays the foot of the list; give the tab its own clearance so the last tile is reachable. */
#tabRequests{padding-bottom:180px}

/* Quantity stepper on a catalogue tile. Present on EVERY category, not only drinks. */
.rmqstep{display:flex; align-items:center; gap:4px; padding:0 8px 8px}
.rmq{min-width:32px; min-height:32px; border:1px solid var(--rule); border-radius:var(--r-ctrl);
  background:transparent; color:var(--ink); font:inherit; font-size:17px; line-height:1}
.rmqn{flex:1 1 auto; text-align:center}

/* ---- deck plans: list rows + full-screen viewer ------------------------------------------------
   Existing tokens only, no new hex. NO RED anywhere here: a fire plan is reference material, not an
   alert state, and red stays reserved for alert/overdue. */

/* The row is the existing .gdoc list component with a thumbnail slot added — not a bespoke card. */
.gdeck{align-items:center; gap:12px}
.gdeck-th{width:56px; height:40px; flex:0 0 56px; object-fit:cover; border-radius:var(--r-ctrl);
  border:1px solid var(--rule); background:var(--bg)}
.gdeck-th-none{display:flex; align-items:center; justify-content:center; color:var(--slate)}

/* SOLID surface, never a translucent scrim — thin black linework over a see-through background is
   the one thing that makes a plan unreadable. */
.planview{position:fixed; inset:0; z-index:var(--z-sheet); background:var(--bg);
  display:flex; flex-direction:column; overscroll-behavior:contain}
/* display:flex above beats the UA's [hidden]{display:none}, so hidden must be re-asserted — same
   pattern as .cap-pop/.sheet-ov. Without it the closed viewer still covers the page and eats taps. */
.planview[hidden]{display:none}
body.pv-open{overflow:hidden}
.pv-bar{display:flex; align-items:center; gap:8px; min-height:52px; padding:0 8px 0 16px;
  padding-top:env(safe-area-inset-top, 0px);
  background:var(--surface); border-bottom:1px solid var(--rule)}
.pv-deck{flex:1 1 auto; min-width:0; font-family:var(--font-cond); font-weight:600; font-size:18px;
  color:var(--ink); overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.pv-close{min-width:var(--tap); min-height:var(--tap); border:0; background:transparent;
  color:var(--slate); display:flex; align-items:center; justify-content:center}
.pv-stage{flex:1 1 auto; position:relative; overflow:hidden; touch-action:none;
  display:flex; align-items:center; justify-content:center; background:var(--bg)}
/* transform-origin centre so pinch scales about the middle of the plan, and no transition —
   a tween on a pinch feels like lag. */
.pv-img{position:absolute; transform-origin:center center; will-change:transform;
  max-width:none; max-height:none; user-select:none; -webkit-user-drag:none}

/* ---- splash / loading overlay ------------------------------------------------
   ONE implementation for every screen. The markup sits in each page's HTML as the
   first child of <body> so the black ground is painted on the FIRST paint, before
   any script runs; /js/loading-screen.js owns the removal and is the only place
   that logic exists.

   Black is the single new colour value in the system and lives here once, as
   --splash-bg. Nothing else may hardcode it.

   The overlay carries .dark-surface — the existing primitive for an element that
   is dark whatever the app theme is — so the bar picks up the night --marine
   (#2A8F88) rather than the day one (#0F5C58), which all but vanishes on black.
   No new accent value. The mark itself is black ink on transparent, so it is
   inverted to read on the black ground: the same treatment .auth-logo already
   gets on the night theme. */
:root{ --splash-bg:#000000; }
.fs-splash{
  position:fixed; inset:0; z-index:var(--z-toast); background:var(--splash-bg);
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  /* full bleed edge to edge: the ground covers the safe areas, the content sits inside them */
  padding:env(safe-area-inset-top,0px) env(safe-area-inset-right,0px)
          env(safe-area-inset-bottom,0px) env(safe-area-inset-left,0px);
  overscroll-behavior:contain;
}
.fs-splash-logo{width:min(60vw,280px); height:auto; filter:invert(1)}
/* indeterminate: a looping sweep, never a percentage. */
.fs-splash-bar{width:200px; height:3px; margin-top:32px; border-radius:var(--r-pill);
  background:rgba(255,255,255,.14); overflow:hidden}
.fs-splash-bar::after{content:''; display:block; width:40%; height:100%;
  border-radius:var(--r-pill); background:var(--marine);
  animation:fs-splash-sweep 1.15s ease-in-out infinite}
@keyframes fs-splash-sweep{from{transform:translateX(-110%)} to{transform:translateX(360%)}}
/* Fade is applied by loading-screen.js immediately before removal, and only when the
   screen took long enough to be seen (see INSTANT_MS there). */
.fs-splash.fs-splash-out{opacity:0; transition:opacity 200ms linear}
@media (prefers-reduced-motion:reduce){
  /* Static bar at rest — full width, so it never implies a percentage. Timings unchanged. */
  .fs-splash-bar::after{animation:none; width:100%; opacity:.6}
}

/* ================================================================================================
   GUEST SURFACE — the page frame and the disclosure row.
   Lived in guest.html's own <style> until the Onboard Essentials page needed the same components.
   MOVED, NOT COPIED (§17): two screens rendering the same row from two stylesheets is how the two
   drift. Nothing on the crew side uses these classes; they are here because guest.html and
   onboard-essentials.html both do.
   ================================================================================================ */
.gwrap{max-width:520px; margin:0 auto; padding:var(--sp-3, 20px)}
.gsec{margin:28px 0 0}
.gsec h2{font-family:var(--font-cond); font-weight:600; font-size:20px; margin:0 0 10px}
.gempty{color:var(--slate); font-size:14px; padding:10px 0}

/* ---- the disclosure row -------------------------------------------------------------------------
   One row per answer: icon, title, a line that is useful WITHOUT opening it, and the full answer
   behind the tap. The row still has to be worth its place when it is shut, which is why the Wi-Fi
   credentials live on the row rather than behind it. */
.grows{display:flex; flex-direction:column; gap:10px}
.grow{display:flex; align-items:flex-start; gap:14px; width:100%; text-align:left; cursor:pointer;
  padding:24px 18px; border:1px solid var(--rule); border-radius:var(--r-card,14px);
  background:var(--surface); color:var(--ink); font:inherit}
.grow-ic{flex:0 0 auto; width:50px; height:50px; border-radius:999px; display:flex;
  align-items:center; justify-content:center; background:var(--marine-tint); color:var(--marine)}
.grow-ic .ic{width:27px; height:27px}
.grow-txt{flex:1; min-width:0; padding-top:2px}
.grow-t{display:block; font-size:19px; font-weight:600; line-height:1.25}
/* The line that has to earn the row its place when it is shut. For Wi-Fi it IS the answer. */
.grow-s{display:block; margin-top:5px; font-size:14.5px; line-height:1.4; color:var(--slate)}
.grow-s .mono{font-family:var(--font-mono,ui-monospace,monospace); color:var(--ink); font-size:14px}
.grow-cv{flex:0 0 auto; margin-top:13px; color:var(--slate); transition:transform .15s ease}
.grow-cv .ic{width:22px; height:22px}
.grow[aria-expanded="true"] .grow-cv{transform:rotate(90deg)}
.grow[aria-expanded="true"]{border-color:color-mix(in srgb,var(--marine) 40%,var(--rule))}
/* The answer. Indented to the row's text column so it reads as belonging to the row above it. */
.grow-body{padding:0 18px 22px 82px; margin-top:-10px; font-size:15px; line-height:1.55;
  color:var(--ink); border:1px solid var(--rule); border-top:0;
  border-radius:0 0 var(--r-card,14px) var(--r-card,14px); background:var(--surface);
  white-space:pre-wrap}
.grow-body[hidden]{display:none}
/* An open row and its body are one object, so the seam between them closes up. */
.grow[aria-expanded="true"]{border-radius:var(--r-card,14px) var(--r-card,14px) 0 0; border-bottom-color:transparent}
/* A row that LEAVES the page rather than opening — same component, an arrow instead of a caret and
   no rotation, because nothing unfolds. */
a.grow{text-decoration:none}
a.grow .grow-cv{margin-top:13px}

/* Header back button (optional, both variants — see js/header.js). Rendered only when the caller
   passes backHref; there is no caret-left in the icon set, so the existing caret is turned. */
.appheader .ah-back{margin-left:-10px; margin-right:0}
.appheader .ah-back .ic{transform:rotate(180deg)}

/* ==== SHARED COMPONENTS (appraisal CR-8) — rules that three or more screens each carried a copy of.
   The field wrapper, the picker button, the photo strip and lightbox, the receipt thumbnail strip,
   the day navigator, the advisory line and the weather line. Defined here once; the pages no longer
   restate them. A page that needs a variant overrides a property, never the whole rule. ==== */
.sf{margin:0 0 var(--sp-2)}
/* sheet pick button + assign rows + segmented control (mirrors work.html) */
  .pickbtn{display:inline-flex; align-items:center; gap:8px; width:100%; justify-content:flex-start; min-height:var(--tap);
    padding:0 14px; border:1px solid var(--rule); border-radius:var(--r-ctrl); background:var(--surface); color:var(--ink);
    font-family:var(--font-mono); font-size:15px}
.pickbtn .ic{color:var(--slate)}
.pickbtn.set{border-color:color-mix(in srgb,var(--marine) 45%,var(--rule)); background:var(--marine-tint)}
/* snag photos */
  .snphotos{display:flex; flex-wrap:wrap; gap:6px; margin-top:8px}
.snthumb{width:54px; height:54px; border-radius:var(--r-ctrl); overflow:hidden; border:1px solid var(--rule);
    background:var(--surface); padding:0; position:relative; flex:0 0 auto; -webkit-tap-highlight-color:transparent}
.snthumb img{width:100%; height:100%; object-fit:cover; display:block}
.snthumb.add{display:flex; align-items:center; justify-content:center; color:var(--slate); border-style:dashed}
.snthumb.pending img{opacity:.5}
/* remove affordance reuses the existing .sf-photos .rm treatment — no new colours or shapes */
  .snthumb-wrap{position:relative; display:inline-block}
.snthumb-wrap .rm{position:absolute; top:-7px; right:-7px; width:22px; height:22px; border-radius:50%;
    background:var(--ink); color:var(--paper); border:0; display:flex; align-items:center; justify-content:center; padding:0}
.snthumb .pend{position:absolute; left:0; right:0; bottom:0; text-align:center; letter-spacing:.06em;
    font-family:var(--font-mono); font-size:9px; line-height:1.5; background:var(--amber); color:#1a1200}
.sf-photos{display:flex; flex-wrap:wrap; gap:10px; margin-top:8px}
.sf-photos .th{position:relative}
.sf-photos .rm{position:absolute; top:-7px; right:-7px; width:22px; height:22px; border-radius:50%;
    background:var(--ink); color:var(--paper); border:none; display:flex; align-items:center; justify-content:center; padding:0}
.photo-ov{position:fixed; inset:0; z-index:calc(var(--z-sheet) + 1); background:rgba(8,10,12,.94); display:flex; flex-direction:column}
.photo-ov img{flex:1; min-height:0; width:100%; object-fit:contain}
.photo-ov .bar{display:flex; justify-content:space-between; gap:8px; padding:12px; padding-top:max(12px,env(safe-area-inset-top))}
/* staged receipt photos — horizontal row of thumbnails, each with a small × to remove before submit */
  .rthumbs{display:flex; gap:8px; flex-wrap:nowrap; overflow-x:auto; margin-top:10px; padding:8px 8px 4px 0; -webkit-overflow-scrolling:touch}
.rthumb{position:relative; flex:0 0 auto; width:72px; height:72px; border:1px solid var(--rule); border-radius:var(--r-ctrl); background:var(--paper)}
.rthumb img{width:100%; height:100%; object-fit:cover; display:block; border-radius:var(--r-ctrl)}
/* badge overhangs the corner — .rthumb has NO overflow clip, and .rthumbs pads 8px so the scroll box doesn't trim it */
  .rthumb .rm{position:absolute; top:-6px; right:-6px; width:22px; height:22px; border-radius:50%; background:var(--ink); color:var(--paper); border:0; display:flex; align-items:center; justify-content:center; padding:0; cursor:pointer}
/* ---- day navigator (shared shape with the Day tab) ---- */
  .daynav{display:flex; align-items:center; gap:8px; margin:0 0 var(--sp-2)}
.daynav .nb{width:var(--tap); height:var(--tap); flex:0 0 auto; display:inline-flex; align-items:center; justify-content:center;
    border:1px solid var(--rule); border-radius:var(--r-ctrl); background:var(--surface); color:var(--ink)}
.daynav .nb:active{background:var(--paper)}
.daynav .nb[disabled]{opacity:.4}
.daynav .nb .ic{color:var(--slate)}
.daynav .lbl{flex:1 1 auto; text-align:center; line-height:1.2}
.daynav .lbl .rel{display:block; font-size:13px; color:var(--slate)}
.daynav .lbl .dt{font-family:var(--font-mono); font-size:16px; color:var(--ink)}
.daynav .today{flex:0 0 auto}
.advisory{display:flex; gap:10px; align-items:flex-start; margin-top:var(--sp-2)}
.advisory .ic{color:var(--amber); flex:0 0 auto; margin-top:1px}
/* ---- position / meta lines ---- */
  .wxline{display:flex; align-items:flex-start; gap:10px; padding:2px 0}
.wxline .ic{color:var(--slate); flex:0 0 auto; margin-top:1px}
.wxline .wxtxt{flex:1 1 auto; min-width:0; line-height:1.4}
.wxline .wl{color:var(--slate); font-size:13px; display:block}

/* THE PLANNED ROW (js/day-plan-rows.js) — meals, movements, guest movements, visitors and deliveries
   on the planning screen AND the Day screen. Rendered with class "prow planrow"; the .planrow rules
   are the component, so a screen with its own .prow (the passage and mode lists) is untouched. */
/* ---- a planned row: stacked lines that WRAP, never a clipped fragment ------------------------- */
  /* It used to be one line with the detail ellipsised, which read as "5 · Water Sports…" — the crew
     could see there was a location and a note and could not see either. Nothing on this screen
     truncates horizontally now; the 2-line note clamp is the only overflow treatment here. */
  .planrow{display:flex; align-items:baseline; gap:10px; padding:10px 0; min-height:44px; box-sizing:border-box;
    border-top:1px solid var(--rule); width:100%;
    text-align:left; background:none; border-left:0; border-right:0; border-bottom:0; color:var(--ink); -webkit-tap-highlight-color:transparent}
.planrow:first-child{border-top:0}
.planrow:active{background:var(--paper)}
.planrow .p-name{flex:0 0 auto; font-weight:600}
.planrow .p-body{flex:1 1 auto; min-width:0; color:var(--slate); font-size:14px}
/* The three stacked lines. .p-main is one flex COLUMN, and the row stays baseline-aligned, so a
     right-aligned caret sits on the title line however many lines the meta wraps to. Empty lines are
     absent from the DOM (§12) — a row with no meta and no note is line 1 alone, no reserved space. */
  .planrow .p-main{flex:1 1 auto; min-width:0; display:flex; flex-direction:column; gap:2px}
.planrow .p-title{display:flex; align-items:center; flex-wrap:wrap; gap:8px; min-width:0}
.planrow .p-label{min-width:0; font-weight:600}
.planrow .p-meta,.planrow .p-note{color:var(--slate); font-size:13px; line-height:1.4}
/* The note is free text and can be a paragraph; two lines here, the whole of it on the edit sheet. */
  .planrow .p-note{display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:2; line-clamp:2; overflow:hidden}
.timechip{flex:0 0 auto; display:inline-flex; align-items:center; height:24px; padding:0 9px; border-radius:var(--r-pill);
    font-family:var(--font-mono); font-size:13px; border:1px solid var(--rule); color:var(--ink); background:var(--surface); white-space:nowrap}
/* TBC and part-of-day are NEUTRAL — an unknown time is not an alert. */
  .timechip.soft{color:var(--slate); font-family:var(--font-sans)}
