/* WCIP Phase 0 — business UI styles (track W-D).
 *
 * Hand-written and vendored on purpose. Tailwind's CDN build fetches a script from the
 * public internet at page load, which fails on the internal VM this is deployed to
 * (PRD §8), and a real Tailwind build needs npm, which CLAUDE.md rules out. web/README
 * allows "a vendored CSS file" — this is it. No build step, no external request.
 *
 * The one visual rule that is a requirement rather than taste: nothing here may make a
 * broker-reported value look verified (red line 4). Hence `.rep` on every reported
 * value, `.stale` greying whole rows, and `.unnorm` marking un-normalised ports.
 */

:root {
  --ink: #16222e;
  --ink-soft: #5b6b7b;
  --ink-faint: #93a1af;
  --line: #dfe5ec;
  --line-soft: #eef2f6;
  --bg: #ffffff;
  --bg-soft: #f7f9fb;
  --accent: #1d4ed8;
  --accent-soft: #eaf0ff;
  --warn: #b45309;
  --warn-soft: #fef3c7;
  --danger: #b42318;
  --danger-soft: #fee4e2;
  --ok: #067647;
  --ok-soft: #e7f6ee;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
    "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font: 14px/1.45 var(--sans);
  color: var(--ink);
  background: var(--bg-soft);
}

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ---------------------------------------------------------------- chrome */
.topbar {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  padding: .55rem 1rem;
  background: var(--ink);
  color: #fff;
  position: sticky;
  top: 0;
  z-index: 20;
}
.topbar .brand { font-weight: 650; letter-spacing: .02em; }
.topbar nav { display: flex; gap: .9rem; }
.topbar a { color: #cfe0ff; }
.topbar a.on { color: #fff; text-decoration: underline; }
.topbar .spacer { flex: 1; }
.topbar .who { color: #a9bdd4; font-size: 12.5px; }
.topbar form { display: inline; }
.topbar button.link {
  background: none; border: 0; color: #cfe0ff; cursor: pointer; font: inherit; padding: 0;
}

.reported-banner {
  background: var(--warn-soft);
  color: var(--warn);
  padding: .4rem 1rem;
  font-size: 12.5px;
  border-bottom: 1px solid #f0dcae;
}

main { padding: 1rem; max-width: 100%; }
h1 { font-size: 1.15rem; margin: 0 0 .2rem; }
h2 { font-size: .95rem; margin: 1.2rem 0 .4rem; }
.sub { color: var(--ink-soft); margin: 0 0 .9rem; font-size: 12.5px; }

/* ---------------------------------------------------------------- panels */
.panel {
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: .75rem .85rem;
  margin-bottom: .8rem;
}
.panel > h3 {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--ink-faint);
  margin: 0 0 .5rem;
}

/* ---------------------------------------------------------------- filters */
details.filter-panel > summary {
  cursor: pointer;
  font-size: 11.5px;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--ink-faint);
  margin-bottom: .5rem;
  display: flex;
  align-items: center;
  gap: .4rem;
}
details.filter-panel > summary .chip { text-transform: none; letter-spacing: 0; }
details.filter-panel:not([open]) > summary { margin-bottom: 0; }

.filters { display: grid; grid-template-columns: repeat(auto-fit, minmax(215px, 1fr)); gap: .7rem .9rem; }
.field { display: flex; flex-direction: column; gap: .25rem; min-width: 0; }
.field > label, .field > .label {
  font-size: 11.5px;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--ink-faint);
}
.field input[type="text"],
.field input[type="number"],
.field input[type="date"],
.field input[type="search"],
.field select {
  font: inherit;
  padding: .3rem .4rem;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: #fff;
  color: var(--ink);
  min-width: 0;
}
/* Inputs fill their grid column instead of using the browser's intrinsic `size`
 * width, which otherwise spills a long placeholder past the panel edge. */
.field > input, .field > select { width: 100%; }
.field .row { display: flex; gap: .35rem; align-items: center; }
.field .row input { flex: 1; width: auto; }
.hint { font-size: 11px; color: var(--ink-faint); }

