/* portal.css — the client portal.
   One quiet palette: paper grey, white surfaces, one accent, and colour only where it carries meaning
   (a document that has lapsed, a decision that is waiting). The brand accent arrives per tenant as
   --accent / --heading from portal_brand; everything else is fixed here. No gradients, no shadows to
   speak of, no decoration that is not information. */
:root {
  --bg: #f5f6f8;
  --surface: #ffffff;
  --line: #e5e7eb;
  --line-2: #eef0f3;
  --ink: #111827;
  --ink-2: #4b5563;
  /* Both of these are TEXT, and both were under WCAG AA on this portal's own paper-grey ground
     (#6b7280 = 4.47:1, #9ca3af = 2.35:1). Same greys, darkened until they pass (UI audit,
     17 Sep 2026). --ink-4 is used for the quietest asides - "Dates are shown on the Manila office
     calendar" - which a principal still has to be able to read. */
  --ink-3: #5d6470;   /* 5.51:1 on --bg */
  --ink-4: #646b78;   /* 4.96:1 on --bg - the quietest tier, and still readable */
  --heading: #0f2a47;
  --accent: #2457c5;
  --accent-bg: #eef3fc;
  --accent-ink: #1d4ed8;
  --ok: #15803d;     --ok-bg: #ecfdf5;
  --warn: #b45309;   --warn-bg: #fffbeb;
  --bad: #b91c1c;    --bad-bg: #fef2f2;
  --neutral: #4b5563; --neutral-bg: #f3f4f6;
  --font: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --mono: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --r: 8px;
  --side-w: 236px;
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body { font-family: var(--font); font-size: 14px; line-height: 1.5; color: var(--ink); background: var(--bg); -webkit-font-smoothing: antialiased; }
a { color: var(--accent-ink); text-decoration: none; }
a:hover { text-decoration: underline; }
h1, h2, h3 { margin: 0; color: var(--heading); font-weight: 600; letter-spacing: -0.01em; text-wrap: balance; }
h1 { font-size: 26px; line-height: 1.2; font-weight: 700; }
h2 { font-size: 17px; }
/* Sentence case, no tracking: the owner's restraint rule, and on a phone a letter-spaced capital run is
   simply wider for no gain. Weight and colour carry the hierarchy instead. */
h3 { font-size: 13px; color: var(--ink-3); font-weight: 600; }
p { margin: 0 0 .8em; }
strong { font-weight: 600; }
.muted { color: var(--ink-3); } .small { font-size: 12.5px; } .tnum { font-variant-numeric: tabular-nums; }
.inline { display: inline; }
.skip { position: absolute; left: -999px; top: 0; background: var(--heading); color: #fff; padding: 8px 12px; z-index: 99; }
.skip:focus { left: 8px; top: 8px; }
/* Said to a screen reader, not drawn: the phone menu's button carries what its badge is counting. */
.sr-only { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
/* utilities used instead of style attributes (the CSP forbids inline styles) */
.m-0 { margin: 0 !important; } .block { display: block !important; } .nowrap { white-space: nowrap; } .maxw-520 { max-width: 520px; }
.mt-6 { margin-top: 6px; } .mt-8 { margin-top: 8px; } .mt-10 { margin-top: 10px; } .mt-12 { margin-top: 12px; } .mt-14 { margin-top: 14px; } .mt-16 { margin-top: 16px; } .mt-18 { margin-top: 18px; }
.mb-12 { margin-bottom: 12px; } .mb-16 { margin-bottom: 16px; } .mb-20 { margin-bottom: 20px; }

/* ---------------------------------------------------------------- shell */
.app { display: grid; grid-template-columns: var(--side-w) 1fr; min-height: 100vh; }
.side { background: var(--surface); border-right: 1px solid var(--line); display: flex; flex-direction: column; position: sticky; top: 0; height: 100vh; }
.logo { display: block; padding: 18px 20px 14px; border-bottom: 1px solid var(--line-2); }
/* THE MARK IS SIZED BY HEIGHT, IN ONE PLACE. This said width:132px, and a blanket
   `.logo img { height: 34px }` at the very END of this file then overrode it at every width — so the
   client's mark rendered about 51 px wide on a 1265 px desktop, which is what the R6 review saw as a
   header mark that is "hard to recognize". Height is the right axis (the asset is 220x147, mostly margin)
   and this is now the only unscoped rule for it; the login card and the narrow widths override it below. */
.logo img { display: block; width: auto; height: 58px; max-width: 100%; object-fit: contain; }
.logo-text { font-weight: 700; font-size: 16px; color: var(--heading); }
.side nav { padding: 10px 12px 16px; flex: 1; overflow-y: auto; }
.side-sec { font-size: 11.5px; font-weight: 600; color: var(--ink-3); padding: 16px 10px 6px; }
.side-item { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 8px 10px; border-radius: 6px; color: var(--ink-2); font-weight: 500; text-decoration: none; }
.side-item:hover { background: var(--bg); text-decoration: none; color: var(--ink); }
.side-item.on { background: var(--neutral-bg); color: var(--heading); font-weight: 600; box-shadow: inset 3px 0 0 var(--accent); }
.side-foot { padding: 14px 20px; border-top: 1px solid var(--line-2); font-size: 11.5px; color: var(--ink-4); line-height: 1.4; }
/* THE PHONE MENU'S BUTTON EXISTS ONLY ON A PHONE. Above 640 px the sidebar is the menu and there is
   nothing to open; the button is in the markup at every width so the shell has one shape, and the phone
   block below is the only place it is shown. */
.nav-toggle { display: none; }
/* --accent-strong, not --accent: these three carry WHITE TEXT, and a mid-tone brand colour under
   white is not readable. layout.php derives it from the tenant's own hue. */
.badge { display: inline-block; min-width: 20px; padding: 0 6px; border-radius: 10px; background: var(--accent-strong, var(--accent)); color: #fff; font-size: 11.5px; font-weight: 700; text-align: center; line-height: 20px; }
.body { min-width: 0; display: flex; flex-direction: column; }
.topbar { height: 60px; background: var(--surface); border-bottom: 1px solid var(--line); display: flex; align-items: center; justify-content: space-between; padding: 0 28px; gap: 16px; }
.clocks { display: flex; gap: 18px; font-size: 12.5px; color: var(--ink-3); min-width: 0; }
.clock b { font-weight: 600; color: var(--ink-2); margin-right: 6px; }
.clock time { font-variant-numeric: tabular-nums; color: var(--ink); font-weight: 500; }
.topbar-right { display: flex; align-items: center; gap: 18px; }
.may { font-size: 12px; color: var(--ink-3); border: 1px solid var(--line); border-radius: 6px; padding: 5px 10px; background: var(--bg); }
.may b { color: var(--accent-ink); font-weight: 600; font-size: 12.5px; }
.user { display: flex; align-items: center; gap: 10px; }
.user-text { display: flex; flex-direction: column; align-items: flex-end; line-height: 1.2; }
.user-text strong { font-size: 13.5px; color: var(--ink); }
.user-text span { font-size: 11.5px; color: var(--ink-3); }
.user-init { width: 34px; height: 34px; border-radius: 50%; background: var(--accent-strong, var(--accent)); color: #fff; display: inline-flex; align-items: center; justify-content: center; font-weight: 600; font-size: 12.5px; flex: none; }
.btn-out { font: inherit; font-size: 12.5px; color: var(--ink-2); background: none; border: 1px solid var(--line); border-radius: 6px; padding: 6px 10px; cursor: pointer; }
.btn-out:hover { background: var(--bg); color: var(--ink); }
.main { padding: 26px 28px 48px; max-width: 1320px; width: 100%; }
.eyebrow { font-size: 12px; font-weight: 600; color: var(--ink-3); margin-bottom: 6px; }

/* ---------------------------------------------------------------- page head */
.page-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; margin-bottom: 18px; flex-wrap: wrap; }
.page-head > div:first-child { display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap; }
.page-head p { margin: 0; color: var(--ink-3); font-size: 14px; }
.btn-row { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }

/* ---------------------------------------------------------------- cards, grids, stats */
.grid { display: grid; gap: 16px; }
/* min(360px,100%) so a 320px viewport never overflows the track floor.
   align-items: start, so a CARD IS ITS OWN HEIGHT. Grid items stretch by default, so the shorter card in a
   row grew to match the taller one: a client viewer's proposal panel — a heading and one sentence saying an
   administrator decides — was rendered 427px tall around 78px of ink, an action-sized empty box with no
   action in it (R7 client-portal review). .grid-main already did this; .grid-2 did not. */
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(min(360px, 100%), 1fr)); align-items: start; }
/* The overview is not three equal cards: the fleet is the anchor and needs the room, the two
   action panels are narrow lists. minmax(0,...) so a wide table scrolls inside its own cell
   instead of forcing the grid wider. */
.grid-main { grid-template-columns: minmax(0, 1.7fr) minmax(0, 1fr); align-items: start; }
.stack { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
/* minmax(0, 1fr), NOT 1fr, for the same reason as .app above: a 1fr track's automatic minimum is its
   content's min-content width, and this cell holds a table. At 360-390 px the fleet card grew to 480 px
   and the whole overview scrolled sideways (post-fix retest, found once the widths were measured in a
   fresh context per width rather than by resizing one). The table scrolls inside .tbl-wrap instead. */
@media (max-width: 1100px) { .grid-main { grid-template-columns: minmax(0, 1fr); } }
.card { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r); padding: 16px 18px; }
.card h2 { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 10px; }
.card h2 a, .card h2 .small { font-size: 12.5px; font-weight: 500; }
/* 1px gap over a line-coloured ground draws every divider, including between wrapped rows —
   the old border-right approach lost the horizontal rule whenever the strip wrapped. */
.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr)); gap: 1px; background: var(--line-2); border: 1px solid var(--line); border-radius: var(--r); margin-bottom: 18px; overflow: hidden; }
.stat { padding: 14px 18px; background: var(--surface); text-decoration: none; color: inherit; display: flex; flex-direction: column; gap: 2px; }
.stat:hover { background: var(--bg); text-decoration: none; }
.stat .n { font-size: 24px; font-weight: 600; color: var(--heading); font-variant-numeric: tabular-nums; line-height: 1.1; }
.stat .l { font-size: 12.5px; color: var(--ink-3); }
/* Red is for a paper that has lapsed or a relief that is overdue — nothing else. "Proposals awaiting you"
   is work, not danger, so it reads in the heading colour and the label does the telling (.stat.due). */
