/* ============================================================================
   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;
  /* 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;
  --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}
@media print{ .collhead{padding:0} .collhead .coll-caret{display:none} }

/* ---- 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)}
.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:1}
.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:50; 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:1; 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:60; 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:80; 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:200; 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:2}
.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:300;
  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{
  .appheader,.tabbar,.toast-wrap,.sheet-ov,.ah-btn,.ah-search{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 /position.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. z-index 750 clears Leaflet's marker/popup panes (600 /
     700) but stays under its controls (800), so the OSM attribution link is still tappable. */
  .cond .pos-map{position:relative; 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 and chevron — the rows are not separately tappable. */
  .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:60; 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. */
.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}
.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)}

/* ---- 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:40;
  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:200; 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:9999; 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}
}