/* Chip groups scroll inside themselves. With 20 brokers (hundreds, later) an
 * unbounded list pushes the actual tonnage table below the fold, and the whole point of
 * this screen is finding a ship in 30 seconds. */
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: .25rem;
  max-height: 5.6rem;
  overflow-y: auto;
  align-content: flex-start;
}
.chips::-webkit-scrollbar { width: 6px; }
.chips::-webkit-scrollbar-thumb { background: var(--line); border-radius: 3px; }
.chip {
  display: inline-block;
  padding: .12rem .45rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: #fff;
  color: var(--ink-soft);
  font-size: 11.5px;
  cursor: pointer;
  white-space: nowrap;
}
.chip:hover { border-color: var(--accent); color: var(--accent); text-decoration: none; }
.chip.on { background: var(--accent-soft); border-color: #b9ccff; color: var(--accent); font-weight: 600; }
.chip .n { color: var(--ink-faint); font-weight: 400; }

.filter-actions { display: flex; align-items: center; gap: .6rem; margin-top: .7rem; flex-wrap: wrap; }
button, .btn {
  font: inherit;
  padding: .32rem .7rem;
  border: 1px solid var(--line);
  border-radius: 4px;
  background: #fff;
  color: var(--ink);
  cursor: pointer;
}
button.primary, .btn.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
button:hover, .btn:hover { border-color: var(--accent); text-decoration: none; }
button.primary:hover { background: #1a45c0; }
.muted { color: var(--ink-soft); }
.count { font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------------- table */
.table-wrap { overflow-x: auto; background: var(--bg); border: 1px solid var(--line); border-radius: 6px; }
table.grid { border-collapse: collapse; width: 100%; font-size: 13px; }
table.grid th, table.grid td { padding: .34rem .5rem; text-align: left; vertical-align: top; white-space: nowrap; }
table.grid thead th {
  position: sticky;
  top: 0;
  background: var(--bg-soft);
  border-bottom: 1px solid var(--line);
  font-size: 11.5px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--ink-soft);
  font-weight: 600;
  z-index: 5;
}
table.grid thead th a { color: inherit; }
table.grid thead th .dir { color: var(--accent); }
table.grid tbody tr { border-bottom: 1px solid var(--line-soft); }
table.grid tbody tr:hover { background: #fbfcfe; }
table.grid td.num { text-align: right; font-variant-numeric: tabular-nums; font-family: var(--mono); font-size: 12.5px; }
/* Vessel names wrap, but only so far. 31% of parsed names carry descriptive text lifted
   from the broker's subject line (SYNC XD-007 ②); the longest measured is 135 characters
   and rendered a 163px row against ~25px for a normal one, so a handful of them push the
   rest of the list off screen — and scanning the list is the entire product. Clamped to
   two lines with an ellipsis so the truncation is visible; the full name is in the link's
   title and in the drawer. */
table.grid td.vessel { white-space: normal; min-width: 12rem; max-width: 22rem; }
table.grid td.vessel > a {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden; overflow-wrap: anywhere;
}
/* Same reason, smaller blast radius: raw port text runs to 45 characters, e.g.
   "Dar es Salaam Port SPOT , ALT ETA Durban port". min-width matters as much as max: with
   one nowrap column hogging the table, Open Port was squeezed to 88px and wrapped onto
   four lines, which is what actually set the row height. */
table.grid td.wrap { min-width: 9rem; max-width: 18rem; overflow-wrap: anywhere; }
/* Gear is the worst offender on real data — 157 characters of crane description. Two
   lines, rest on hover. */
table.grid td.gear {
  max-width: 16rem;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
table.grid td.wrap { white-space: normal; }

tr.stale > td { color: var(--ink-faint); background: #fcfcfd; }
tr.stale td.vessel a { color: #6f89b5; }

.hint.keys { margin: .55rem 0 .2rem; }
kbd {
  font-family: var(--mono); font-size: 11px; padding: 0 .28rem;
  border: 1px solid var(--line); border-bottom-width: 2px; border-radius: 3px;
  background: #fff; color: var(--ink-soft);
}

/* Keyboard cursor (j/k). A left bar plus a tint, not an outline: the row is focusable, so
   the browser draws its own focus ring on top and two rings read as two selections.
   `:focus-visible` is left alone deliberately — the tint has to stay after focus moves
   into the drawer, otherwise Esc has nowhere visible to come back to. */
table.grid tbody tr.cursor > td { background: #eef4ff; }
table.grid tbody tr.cursor > td:first-child { box-shadow: inset 3px 0 0 var(--accent); }
table.grid tbody tr.cursor:focus { outline: none; }
table.grid tbody tr.stale.cursor > td { background: #eef2f8; }

.drawer-row > td { background: #fbfcfe; padding: 0; }
.drawer { padding: .7rem .8rem; border-top: 1px dashed var(--line); }

/* ---------------------------------------------------------------- badges */
/* "报称" — every broker-stated value carries one. Small, but never absent: Phase 0 has
 * no verified data and the UI is not allowed to imply otherwise (red line 4). */
.rep {
  font-size: 9px;
  line-height: 1;
  color: var(--warn);
  background: var(--warn-soft);
  border-radius: 2px;
  padding: .06rem .16rem;
  margin-left: .18rem;
  vertical-align: super;
  font-weight: 600;
  letter-spacing: .02em;
  cursor: help;
  white-space: nowrap;
}
th .rep { vertical-align: middle; }

.tag {
  display: inline-block;
  font-size: 10.5px;
  padding: .05rem .3rem;
  border-radius: 3px;
  border: 1px solid transparent;
  white-space: nowrap;
}
.tag.unnorm { background: var(--danger-soft); color: var(--danger); border-color: #f7c9c5; }
.tag.stale { background: #eef1f4; color: var(--ink-soft); border-color: #dbe1e8; }
.tag.fresh { background: var(--ok-soft); color: var(--ok); border-color: #bfe6d1; }
.tag.manual { background: var(--accent-soft); color: var(--accent); border-color: #c9d8ff; }
.tag.corrected { background: #f3e8ff; color: #6b21a8; border-color: #e0cffc; }
.tag.disagree { background: var(--warn-soft); color: var(--warn); border-color: #f0dcae; }
/* D-008: SPOT is a positive statement ("open now"), so it reads as good news;
 * an unreadable date is a gap, so it reads as missing data — not as a value. */
.tag.spot { background: var(--ok-soft); color: var(--ok); border-color: #bfe6d1; font-weight: 600; }
.tag.unknown-date { background: #eef1f4; color: var(--ink-soft); border-color: #dbe1e8; }
.tag.superseded { background: #eef1f4; color: var(--ink-faint); border-color: #dbe1e8; }
.tag.sources { background: #fff; color: var(--ink-soft); border-color: var(--line); font-family: var(--mono); }

/* ---------------------------------------------------------------- drawer */
.timeline { display: flex; flex-direction: column; gap: .55rem; }
.report {
  border: 1px solid var(--line);
  border-left: 3px solid var(--accent);
  border-radius: 4px;
  background: #fff;
  padding: .5rem .6rem;
}
.report.superseded { border-left-color: var(--ink-faint); background: #fbfbfc; opacity: .85; }
.report.stale { border-left-color: #cbd5e1; }
.report header { display: flex; gap: .5rem; align-items: baseline; flex-wrap: wrap; margin-bottom: .3rem; }
.report header .src { font-weight: 600; }
.report header .when { color: var(--ink-soft); font-size: 12px; }
.report dl { display: grid; grid-template-columns: max-content 1fr; gap: .12rem .55rem; margin: 0; }
.report dt { color: var(--ink-faint); font-size: 11.5px; }
.report dd { margin: 0; }
.report .trace { margin-top: .4rem; }
.report .trace summary { cursor: pointer; color: var(--ink-soft); font-size: 12px; }
.report pre {
  background: var(--bg-soft);
  border: 1px solid var(--line-soft);
  border-radius: 4px;
  padding: .4rem .5rem;
  font: 11.5px/1.4 var(--mono);
  overflow-x: auto;
  margin: .3rem 0 0;
}

.correct-form { display: flex; gap: .3rem; align-items: center; margin-top: .45rem; flex-wrap: wrap; }
.correct-form select, .correct-form input { font: inherit; padding: .22rem .3rem; border: 1px solid var(--line); border-radius: 4px; }
.correct-form input[type="text"] { min-width: 11rem; }

/* ---------------------------------------------------------------- messages */
.notice, .error, .ok {
  padding: .45rem .6rem;
  border-radius: 4px;
  font-size: 12.5px;
  margin: 0 0 .6rem;
}
.notice { background: var(--warn-soft); color: var(--warn); }
.error { background: var(--danger-soft); color: var(--danger); }
.ok { background: var(--ok-soft); color: var(--ok); }
.field .error { margin: .15rem 0 0; padding: .15rem .3rem; }

.empty { padding: 1.4rem 1rem; text-align: center; }
.empty h3 { margin: 0 0 .3rem; font-size: 1rem; }
.relax { list-style: none; padding: 0; margin: .7rem auto 0; max-width: 34rem; text-align: left; }
.relax li { border: 1px solid var(--line); border-radius: 4px; padding: .4rem .55rem; margin-bottom: .35rem; background: #fff; }
.relax .n { font-weight: 700; color: var(--accent); font-variant-numeric: tabular-nums; }

.pager { display: flex; align-items: center; gap: .7rem; padding: .5rem .1rem; font-size: 12.5px; color: var(--ink-soft); }
.pager .spacer { flex: 1; }
.perf { font-family: var(--mono); font-size: 11px; color: var(--ink-faint); }

/* ---------------------------------------------------------------- login */
.login-wrap { max-width: 20rem; margin: 4.5rem auto; }
.login-wrap .panel { padding: 1.1rem; }
.login-wrap .field { margin-bottom: .7rem; }
.login-wrap input { width: 100%; }
.login-wrap button { width: 100%; }

/* ---------------------------------------------------------------- archive */
.hit { border-bottom: 1px solid var(--line-soft); padding: .55rem .1rem; }
.hit:last-child { border-bottom: 0; }
.hit .meta { color: var(--ink-soft); font-size: 12px; display: flex; gap: .5rem; flex-wrap: wrap; align-items: baseline; }
.hit .subject { font-weight: 600; }
.hit .snip { color: var(--ink-soft); font-size: 12.5px; margin-top: .2rem; white-space: pre-wrap; overflow-wrap: anywhere; }
mark { background: #fff3bf; color: inherit; padding: 0 .05rem; }
.email-body {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  background: #fff;
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: .8rem;
  font: 13px/1.5 var(--mono);
}

/* ---------------------------------------------------------------- misc */
.htmx-request .htmx-indicator { visibility: visible; }
.htmx-indicator { visibility: hidden; color: var(--accent); font-size: 12px; }
/* Saved filter combinations (D-009). Every control is its own POST form, so the layout
   has to make a row of forms read as one row — hence the flex/inline resets. */
details.saved-box { margin-top: .6rem; }
details.saved-box > summary { cursor: pointer; font-size: 12.5px; color: var(--ink-soft); }
.saved { margin-top: .5rem; }
.saved form { display: inline; margin: 0; }
.saved-save { display: flex; flex-wrap: wrap; gap: .35rem; align-items: center; }
.saved-save input[type=text] { min-width: 14rem; }
.saved-head {
  font-size: 11px; text-transform: uppercase; letter-spacing: .04em;
  color: var(--ink-faint); margin: .7rem 0 .3rem; font-weight: 600;
}
.saved-list { list-style: none; display: flex; flex-direction: column; gap: .3rem; }
.saved-item { display: flex; flex-wrap: wrap; gap: .4rem; align-items: center; }
.saved-item .saved-apply button { cursor: pointer; }
.saved-meta { display: inline-flex; gap: .35rem; align-items: center; }
/* The edit controls are secondary to "apply": visible, but not competing with it. */
.saved-ops { display: inline-flex; flex-wrap: wrap; gap: .25rem; align-items: center; }
.saved-ops input[type=text] { width: 9rem; font-size: 11.5px; padding: .12rem .3rem; }
.saved-ops button {
  font-size: 11px; padding: .12rem .4rem; color: var(--ink-soft);
  border: 1px solid var(--line); background: #fff; border-radius: 3px; cursor: pointer;
}
.saved-ops button:hover { border-color: var(--accent); color: var(--accent); }
.saved-ops button.x { border: 0; background: none; color: var(--ink-faint); padding: 0 .25rem; }
.saved-item.shared .saved-apply button { border-style: dashed; }
details.saved-edit { display: inline; }
details.saved-edit > summary {
  display: inline; cursor: pointer; font-size: 11px; color: var(--ink-faint);
  list-style: none;
}
details.saved-edit > summary::-webkit-details-marker { display: none; }
details.saved-edit > summary:hover { color: var(--accent); }
details.saved-edit[open] > summary { color: var(--accent); }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* A-test feedback line. Quiet by design — it should be findable, not competing with the
   table for attention. */
footer.foot {
  margin: 1.4rem 1rem .8rem; padding-top: .5rem;
  border-top: 1px solid var(--line-soft);
  font-size: 11.5px; color: var(--ink-faint);
  display: flex; gap: .5rem; align-items: center; flex-wrap: wrap;
}
footer.foot .sep { color: var(--line); }

/* --- archive tabs + the unparsed scoreboard (未解析邮件) -------------------- */
.tabs { display: flex; gap: 0; border-bottom: 1px solid var(--rule); margin: 0 0 1rem; }
.tabs .tab {
  padding: .5rem .9rem; text-decoration: none; color: var(--ink-faint);
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.tabs .tab:hover { color: var(--ink); }
.tabs .tab.on { color: var(--ink); border-bottom-color: var(--accent); font-weight: 600; }

h2.section { font-size: 1rem; margin: 0 0 .5rem; color: var(--ink-faint); font-weight: 600; }

.scoreboard { display: flex; gap: 1.5rem; flex-wrap: wrap; margin: 0 0 .75rem; }
.scoreboard .score {
  border: 1px solid var(--rule); border-radius: 6px; padding: .6rem .9rem;
  display: flex; flex-direction: column; gap: .1rem; min-width: 12rem;
}
.scoreboard .score .n { font-size: 1.5rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.scoreboard .score.big .n { color: var(--accent); }
.scoreboard .score .label { font-size: .85rem; }
.scoreboard .score .hint { font-size: .75rem; color: var(--ink-faint); }

/* One colour per reason, so the eye can group a long list without reading every badge.
   The two that mean "the parser tried and did not get there" are the warm ones. */
.tag.reason-unclassified   { background: var(--chip-bg); }
.tag.reason-not_for_parsing{ background: var(--chip-bg); color: var(--ink-faint); }
.tag.reason-template_miss  { background: var(--warn-bg, #fdf0e3); }
.tag.reason-partial        { background: var(--warn-bg, #fdf0e3); }
.tag.reason-pending        { background: var(--chip-bg); color: var(--ink-faint); }

table.unparsed .subject { max-width: 38rem; }
table.unparsed .sender  { max-width: 16rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Why a row matched the global search — only rendered when the cause is not one of the
   columns already on screen (remarks / cargo / owner / IMO). Muted on purpose: it is an
   explanation, not data about the ship. */
.why-matched {
  display: inline-block; margin-left: .4rem; padding: 0 .3rem;
  font-size: .72rem; color: var(--ink-faint);
  border: 1px dashed var(--rule); border-radius: 3px; cursor: help; white-space: nowrap;
}