.stat.hot .n { color: var(--bad); }
.stat.due .n { color: var(--heading); }
.stat.due .l { color: var(--ink-2); font-weight: 500; }

/* ---------------------------------------------------------------- tables */
.tbl-wrap { overflow-x: auto; }
table.tbl { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.tbl th { text-align: left; font-size: 12px; color: var(--ink-3); font-weight: 600; padding: 8px 10px; border-bottom: 1px solid var(--line); white-space: nowrap; }
.tbl td { padding: 10px; border-bottom: 1px solid var(--line-2); vertical-align: middle; }
.tbl tr:last-child td { border-bottom: 0; }
.tbl .num { text-align: right; font-variant-numeric: tabular-nums; }
.tbl .date { white-space: nowrap; font-variant-numeric: tabular-nums; }
.person { display: flex; align-items: center; gap: 10px; }
.person a { font-weight: 600; color: var(--ink); }
.person .sub { display: block; font-size: 12px; color: var(--ink-3); }
.group-head td { background: var(--bg); font-weight: 600; color: var(--heading); padding: 7px 10px; font-size: 12.5px; }

/* ---------------------------------------------------------------- avatars, chips, pills */
.av { display: inline-flex; width: 32px; height: 32px; border-radius: 50%; overflow: hidden; background: var(--neutral-bg); align-items: center; justify-content: center; flex: none; }
.av img { width: 100%; height: 100%; object-fit: cover; display: block; }
.av-init { font-weight: 600; font-size: 12px; color: var(--ink-2); }
.av-lg { width: 64px; height: 64px; } .av-lg.av-init { font-size: 20px; }
/* STATUS IS A WORD, NOT A COLOURED TABLET.
   Every status here used to be a filled pill — green "Documents in order", amber "Expires within 30 days",
   blue "Sent", red "Expired" — and a crew list at phone width became a column of tinted tablets with the
   facts squeezed between them. The office pages were restyled off that pattern months ago and the rule is
   the owner's: words and weight, with red kept for a paper that has LAPSED or a relief that is OVERDUE.
   So: no backgrounds, no borders; ordinary ink for "in order", darker ink and weight for "attention soon",
   red only where something is actually out of date. The markup and the class names are untouched, so every
   page and every test that names a chip still works. */
.chip, .pill { display: inline-block; padding: 0; border-radius: 0; background: none; font-size: 12.5px; font-weight: 500; white-space: nowrap; line-height: 18px; color: var(--ink-2); }
.chip-ok { color: var(--ink-3); font-weight: 500; }
.chip-warn { color: var(--ink-2); }
.chip-soon { color: var(--ink); font-weight: 600; }
.chip-expired { color: var(--bad); font-weight: 600; }
.chip-overdue { color: var(--bad); font-weight: 600; }
.chip-none { color: var(--ink-3); }
.pill-short { color: var(--bad); font-weight: 600; }
.pill { color: var(--ink-2); }
.pill-sent { color: var(--ink); font-weight: 600; }
.pill-accepted, .pill-on { color: var(--ink-2); font-weight: 600; }
.pill-declined { color: var(--ink-3); }
.pill-withdrawn { color: var(--ink-3); }
.pill-expired { color: var(--ink-2); font-weight: 600; }
.doc-list { margin: 0 0 8px; padding-left: 18px; } .doc-list li { margin: 3px 0; }
.pill-off { color: var(--ink-2); font-weight: 600; }
.ref { font-family: var(--mono); font-size: 12px; color: var(--ink-3); letter-spacing: .02em; }

/* ---------------------------------------------------------------- forms and buttons */
.field { display: flex; flex-direction: column; gap: 6px; margin-bottom: 14px; }
.field label { font-size: 12.5px; font-weight: 600; color: var(--ink-2); }
.field input, .field textarea, .field select { font: inherit; padding: 9px 11px; border: 1px solid var(--line); border-radius: 6px; background: #fff; color: var(--ink); }
.field input:focus, .field textarea:focus, .field select:focus, .btn:focus-visible, .btn-out:focus-visible, a:focus-visible, .side-item:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.btn { font: inherit; font-size: 13.5px; font-weight: 600; padding: 8px 14px; border-radius: 6px; border: 1px solid var(--accent-strong, var(--accent)); cursor: pointer; background: var(--accent-strong, var(--accent)); color: #fff; text-decoration: none; display: inline-block; line-height: 1.3; }
.btn:hover { filter: brightness(.94); text-decoration: none; }
.btn-accent { background: var(--accent-strong, var(--accent)); border-color: var(--accent-strong, var(--accent)); }
.btn-ghost { background: var(--surface); color: var(--ink); border-color: var(--line); }
.btn-ghost:hover { background: var(--bg); filter: none; }
.btn-danger { background: var(--surface); color: var(--bad); border-color: #f1c4c4; }
.btn-sm { padding: 5px 10px; font-size: 12.5px; }
.notice { padding: 11px 14px; border-radius: var(--r); border: 1px solid var(--line); background: var(--surface); margin-bottom: 16px; font-size: 13.5px; }
.notice-ok { border-color: #bfe3d0; background: var(--ok-bg); color: #14532d; }
.notice-bad { border-color: #f1c4c4; background: var(--bad-bg); color: #7f1d1d; }
.notice-info { border-color: #cfdcf5; background: var(--accent-bg); color: #1e3a8a; }
.empty { padding: 22px; text-align: center; color: var(--ink-3); border: 1px dashed var(--line); border-radius: var(--r); font-size: 13.5px; }

/* ---------------------------------------------------------------- kanban (proposals only) */
.board { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(290px, 100%), 1fr)); gap: 16px; align-items: start; }
.col { min-width: 0; }
.col-head { display: flex; align-items: center; justify-content: space-between; background: var(--surface); border: 1px solid var(--line); border-radius: var(--r); padding: 12px 14px; margin-bottom: 10px; }
.col-head strong { font-size: 14px; color: var(--heading); }
.col-head .cnt { font-size: 12px; font-weight: 600; color: var(--ink-3); background: var(--neutral-bg); border-radius: 6px; padding: 2px 8px; }
.col-head.hot { box-shadow: inset 3px 0 0 var(--bad); }
.kcard { background: var(--surface); border: 1px solid var(--line); border-radius: var(--r); padding: 14px; margin-bottom: 10px; }
.kcard-top { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; flex-wrap: wrap; }
.kcard-top .when { margin-left: auto; font-size: 12px; color: var(--ink-3); white-space: nowrap; }
.kcard .who { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.kcard .who strong { font-size: 14.5px; color: var(--ink); }
.kcard .who span { display: block; font-size: 12px; color: var(--ink-3); }
.kcard .line { font-size: 13px; color: var(--ink-2); margin: 2px 0; }
.kcard .line b { color: var(--ink); font-weight: 600; }
.kcard .actions { display: flex; gap: 8px; margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--line-2); }
details.more > summary { list-style: none; cursor: pointer; padding: 12px 14px; border: 1px dashed var(--line); border-radius: var(--r); color: var(--ink-3); font-size: 13px; text-align: center; background: var(--surface); }
details.more > summary::-webkit-details-marker { display: none; }
details.more[open] > summary { margin-bottom: 10px; }

/* ---------------------------------------------------------------- contract progress */
/* Width comes from the pw-* utility (10% steps) — the CSP forbids style attributes. */
.ctr { display: flex; align-items: center; gap: 8px; white-space: nowrap; }
.ctr-bar { width: 56px; height: 4px; border-radius: 2px; background: var(--line); overflow: hidden; flex: none; }
.ctr-bar i { display: block; height: 100%; background: var(--ink-3); }   /* a length, not a brand ribbon */
.ctr-bar.over i { background: var(--bad); }                             /* over the tour: the one red that belongs */
.pw-0{width:0}.pw-10{width:10%}.pw-20{width:20%}.pw-30{width:30%}.pw-40{width:40%}.pw-50{width:50%}
.pw-60{width:60%}.pw-70{width:70%}.pw-80{width:80%}.pw-90{width:90%}.pw-100{width:100%}

/* ---------------------------------------------------------------- list filter */
.filterbar { display: flex; gap: 10px; align-items: center; margin-bottom: 14px; flex-wrap: wrap; }
.filterbar input[type="search"] { font: inherit; padding: 8px 12px; border: 1px solid var(--line); border-radius: 6px; background: #fff; color: var(--ink); flex: 1; max-width: 340px; min-width: 180px; }
.filterbar input[type="search"]:focus { outline: 2px solid var(--accent); outline-offset: 2px; }
.filterbar .count { font-size: 12.5px; color: var(--ink-3); }
/* Named classes, because the portal's CSP forbids a style attribute: two pages carried one, so the margin
   was silently dropped and the console logged a violation on every load (UI sweep, 9 Sep 2026). */
.pager { margin: 0 0 10px; }
.board-spaced { margin-top: 12px; }
/* A FILTERED-OUT ROW STAYS OUT, AT EVERY WIDTH — and this needs !important, which is not decoration here.
   `display: none` on `tr.hide` has the same specificity as `.tbl tr { display: block }` in the phone block
   below, and that rule comes later in the file, so it won. Below 640 px the search counted correctly and
   then showed the rows it had just excluded: "3 of 22" above Andres, Benigno and Joaquin on Documents, and
   the same on Crew and Fleet (R7-UI-01). The count and the visible records disagreed, which is the one thing
   a client must be able to trust. This is a STATE rule and the phone block is a LAYOUT rule; the state has
   to outrank it, or every future responsive rule for tables re-opens the same hole. */
tr.hide, tfoot.hide, tbody.hide, thead.hide, section.card.hide,
.tbl tr.hide, .tbl tbody.hide, .tbl tfoot.hide { display: none !important; }
.badge-bad { background: var(--bad); }

/* ---------------------------------------------------------------- profile */
.profile-head { display: flex; gap: 16px; align-items: center; margin-bottom: 18px; }
.profile-head h1 { margin-bottom: 2px; }
.facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr)); gap: 12px 20px; margin: 0; }
.facts div { min-width: 0; }
/* ONE TREATMENT FOR A SECONDARY LABEL, AND IT IS NOT LETTER-SPACED CAPITALS. The vessel particulars were
   the last place in the portal using UPPERCASE with tracking, which the house restraint rule forbids and
   which made the same kind of label look different from a table heading two cards away (R7 review: "give
   labels a consistent secondary text weight"). Now identical to .tbl th — 12px, weight 600, muted, sentence
   case — so a reader recognises a label as a label wherever it appears. */
.facts dt { font-size: 12px; font-weight: 600; color: var(--ink-3); }
.facts dd { margin: 2px 0 0; font-weight: 500; font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------------- login */
.login-wrap { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 32px 16px; background: var(--bg); }
.login-card { width: 100%; max-width: 400px; background: var(--surface); border: 1px solid var(--line); border-radius: 10px; padding: 30px 30px 24px; }
.login-card .logo { padding: 0 0 18px; border: 0; }
/* HEIGHT here as well, and only here: mixing a width from this rule with a height from another stretched
   the mark to 150x44 out of a 220x147 asset. One axis, one place. The login card is the quiet centred card
   the owner settled on, so the mark is simply a little larger than in the sidebar. */
.login-card .logo img { height: 52px; max-width: 100%; }
.login-card h1 { font-size: 20px; margin-bottom: 4px; }
.login-card .btn { width: 100%; padding: 10px; margin-top: 4px; }
.login-foot { text-align: center; font-size: 12px; color: var(--ink-4); margin-top: 22px; }

/* ---------------------------------------------------------------- footer */
.foot { padding: 14px 28px; border-top: 1px solid var(--line-2); font-size: 12px; color: var(--ink-4); display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap; margin-top: auto; }

/* ---------------------------------------------------------------- responsive */
@media (max-width: 900px) {
  /* minmax(0, 1fr), not 1fr: a 1fr track's automatic minimum is its content's min-content width, and the menu (one row,
     nowrap, scrolling inside its own nav) made the whole page 880 px wide on a phone — every page scrolled sideways
     (owner, 8 Sep 2026). The three regions may shrink below their content; the tables and the menu scroll inside. */
  /* auto 1fr, not two automatic rows: .app is min-height:100vh, so on a SHORT page (the "Document
     unavailable" refusal, a 404) the collapsed menu row was stretched to share the viewport and .side nav,
     still flex:1 from the desktop rule, swallowed the slack — 224 px of menu above a two-line message at
     735 px wide, the heading pushed to 419 px (final QA QA-008). The menu row now keeps its content height
     (49 px, one scrolling row) and the body takes the rest, on every page, long or short. */
  .app { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto 1fr; }
  .side, .main, .topbar, .side nav { min-width: 0; max-width: 100%; }
  .side { position: static; height: auto; border-right: 0; border-bottom: 1px solid var(--line); }
  /* WRAP HERE TOO, not just on a phone. Between a phone and a laptop the menu was one nowrap row that
     scrolled: at 760 px the last sections sat past the right edge with nothing to say they were there, so
     "Proposals" and "Documents" were simply unreachable to anyone who did not think to drag the strip
     (R6 client-portal review, item 6). Wrapping puts every section on screen at every width; the row still
     scrolls if a tenant ever has more sections than fit two lines. */
  .side nav { display: flex; gap: 4px; flex-wrap: wrap; overflow-x: auto; padding: 6px 10px 8px; flex: 0 0 auto; }
  .side-sec { display: none; }
  .side-item { white-space: nowrap; padding: 8px 12px; flex: 0 0 auto; }
  .side-item.on { box-shadow: inset 0 -3px 0 var(--accent); }
  .side-foot { display: none; }
  .logo { padding: 12px 16px 10px; }
  .logo img { height: 40px; }
  .topbar { height: auto; padding: 10px 16px; flex-wrap: wrap; }
  .user-text { display: none; }
  /* .may stays visible: a mobile viewer must still see whether they can decide proposals. */
  .main { padding: 18px 16px 40px; }
  h1 { font-size: 21px; }
}
/* ================================================================ phones and small tablets
   The client portal is read on a phone in a car park as often as at a desk, and at 390 px the desk layout
   was doing three unkind things: every table cell wrapped into a tower ("FURE XXX 1" over two lines), the
   last cell of a wide row — including a Review button — sat outside the card, and a third of the screen
   went on a logo band, two clocks and a role chip before any content.

   So below 640 px a table stops being a grid and becomes a list of labelled facts: the header row is kept
   for screen readers, every cell prints its own column name (portal_label_cells() in the shell fills
   data-label from the thead), and nothing scrolls sideways. The rest is spacing and tap targets. No new
   colour, no chips, no decoration — the same quiet page, laid out for the screen it is on. */
@media (max-width: 640px) {
  .main { padding: 10px 12px 40px; }
  /* The eyebrow names the section above the heading — and on a phone the highlighted menu item already
     says which section this is, so on Overview it read "Overview" directly above "Overview". Dropping a
     line that repeats what is on screen buys 20 px of the top of the viewport and loses nothing. */
  .eyebrow { display: none; }
  h1 { font-size: 21px; }
  h2 { font-size: 16px; }
  .card { padding: 13px 13px; border-radius: 6px; }
  .grid { gap: 12px; }
  .page-head { margin-bottom: 14px; gap: 10px; }
  .page-head p { font-size: 13.5px; }
  /* THE DOCUMENT FILTER ON A PHONE: the field takes the whole row — beside the count its placeholder truncated at
     390 px — and the count and Clear sit together on the line below (R11 usability note, 11 Sep 2026). */
  .filterbar input[type="search"] { flex: 1 1 100%; max-width: none; min-width: 0; }
  .filterbar .count { order: 2; }
  .filterbar .btn { order: 3; }

  /* THE CHROME ABOVE THE FIRST HEADING IS A BUDGET. Measured on a 390x844 phone it was logo 53 + identity
     50 + menu 127 + topbar 54, and the first heading sat 317 px down — more than a third of the screen
     before anything a client came to read (R7 client-portal review). Trimmed on the bands that are not tap
     targets: the mark keeps its 34 px height, every menu item and button keeps at least 32 px, the wrap and
     the active marker stay. Verified with claude-dev/playwright/probe_portal_chrome.js, which fails if the
     chrome grows past its budget or a tap target shrinks below 30 px. */
  /* The mark is 220x147, so sizing it by WIDTH gave a 70 px band on a 390 px screen. Height decides. */
  .logo { padding: 7px 12px 6px; }
  .logo img { width: auto; height: 34px; max-width: 60%; object-fit: contain; }
  /* One line: the clocks take what is left and end in an ellipsis rather than pushing the account
     controls onto a line of their own with an empty half beside them. */
  /* THE ACCOUNT CONTROLS ARE TAP TARGETS TOO (R17). The menu's links were given 44 px and these were left
     at 34, which is the same mean thumb the earlier review objected to. They are 44 px now and the band is
     NOT taller for it: the row's own padding comes down by the three pixels the controls gained, so this
     stays the 48 px it measured before. */
  .topbar { height: auto; min-height: 48px; padding: 2px 12px; gap: 10px; flex-wrap: nowrap; }
  .user { min-height: 44px; }
  .btn-out { min-height: 44px; display: inline-flex; align-items: center; }
  .clocks { flex: 1 1 auto; min-width: 0; gap: 12px; font-size: 12px; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
  .clock { overflow: hidden; text-overflow: ellipsis; }
  .topbar-right { flex: none; gap: 10px; }
  .may { display: none; }                    /* the role is on the account page; the chip is not worth a band */
  .user-text { display: none; }              /* the initials disc already names the account */
  .btn-out { padding: 8px 12px; }            /* a real tap target */

  /* the collapsed menu: items sized to their words, the badge beside the label */
  /* WRAP, don't hide. The one-row scroller cut "Expiry register" in half at the screen edge, which reads as
     a broken page; a phone has room for two or three short rows and nothing is then out of sight. (The
     row still scrolls if a tenant ever has more sections than fit.) */
  /* TOUCH COMFORT BEATS A SHORTER BAND. These were 33 px, which works with a mouse and is mean with a
     thumb; the R8 review asked for 40-44 px. That costs about 25 px of the top of the screen, and it is
     worth it — the chrome budget in probe_portal_chrome.js was raised to match, deliberately, rather than
     the links being squeezed to protect a number. */
  .side nav { padding: 5px 8px 6px; gap: 4px; flex-wrap: wrap; overflow-x: auto; }
  .side-item { padding: 10px 11px; justify-content: flex-start; gap: 6px; flex: 0 0 auto; min-height: 40px; }
  .side-item.on { box-shadow: inset 0 -3px 0 var(--accent); }

  /* ---- THE MENU FOLDS AWAY (owner, 13 September 2026) -----------------------------------------
     Everything above this comment is what a phone gets with NO SCRIPTING: the wrapped row, every link
     on screen, nothing hidden. It stays because it is the state you cannot get out of without a button.

     With scripting (html.js-nav, set in the head so the open menu is never painted and then folded) the
     three rows of links become one 44 px control, and the mark, the principal's name and that control
     share a single band: about 100 px of chrome before the first heading, against about 290 px.

     What is deliberately NOT traded away to get it — both of these were decided against once already
     (R7, R8), and neither is reopened here:
       - the links keep 44 px of height, and gain the full width of the screen;
       - nothing becomes unreachable: the panel lists every section the sidebar lists, and the section
         names come back with it, which the scrolling row had to drop.
     And what is waiting is not hidden while the menu is shut — the counts add up onto the button. */
  .js-nav .side { flex-direction: row; flex-wrap: wrap; align-items: center; column-gap: 10px; padding-right: 12px; }
  .js-nav .logo { flex: none; padding: 6px 0 6px 12px; border-bottom: 0; }
  /* A PERCENTAGE MAX-WIDTH IS CIRCULAR ONCE THE MARK IS A FLEX ITEM SIZED TO ITS CONTENT. The phone rule
     above caps the mark at 60% of .logo, which was the full width of the screen while the bar was a column;
     in the row it is 60% of a box whose width the mark itself decides, and the browser settles that at 31 px
     — a 220x147 asset squeezed to 31x34 (probe_portal_mark.js, "aspect STRETCHED"). The cap is worth keeping
     for a tenant with a very wide mark, so it stays and stops referring to itself. */
  .js-nav .logo img { max-width: 40vw; }
  /* A TENANT WITH NO MARK GETS ITS NAME AS THE MARK (portal_logo's own fallback), and in this row that is a
     flex item sized to its own text: a long company name pushes the control onto a second line and gives the
     saving straight back. Capped and truncated — the principal is named in full beside it anyway. */
  .js-nav .logo-text { display: block; max-width: 40vw; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  /* the principal is still named, beside the mark instead of under it, and truncates rather than wraps
     the band into two lines */
  .js-nav .side-who { flex: 1 1 auto; min-width: 0; margin: 0; padding: 0; border-bottom: 0; font-size: 11.5px; line-height: 1.25; }
  .js-nav .side-who b { font-size: 13px; margin-top: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .js-nav .nav-toggle { display: inline-flex; align-items: center; gap: 7px; flex: none; min-height: 44px; padding: 0 12px;
                        font: inherit; font-size: 13.5px; font-weight: 600; color: var(--ink-2);
                        background: var(--surface); border: 1px solid var(--line); border-radius: 6px; cursor: pointer; }
  .js-nav .nav-toggle .ic-close { display: none; }
  .js-nav.nav-open .nav-toggle { background: var(--bg); color: var(--ink); }
  .js-nav.nav-open .nav-toggle .ic-menu { display: none; }
  .js-nav.nav-open .nav-toggle .ic-close { display: inline-block; }
  .js-nav .side nav { display: none; }
  .js-nav.nav-open .side nav { display: flex; flex: 1 0 100%; flex-direction: column; gap: 2px; padding: 6px 10px 10px;
                               border-top: 1px solid var(--line-2); max-height: calc(100vh - 150px); overflow-y: auto; }
  /* THE SECTION NAMES COME BACK. They are hidden on the scrolling row for width, but "Fleet & crew" over
     Fleet / Crew / Rotations / Expiry register is how a reader tells the vessels from the people without
     opening both; a full-width panel has the room the row never had. */
  .js-nav.nav-open .side-sec { display: block; padding: 10px 10px 2px; }
  .js-nav.nav-open .side-item { width: 100%; flex: none; min-height: 44px; padding: 10px 12px; justify-content: space-between; border-radius: 6px; }
  .js-nav.nav-open .side-item.on { background: var(--neutral-bg); color: var(--heading); font-weight: 600; box-shadow: inset 3px 0 0 var(--accent); }

  /* an odd last tile spans the row instead of leaving an empty box beside it */
  .stats { grid-template-columns: repeat(auto-fit, minmax(min(130px, 100%), 1fr)); margin-bottom: 14px; }
  .stat { padding: 12px 13px; }
  .stat .n { font-size: 21px; }
  .stats > .stat:last-child:nth-child(odd) { grid-column: 1 / -1; }

  /* TABLES BECOME LABELLED LISTS */
  .tbl-wrap { overflow-x: visible; }
  table.tbl, .tbl tbody, .tbl tfoot, .tbl tr, .tbl td { display: block; width: 100%; }
  .tbl thead { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip-path: inset(50%); border: 0; }
  .tbl tr { padding: 10px 0; border-bottom: 1px solid var(--line); }
  .tbl tr:last-child { border-bottom: 0; }
  .tbl td { border: 0; padding: 2px 0; display: grid; grid-template-columns: 8.5em minmax(0, 1fr); gap: 10px; align-items: baseline; }
  .tbl td::before { content: attr(data-label); color: var(--ink-3); font-size: 12.5px; }
  .tbl td[data-label=""]::before, .tbl td:not([data-label])::before { content: none; }
  .tbl td[data-label=""], .tbl td:not([data-label]) { display: block; }
  .tbl td[colspan]::before { content: none; }
  .tbl td[colspan] { display: block; }
  .tbl td.num, .tbl .num { text-align: left; }
  .tbl td:empty { display: none; }
  .tbl tr.group-head td { display: block; background: var(--bg); font-weight: 600; padding: 7px 10px; margin: 4px 0; border-radius: 4px; }
  .tbl tr.group-head td::before { content: none; }
  .person { align-items: flex-start; }
  .btn, .btn-sm { min-height: 38px; display: inline-flex; align-items: center; }

  /* facts and boards stack rather than squeeze */
  .facts { grid-template-columns: 1fr; gap: 8px 0; }
  .board { grid-template-columns: 1fr; }
  .av-lg { width: 52px; height: 52px; } .av-lg.av-init { font-size: 17px; }
}

/* Between a phone and a laptop: two stat columns and roomier cells, but the table stays a table. */
@media (min-width: 641px) and (max-width: 900px) {
  .main { padding: 20px 18px 44px; }
  .stats { grid-template-columns: repeat(auto-fit, minmax(min(160px, 100%), 1fr)); }
}

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

/* ---------------------------------------------------------------- print */
/* PSC inspections and owner reviews still run on paper: chrome off, content full-width. */
@media print {
  .side, .topbar, .foot, .btn, .btn-row, .btn-out, .filterbar, .skip { display: none !important; }
  .app { display: block; }
  body { background: #fff; font-size: 12px; }
  .main { padding: 0; max-width: none; }
  .card { border: 0; padding: 0 0 14px; }
  .tbl th, .tbl td { padding: 5px 8px; }
  tr { break-inside: avoid; }
  a { color: inherit; text-decoration: none; }
  .empty { display: none; }
}

/* ============================================================================================
   THE OVERVIEW BRIEFING AND THE TWO FLEET GRAPHICS (10 September 2026)
   docs/client-portal-design-recommendations.md. Everything here follows the house restraint rule:
   weight, spacing and thin rules carry the emphasis; the only colour with meaning is the danger
   red already reserved for overdue and expired. Each graphic states its numbers in words, so it
   is readable in greyscale, by a screen reader and on a printed page.
   ============================================================================================ */

/* --- a briefing row: who, why, what to do ------------------------------------------------- */
.brief + .brief { margin-top: 14px; }
.brief-none { color: var(--ink-3); margin: 0; }
.brief-list { list-style: none; margin: 0; padding: 0; }
.brief-list li { display: grid; grid-template-columns: minmax(0, 1.5fr) minmax(0, 1.4fr) auto;
                 gap: 12px; align-items: center; padding: 10px 0; border-top: 1px solid var(--line-2); }
.brief-list li:first-child { border-top: 0; }
.bl-who { display: flex; align-items: center; gap: 10px; min-width: 0; }
.bl-who .sub { display: block; color: var(--ink-3); font-size: 12.5px; }
/* tabular figures so the dates down this column line up on their digits rather than drifting with the
   width of a "1" against a "0" (R6 review, item 3: consistent date alignment) */
.bl-why { color: var(--ink-2); font-size: 13px; min-width: 0; font-variant-numeric: tabular-nums; }
.bl-do { justify-self: end; }

/* --- the fleet strip: four facts, not seven competing tiles -------------------------------- */
.fleet-strip { display: flex; flex-wrap: wrap; gap: 10px 22px; align-items: baseline;
               padding: 12px 16px; margin: 16px 0; background: var(--surface);
               border: 1px solid var(--line); border-radius: var(--r); }
.fleet-strip a { color: var(--ink-2); text-decoration: none; font-size: 13px; }
.fleet-strip a:hover { color: var(--heading); text-decoration: underline; }
.fleet-strip b { color: var(--heading); font-size: 18px; font-weight: 700; margin-right: 4px;
                 font-variant-numeric: tabular-nums; }

/* --- graphic 1: crew coverage against the declared complement ------------------------------
   A navy bar with an outlined remainder. The sentence beside it is the fact; the bar is an aid.
   Short of complement is the same danger red the rest of the portal reserves for a real problem. */
.cov { display: flex; align-items: center; gap: 10px; text-decoration: none; color: inherit; min-width: 0; }
.cov:hover .cov-txt { text-decoration: underline; }
.cov-bar { flex: 0 0 84px; height: 8px; border: 1px solid var(--line); border-radius: 999px;
           background: var(--bg); overflow: hidden; }
.cov-fill { display: block; height: 100%; background: var(--heading); }
/* width arrives as a pw-* class: the portal's CSP forbids style attributes */
.cov-fill.cov-short { background: var(--bad); }
.cov-txt { font-size: 13px; color: var(--ink-2); white-space: nowrap; }
/* A VESSEL WITH NO DECLARED COMPLEMENT STILL LINES UP. It has no bar to draw — drawing an empty track
   would read as "nobody aboard" — so an empty box of the bar's own width holds its place and the sentences
   down the column start on one line (R6 review, item 5: align them consistently). */
.cov-none { display: flex; align-items: center; gap: 10px; min-width: 0; }
.cov-none::before { content: ""; flex: 0 0 84px; }

/* --- the one icon set (portal_icon() in inc/layout.php) --------------------------------------
   ONE tone: currentColor, so an icon is the colour of the text it sits beside and adds no accent. It never
   carries meaning alone — the document's title is beside it in words and the icon is aria-hidden — so it
   costs a reader nothing if they cannot tell a rosette from a sheet of paper. */
.ic { flex: 0 0 auto; vertical-align: -2px; color: var(--ink-3); }
/* The icon and the title are ONE value: below 640px every cell becomes a two-column label/value grid, and
   a loose icon beside the title would have made it a third column and knocked the whole list out of
   alignment. Wrapping them keeps the icon on the phone, which is the screen the review measured. */
.doc-name .dn { display: flex; align-items: flex-start; gap: 8px; min-width: 0; }
.doc-name .dn > span { min-width: 0; }
.doc-name .ic { margin-top: 2px; }
.group-head .ic { color: var(--heading); }

/* --- graphic 2: the movements arranged for the next 60 days --------------------------------- */
.mv-vessel { font-size: 12.5px; font-weight: 600; color: var(--ink-3); margin: 12px 0 4px;
             padding-bottom: 4px; border-bottom: 1px solid var(--line-2); }
.mv-vessel:first-child { margin-top: 0; }
.mv-list { list-style: none; margin: 0; padding: 0; }
.mv-list li { display: grid; grid-template-columns: 92px minmax(0, 1fr); gap: 10px;
              padding: 6px 0; align-items: baseline; }
.mv-date { color: var(--ink-2); font-size: 12.5px; }
.mv-what { font-size: 13px; min-width: 0; }
.mv-what .sub { color: var(--ink-3); font-size: 12px; margin-left: 6px; }
/* a direction marker that is a SHAPE, not a colour: joining points in, signing off points out */
.mv-dir { display: inline-block; width: 0; height: 0; margin-right: 6px; vertical-align: middle;
          border-top: 4px solid transparent; border-bottom: 4px solid transparent; }
.mv-dir.on  { border-left: 6px solid var(--heading); }
.mv-dir.off { border-right: 6px solid var(--ink-3); }

/* --- identity: the principal named under the mark ------------------------------------------ */
/* WHOSE PORTAL, THEN WHAT IT IS. The two lines were nearly the same size, so "Client portal" and the
   company's name read as one grey label and the reader could not tell at a glance whose portal they were
   in (R6 review, item 2: client identity, page title and service context need to be distinguishable).
   The company is the identity and carries the weight; the label above it stays quiet. */
.side-who { padding: 2px 20px 14px; margin-top: -6px; border-bottom: 1px solid var(--line-2);
            color: var(--ink-3); font-size: 11.5px; line-height: 1.35; }
.side-who b { display: block; margin-top: 1px; color: var(--heading); font-size: 15px; font-weight: 700;
              letter-spacing: 0; }
.login-card .who { color: var(--ink-2); font-weight: 600; margin: 0 0 2px; }
/* The blanket `.logo img` rule that used to sit here is gone: being last in the file it beat the shell's
   own sizing and shrank the mark on every screen. The mark is sized in the shell block above, and the
   login card's own size is set with the rest of the login styles. */

@media (max-width: 720px) {
  /* the coverage sentence must WRAP once the table becomes labelled facts: a nowrap phrase in a
     minmax(0,1fr) column is exactly how a page starts scrolling sideways at 390px */
  .cov { flex-wrap: wrap; gap: 6px 10px; }
  .cov-txt { white-space: normal; }
  .cov-bar { flex-basis: 64px; }
  /* once the table is a list of labelled facts there is no column to line up with, and 84 px of empty
     box in front of the sentence is just a dent in a narrow screen */
  .cov-none::before { display: none; }
  .brief-list li { grid-template-columns: minmax(0, 1fr); gap: 6px; }
  .bl-do { justify-self: start; }
  .mv-list li { grid-template-columns: minmax(0, 1fr); gap: 2px; }
  .fleet-strip { gap: 8px 16px; }
  /* the company still named, in two lines, but without a band of its own */
  .side-who { padding: 0 12px 8px; margin-top: -4px; font-size: 11px; line-height: 1.25; }
  .side-who b { font-size: 13.5px; }
}

/* A SIDEBAR ITEM IS A CONTROL, AND A FINGER IS THE SAME SIZE ON A TABLET AS ON A PHONE.
   37px measured on a phone in landscape and on every tablet size (UI audit, 17 Sep 2026). The
   question is the pointer, not the width: a mouse is unaffected by this rule. */
@media (pointer: coarse) {
  .side-item { min-height: 44px; display: flex; align-items: center; }
  .btn, .btn-out { min-height: 44px; }
  /* The account control in the header is a 34px circle, and the links inside table cells - a vessel
     name, a seafarer's name, "All proposals" - are 19 to 20px of text with nothing around them to
     aim at. Padding rather than a height, so the rows keep their shape and only the hit area grows:
     the same answer the office uses for th a / .crew-link (UI audit, 17 Sep 2026). */
  .user, .user-init { min-height: 44px; }
  td a, th a, .side-who a, .card a:not(.btn):not(.btn-out) { padding-top: 11px; padding-bottom: 11px; }
}
