@font-face {
  font-family: 'Montserrat';
  src: url('fonts/montserrat-var.woff2') format('woff2-variations');
  font-weight: 400 700;
  font-display: swap;
}

:root {
  --bg:           #0a1f26;
  --panel:        #0e2b35;
  --panel-border: #183540;
  --ink:          #dce8ed;
  --ink-dim:      #7aaabb;
  --accent:       #0e4d5c;
  --accent-mid:   #7aaabb;
  --accent-hi:    #a8ccd6;
  --line:         #13303c;
  --content-bg:   #f0f4f6;
  --content-ink:  #1a3a45;
  --content-dim:  #5a7a88;
  --panel-light-border: #d4e2e8;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

html, body {
  height: 100%;
}

body {
  background: var(--bg);
  color: var(--ink);
  font-family: 'Montserrat', Helvetica, Arial, Lucida, sans-serif;
  font-size: 15px;
  line-height: 1.5;
  display: flex;
  flex-direction: column;
}

/* ---------- Top bar ---------- */
header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.75rem 1rem;
  border-bottom: 1px solid var(--panel-light-border);
  background: #ffffff;
  flex-shrink: 0;
}

.brand-group {
  display: flex;
  align-items: center;
}

.brand {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.brand-logo {
  display: block;
}

.brand-mark {
  font-weight: 500;
  letter-spacing: 0.02em;
  font-size: 0.85rem;
  color: var(--content-dim);
  padding-left: 1rem;
  border-left: 1px solid var(--panel-light-border);
}

.status {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.72rem;
  font-weight: 500;
  color: var(--content-dim);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.status-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent-hi);
  box-shadow: 0 0 0 3px rgba(122, 170, 187, 0.18);
  flex-shrink: 0;
}

/* ---------- Layout ---------- */
.frame {
  display: flex;
  flex: 1;
  min-height: 0;
}

/* ---------- Side menu ---------- */
nav {
  width: 200px;
  flex-shrink: 0;
  border-right: 1px solid var(--line);
  background: var(--panel);
  padding: 1rem 0;
  overflow-y: auto;
}

.nav-label {
  font-size: 0.65rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--ink-dim);
  padding: 0 1rem;
  margin-bottom: 0.4rem;
}

.nav-label-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  cursor: pointer;
  user-select: none;
}

.nav-label-toggle:hover {
  color: var(--ink);
}

.nav-label-toggle .nav-chevron {
  font-size: 0.6rem;
  transition: transform 0.15s;
}

.nav-label-toggle.open .nav-chevron {
  transform: rotate(90deg);
}

.nav-list {
  list-style: none;
}

.nav-list a {
  display: block;
  padding: 0.5rem 1rem;
  color: var(--ink-dim);
  text-decoration: none;
  font-size: 0.82rem;
  font-weight: 500;
  letter-spacing: 0.02em;
  border-left: 2px solid transparent;
  transition: color 0.1s, background 0.1s;
}

.nav-list a:hover {
  color: var(--ink);
  background: rgba(122, 170, 187, 0.06);
}

.nav-list a.active {
  color: var(--ink);
  border-left-color: var(--accent-mid);
  background: rgba(122, 170, 187, 0.1);
}

.nav-divider {
  height: 1px;
  background: var(--line);
  margin: 0.75rem 1rem;
}

/* ---------- Content ---------- */
main {
  flex: 1;
  padding: 1.25rem 1.5rem;
  overflow-y: auto;
  min-width: 0;
  background: var(--content-bg);
}

main h1 {
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  margin-bottom: 0.2rem;
  color: var(--content-ink);
}

/* h2-h6 have no explicit rule of their own, so without this they inherit
   body's --ink (a light color meant for the dark sidebar/nav) and render
   almost invisible against main's light --content-bg — e.g. the "Cargo
   Tanks" / "Deck / Slop Tanks" h5s on the Vessel HF pages. */
main h2, main h3, main h4, main h5, main h6 {
  color: var(--content-ink);
}

main .page-sub {
  font-size: 0.8rem;
  font-weight: 400;
  color: var(--content-dim);
  margin-bottom: 1.25rem;
}

.panel {
  background: #ffffff;
  border: 1px solid var(--panel-light-border);
  border-radius: 3px;
  padding: 1rem;
}

.placeholder-text {
  font-size: 0.85rem;
  color: var(--content-dim);
}

.dt-hf-list {
  max-height: 320px;
  overflow-y: auto;
  border: 1px solid var(--panel-light-border);
  border-radius: 3px;
  padding: 0.5rem 0.75rem;
}

.dt-hf-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.25rem 0;
  font-size: 0.85rem;
  font-weight: 400;
  cursor: pointer;
}

/* ---------- Content: links, tables, forms, buttons ---------- */
main a {
  color: var(--accent);
  text-decoration: none;
}
main a:hover { text-decoration: underline; }

/* Tables */
main table {
  width: 100%;
  border-collapse: collapse;
  background: #ffffff;
  font-size: 0.84rem;
  margin-bottom: 1rem;
}
main th, main td {
  padding: 0.45rem 0.6rem;
  border: 1px solid var(--panel-light-border);
  color: var(--content-ink);
  text-align: left;
  vertical-align: middle;
}
#plans-table td { vertical-align: top; }
/* Program bullet lists — see the .program-list rules further down; the
   dashboard and the plan list render the same markup. */
main thead th {
  background: var(--content-bg);
  font-weight: 700;
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  vertical-align: bottom;
}
main tbody tr:hover td { background: #f7fbfc; }

/* Form inputs */
main input[type="text"],
main input[type="email"],
main input[type="password"],
main input[type="date"],
main input[type="datetime-local"],
main input[type="time"],
main input[type="number"] {
  padding: 0.3rem 0.5rem;
  border: 1px solid var(--panel-light-border);
  border-radius: 3px;
  font-size: 0.84rem;
  font-family: inherit;
  color: var(--content-ink);
  background: #fff;
}
main input:focus {
  outline: none;
  border-color: var(--accent-mid);
}
main label { font-size: 0.78rem; color: var(--content-dim); display: block; margin-bottom: 0.2rem; }

.btn-icon { padding: 0.18rem 0.4rem; border: none; background: none; cursor: pointer; font-size: 0.9rem; border-radius: 3px; }
.btn-icon:hover { background: var(--content-bg); }

/* Print/PDF buttons while a server-rendered PDF is being prepared — see
   runPdfDownload() in plan_print.js. */
.btn-spinning { cursor: default; opacity: 0.8; }
.btn-spinner {
  display: inline-block;
  width: 0.8em;
  height: 0.8em;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  vertical-align: -0.15em;
  margin-right: 0.3em;
  animation: btn-spin 0.7s linear infinite;
}
@keyframes btn-spin {
  to { transform: rotate(360deg); }
}

/* Buttons — .btn is global so modals (outside <main>) inherit the same style */
main button, .btn {
  padding: 0.28rem 0.7rem;
  border-radius: 3px;
  font-size: 0.8rem;
  font-family: inherit;
  cursor: pointer;
  border: 1px solid var(--panel-light-border);
  background: #fff;
  color: var(--content-ink);
  text-decoration: none;
  display: inline-block;
}
main button:hover, .btn:hover { background: var(--content-bg); }
main button:disabled, .btn:disabled { opacity: .4; cursor: not-allowed; pointer-events: none; }
/* Keyboard focus ring — offset outline + a contrasting inset ring so it
   reads clearly on both light buttons and dark (.btn-primary) buttons. */
main button:focus-visible, .btn:focus-visible {
  outline: 2px solid var(--accent-mid);
  outline-offset: 2px;
  box-shadow: 0 0 0 2px #fff;
}
.live-elapsed { background: #f59e0b; color: #fff; padding: .28rem .45rem; border-radius: 3px; font-size: .8rem; font-weight: 600; white-space: nowrap; display: inline-block; line-height: 1; }

.btn-primary, main button[type="submit"] {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
}
.btn-primary:hover, main button[type="submit"]:hover {
  background: var(--accent-mid);
  border-color: var(--accent-mid);
  color: #fff;
}
.btn-danger { border-color: #e57373; color: #c62828; }
.btn-danger:hover { background: #fef2f2; }

.info-icon {
  cursor: help;
  color: var(--content-dim);
  font-size: 1.1rem;
}
.info-icon:hover { color: var(--accent-mid); }

/* Fixed-position tooltip for .info-icon (native title tooltips get clipped
   by the reactable table's overflow-y:auto). Positioned via JS from the
   icon's bounding rect on mouseenter/mouseleave. */
.hf-tooltip {
  display: none;
  position: fixed;
  transform: translate(-50%, calc(-100% - 8px));
  max-width: 280px;
  padding: 0.4rem 0.6rem;
  background: var(--content-ink, #222);
  color: #fff;
  font-size: 0.8rem;
  line-height: 1.4;
  white-space: pre-wrap;
  border-radius: 4px;
  z-index: 1000;
  pointer-events: none;
}
.hf-tooltip.visible { display: block; }

/* Flex layout helpers (replaces Bootstrap d-flex / gap-2 / align-items-end etc.) */
main .row-form input[type="text"],
.dash-action-row input[type="text"] {
  height: 2rem;
  box-sizing: border-box;
  padding: 0 0.6rem;
}
main .row-form .btn,
.dash-action-row .btn {
  height: 2rem;
  box-sizing: border-box;
  display: inline-flex;
  align-items: center;
}
.btn-glyph { font-size: 1.15rem; line-height: 1; padding-left: 0.55rem; padding-right: 0.55rem; }
main .row-form {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  align-items: center;
  margin-bottom: 1rem;
}
main .btn-group {
  display: inline-flex;
  gap: 0.3rem;
}

/* hr */
main hr {
  border: none;
  border-top: 1px solid var(--panel-light-border);
  margin: 1.25rem 0;
}

/* ---------- select inputs ---------- */
main select {
  padding: 0.3rem 0.5rem;
  border: 1px solid var(--panel-light-border);
  border-radius: 3px;
  font-size: 0.84rem;
  font-family: inherit;
  color: var(--content-ink);
  background: #fff;
}
main select:focus { outline: none; border-color: var(--accent-mid); }

/* ---------- Modal dialog ---------- */
.modal-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(10, 31, 38, 0.55);
  z-index: 100;
  align-items: center;
  justify-content: center;
}
.modal-backdrop.open { display: flex; }

.modal {
  background: #ffffff;
  border: 1px solid var(--panel-light-border);
  border-radius: 4px;
  padding: 1.5rem;
  min-width: 360px;
  max-width: 480px;
  width: 100%;
  box-shadow: 0 4px 24px rgba(0,0,0,0.18);
}
.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1.25rem;
}
.modal-title {
  font-size: 0.9rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--content-ink);
}
.modal-close {
  background: none;
  border: none;
  font-size: 1.2rem;
  cursor: pointer;
  color: var(--content-dim);
  padding: 0 0.25rem;
  line-height: 1;
}
.modal-close:hover { color: var(--content-ink); }

.modal .form-row {
  margin-bottom: 0.75rem;
}
.modal label {
  display: block;
  font-size: 0.78rem;
  color: var(--content-dim);
  margin-bottom: 0.2rem;
}
.modal input[type="email"],
.modal input[type="password"],
.modal input[type="text"],
.modal select {
  display: block;
  width: 100%;
  padding: 0.38rem 0.55rem;
  border: 1px solid var(--panel-light-border);
  border-radius: 3px;
  font-size: 0.88rem;
  font-family: inherit;
  color: var(--content-ink);
  background: #fff;
  box-sizing: border-box;
}
.modal input:focus, .modal select:focus { outline: none; border-color: var(--accent-mid); }
.modal input[readonly] { background: var(--content-bg); color: var(--content-dim); }

.modal-lg { max-width: 620px; }
/* Tall add/edit forms (lookup pages with a checkbox group) scroll inside the viewport. */
.modal.modal-scroll { max-height: calc(100vh - 2rem); overflow-y: auto; }
.modal .group-heading { font-weight: 700; color: var(--content-ink); }
.modal .check-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.3rem 0.75rem; }
.modal .check-item { display: flex; align-items: center; gap: 0.3rem; margin: 0; cursor: pointer; }

.modal-duration-detail {
  max-width: 1000px;
  max-height: 90vh;
  display: flex;
  flex-direction: column;
  /* padding moves to header/body/footer individually so the header and
     footer stay put while only .modal-body scrolls, same pattern as
     .modal-feedback */
  padding: 0;
}
.modal-duration-detail .modal-header {
  padding: 1.5rem 1.5rem 0;
  margin-bottom: 1.25rem;
  flex-shrink: 0;
}
.modal-duration-detail .modal-body {
  padding: 0 1.5rem;
  overflow-y: auto;
  flex: 1 1 auto;
  min-height: 0;
}
.modal-duration-detail .modal-footer {
  padding: 1.25rem 1.5rem 1.5rem;
  margin-top: 0;
  flex-shrink: 0;
}

/* ---------- Feedback widget ---------- */
.feedback-fab {
  position: fixed;
  right: 1.25rem;
  bottom: 1.25rem;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  border: none;
  background: var(--accent);
  color: var(--accent-hi);
  font-size: 1.3rem;
  cursor: pointer;
  box-shadow: 0 2px 10px rgba(0,0,0,0.3);
  z-index: 90;
}
.feedback-fab:hover { background: var(--accent-mid); color: var(--content-bg); }

.modal-feedback {
  max-width: 720px;
  max-height: 90vh;
  display: flex;
  flex-direction: column;
  /* padding moves to header/body/footer individually so the header and
     footer can stay put while only .modal-body scrolls */
  padding: 0;
}
.modal-feedback .modal-header {
  padding: 1.5rem 1.5rem 0;
  margin-bottom: 1.25rem;
  flex-shrink: 0;
}
.modal-feedback .modal-body {
  padding: 0 1.5rem;
  overflow-y: auto;
  flex: 1 1 auto;
  min-height: 0;
}
.modal-feedback .modal-footer {
  padding: 1.25rem 1.5rem 1.5rem;
  margin-top: 0;
  flex-shrink: 0;
}

.modal textarea {
  display: block;
  width: 100%;
  padding: 0.38rem 0.55rem;
  border: 1px solid var(--panel-light-border);
  border-radius: 3px;
  font-size: 0.88rem;
  font-family: inherit;
  color: var(--content-ink);
  background: #fff;
  resize: vertical;
  box-sizing: border-box;
}
.modal textarea:focus { outline: none; border-color: var(--accent-mid); }

.feedback-screenshot-preview {
  display: block;
  max-width: 100%;
  margin-top: 0.5rem;
  border: 1px solid var(--panel-light-border);
  border-radius: 3px;
}
.feedback-status {
  font-size: 0.78rem;
  color: var(--content-dim);
  margin-top: 0.5rem;
  min-height: 1.1em;
}

.feedback-case-banner {
  background: var(--panel-light-bg, #f5f5f0);
  border: 1px solid var(--panel-light-border);
  border-radius: 4px;
  padding: 0.6rem 0.75rem;
  margin-bottom: 0.9rem;
}
.feedback-case-banner p {
  margin: 0 0 0.5rem 0;
  font-size: 0.85rem;
  color: var(--content-ink);
}
.feedback-case-banner select {
  display: block;
  width: 100%;
  padding: 0.35rem 0.5rem;
  margin-bottom: 0.6rem;
  border: 1px solid var(--panel-light-border);
  border-radius: 3px;
  font-size: 0.85rem;
  background: #fff;
  color: var(--content-ink);
}

.feedback-screenshot-thumbs {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0.6rem;
  margin-top: 0.4rem;
}
.feedback-screenshot-thumbs figure {
  margin: 0;
  text-align: center;
}
.feedback-screenshot-thumbs img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  border: 1px solid var(--panel-light-border);
  border-radius: 3px;
}
.feedback-screenshot-thumbs figcaption {
  font-size: 0.72rem;
  color: var(--content-dim);
  margin-top: 0.2rem;
}

.feedback-attachment-list {
  list-style: none;
  margin: 0.3rem 0 0;
  padding: 0;
  font-size: 0.82rem;
  color: var(--content-ink);
}
.feedback-attachment-list li {
  padding: 0.2rem 0;
  border-bottom: 1px solid var(--panel-light-border);
}
.feedback-attachment-list li:last-child { border-bottom: none; }

.modal-footer {
  display: flex;
  gap: 0.5rem;
  justify-content: flex-end;
  margin-top: 1.25rem;
}

/* Explanatory note under a form control — e.g. what happens to the password
   when a user is created or reset. */
.form-hint {
  margin: 0.3rem 0 0;
  font-size: 0.8rem;
  line-height: 1.45;
  color: var(--content-dim);
}

/* ---------- Reactable table overrides ---------- */
.rt-th  { color: var(--content-ink) !important; font-weight: 700 !important; }
.rt-td  { color: var(--content-ink) !important; }
.rt-thead {
  background: var(--content-bg) !important;
  position: sticky !important;
  top: 0 !important;
  z-index: 2 !important;
}

/* Strip the white background. Constrain height so the table scrolls on its own
   and the sticky header works within its own scroll container. */
.Reactable {
  background-color: transparent !important;
  max-height: calc(100vh - 170px) !important;
  overflow-y: auto !important;
}

/* Give the analytics duration table the same bordered/boxed grid look as the
   rest of the app's data tables (Planning's .steps-table, Plan vs Actual)
   instead of reactable's default borderless list — the two otherwise read
   as different visual systems on the same page. Scoped to this table's id
   only so the other reactables on the page (e.g. "Selected set") keep the
   lighter, borderless look that already suits their in-page context. */
#analytics-duration-table.Reactable,
#analytics-duration-chem-table.Reactable,
#analytics-recent-table.Reactable {
  background-color: #fff !important;
  max-height: none !important;
  overflow-y: visible !important;
}
#analytics-duration-table .rt-table,
#analytics-duration-table .rt-tr,
#analytics-duration-table .rt-tbody,
#analytics-duration-chem-table .rt-table,
#analytics-duration-chem-table .rt-tr,
#analytics-duration-chem-table .rt-tbody,
#analytics-recent-table .rt-table,
#analytics-recent-table .rt-tr,
#analytics-recent-table .rt-tbody { background-color: #fff !important; }
/* Outer edge of the grid itself — reactable.css only sets border-color on
   .rt-table (no border-style), so nothing renders by default. Give it one,
   in the same color as the cell borders, so the grid reads as a single
   bordered block instead of stopping short at the card's own border. */
@supports (position: sticky) {
  #analytics-duration-table .rt-table,
  #analytics-duration-chem-table .rt-table,
  #analytics-recent-table .rt-table {
    border: 1px solid var(--panel-light-border);
  }
}
/* Reactable renders cells as flex divs, not a real <table>, so a border on
   every side of every cell (like a real table without border-collapse)
   doubles up into a heavier line wherever two cells meet — unlike
   .steps-table, which uses border-collapse:collapse for single 1px lines.
   Mimic collapse manually: each cell only draws its own right + bottom
   edge; the container's own border (see .Reactable rule above) supplies
   the missing left/top edge of the whole grid. */
#analytics-duration-table .rt-th,
#analytics-duration-table .rt-td,
#analytics-duration-chem-table .rt-th,
#analytics-duration-chem-table .rt-td,
#analytics-recent-table .rt-th,
#analytics-recent-table .rt-td {
  border: none;
  border-right: 1px solid var(--panel-light-border);
  border-bottom: 1px solid var(--panel-light-border);
  font-size: 0.82rem !important;
  line-height: 1.2 !important;
  align-items: flex-start;
  color: var(--content-ink) !important;
}
/* reactable.css always draws a decorative ::after line under every grouped
   header cell — including blank cells via .rt-th-group-none — hardcoded to
   #e6e6e6 regardless of any border-bottom override. Hidden entirely rather
   than recolored, since the group-header row's own border-bottom already
   separates it from the row below. */
#analytics-duration-table .rt-th-group:after,
#analytics-duration-table .rt-th-group-none:after,
#analytics-duration-chem-table .rt-th-group:after,
#analytics-duration-chem-table .rt-th-group-none:after {
  display: none;
}
/* reactable's own row height comes from .rt-td-inner/.rt-th-inner padding
   (7px 8px, set in reactable.css), not .rt-td/.rt-th themselves — override
   here or the padding rule above has nothing to shrink. */
#analytics-duration-table .rt-th-inner,
#analytics-duration-table .rt-td-inner,
#analytics-duration-chem-table .rt-th-inner,
#analytics-duration-chem-table .rt-td-inner,
#analytics-recent-table .rt-th-inner,
#analytics-recent-table .rt-td-inner {
  padding: 0.55rem 0.5rem 0.2rem !important;
}
/* Header style matches the dashboard's "main thead th" rule: filled
   var(--content-bg) background behind uppercase/dim label text. */
#analytics-duration-table .rt-th,
#analytics-duration-chem-table .rt-th,
#analytics-recent-table .rt-th {
  background-color: var(--content-bg) !important;
  font-size: 0.7rem !important;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--content-dim) !important;
}
#analytics-duration-table .rt-th-group,
#analytics-duration-chem-table .rt-th-group {
  background-color: var(--content-bg) !important;
  border: none;
  border-right: 1px solid var(--panel-light-border);
  border-bottom: 1px solid var(--panel-light-border);
  text-align: center;
  display: flex;
  align-items: flex-end;
  justify-content: center;
}
/* The merged "#"/group-label header cell (see the group_label colGroup in
   analytics_dashboard_view.R, now spanning c("num", "group_label")) holds
   the group-by picker or drill breadcrumb — left-align it there instead of
   the default centering, and it is always the leftmost group-header cell. */
#analytics-duration-table .rt-th-group:first-child,
#analytics-duration-chem-table .rt-th-group:first-child {
  justify-content: flex-start;
  padding-left: 0.5rem;
}
#analytics-duration-table .rt-tr .rt-td:last-child,
#analytics-duration-table .rt-tr .rt-th:last-child,
#analytics-duration-table .rt-th-group:last-child,
#analytics-duration-chem-table .rt-tr .rt-td:last-child,
#analytics-duration-chem-table .rt-tr .rt-th:last-child,
#analytics-duration-chem-table .rt-th-group:last-child,
#analytics-recent-table .rt-tr .rt-td:last-child,
#analytics-recent-table .rt-tr .rt-th:last-child { border-right: none; }

/* Detail (magnifying-glass) button in the duration table — a small icon
   button sized to sit inside a compact row without adding extra height the
   way an emoji glyph (taller line box, inconsistent across platforms) did. */
.analytics-detail-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  padding: 0;
  border: 1px solid var(--panel-light-border);
  border-radius: 3px;
  background: #fff;
  color: var(--content-dim);
  cursor: pointer;
  line-height: 1;
}
.analytics-detail-btn:hover { color: var(--content-ink); border-color: var(--content-dim); }

/* "All products" summary row — a manually-inserted div, styled to match a
   real .rt-tr/.rt-td row, spliced between duration_tbl's .rt-thead and
   .rt-tbody (see insertDurationSummaryRow() in the inline script). Reactable
   has no built-in pinned-row support, so this row is DOM content we own and
   re-insert after every render rather than reactable data, which keeps it
   fixed regardless of the table's own sorting/pagination. */
#analytics-duration-table .an-duration-summary-row,
#analytics-duration-chem-table .an-duration-summary-row {
  display: flex;
  background-color: var(--content-bg) !important;
  font-style: italic;
}
#analytics-duration-table .an-duration-summary-row .rt-td,
#analytics-duration-chem-table .an-duration-summary-row .rt-td {
  display: flex !important;
  align-items: flex-start !important;
  font-size: 0.82rem !important;
  line-height: 1.2 !important;
  padding: 0.55rem 0.5rem 0.2rem !important;
  border: none;
  border-right: 1px solid var(--panel-light-border);
  border-bottom: 1px solid var(--panel-light-border);
  font-weight: 700;
  flex-shrink: 0;
}
#analytics-duration-table .an-duration-summary-row .rt-td:last-child,
#analytics-duration-chem-table .an-duration-summary-row .rt-td:last-child { border-right: none; }
/* Duration (avg/avg incl. stoppages/median/min/max) and Cases columns — 3rd through 8th cell,
   matching the right-aligned numeric columns in the rows below. */
#analytics-duration-table .an-duration-summary-row .rt-td:nth-child(n+3):nth-child(-n+8),
#analytics-duration-chem-table .an-duration-summary-row .rt-td:nth-child(n+3):nth-child(-n+8) {
  justify-content: flex-end;
  text-align: right;
}

/* "Group by" picker embedded in the duration table's own group header (see
   the group_label colGroup in analytics_dashboard_view.R) instead of a
   separate toolbar row — kept plain/compact to fit inside a header cell. */
.analytics-duration-groupby-select {
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: none;
  letter-spacing: normal;
  color: var(--content-ink);
  background: #fff;
  border: 1px solid var(--panel-light-border);
  border-radius: 3px;
  padding: 0.15rem 0.35rem;
  width: 100%;
  max-width: 100%;
}

/* Pagination controls */
.rt-pagination { color: var(--content-ink) !important; }
.rt-pagination-info { color: var(--content-ink) !important; }
.rt-page-button { color: var(--content-ink) !important; background: #fff !important; border: 1px solid var(--panel-light-border) !important; border-radius: 3px !important; padding: 0.2rem 0.55rem !important; }
.rt-page-button:hover { background: var(--content-bg) !important; }
.rt-page-button[disabled] { color: var(--content-dim) !important; }
.rt-page-button-current { background: var(--accent) !important; color: #fff !important; border-color: var(--accent) !important; }

/* .page-toolbar: flex row shared by "+ Add" button and the reactable search
   input (moved there by app.js at DOMContentLoaded). */
.page-toolbar { margin-bottom: 0.5rem; }

/* Analytics dashboard KPI tile row (R/views/analytics_dashboard_view.R) —
   "Option E" from the KPI tile design exploration: value + a combined
   %/absolute "vs last year" row + an 8-month trend sparkline. Sentiment
   (good/bad) is per-metric, not direction-based — see KPI_FIELDS in
   analytics_dashboard_view.R — and drives the kpi-chip-good/bad,
   kpi-compare-abs-good/bad classes below for the "vs last year" chip/delta
   text. The sparkline is colored independently, per month-to-month step
   (see applyKpiTrend()/buildSparklineSegmentsHtml()) rather than from that
   same-tile comparison, so it can show its own ups and downs. */
.kpi-row { display: flex; flex-wrap: wrap; gap: 0.75rem; margin: 0.75rem 0 1.25rem; }
.kpi-tile {
  flex: 1 1 220px;
  min-width: 220px;
  padding: 0.9rem 1rem 0.8rem;
  background: var(--content-bg);
  border: 1px solid var(--panel-light-border);
  border-radius: 10px;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}
.kpi-label { font-size: 0.78rem; font-weight: 600; color: var(--content-dim); }
.kpi-value-row { display: flex; align-items: baseline; gap: 0.35rem; }
.kpi-value { font-size: 1.35rem; font-weight: 700; color: var(--content-ink); }
.kpi-unit { font-size: 0.75rem; font-weight: 600; color: var(--content-dim); }
/* min-height keeps this row's layout space reserved when hidden via
   visibility (see applyComparisonBadge() in analytics_dashboard_view.R) —
   so its height, and therefore how far down the chart/footer below it
   sit, never depends on whether a comparison was available this period. */
.kpi-compare-row { display: flex; align-items: center; gap: 0.4rem; flex-wrap: wrap; min-height: 1.35rem; }
.kpi-chip {
  display: inline-flex; align-items: center; gap: 0.2rem;
  padding: 0.1rem 0.5rem; border-radius: 999px;
  font-size: 0.72rem; font-weight: 700; white-space: nowrap;
}
.kpi-chip-sm { padding: 0.05rem 0.4rem; font-size: 0.64rem; }
.kpi-chip-good { background: #e7f5ec; color: #15803d; }
.kpi-chip-bad { background: #fdf1e2; color: #b45309; }
.kpi-compare-text { font-size: 0.72rem; color: var(--content-dim); white-space: nowrap; }
.kpi-compare-abs { font-weight: 700; }
.kpi-compare-abs-good { color: #15803d; }
.kpi-compare-abs-bad { color: #b45309; }
.kpi-trend { position: relative; }
.kpi-trend-svg { display: block; width: 100%; height: 44px; pointer-events: none; }
/* Plain HTML overlay for the sparkline hover targets, laid over the SVG
   with flexbox rather than SVG <rect> hit-testing — a fixed-viewBox SVG
   scaled to a variable container width relies on the browser to map
   pointer coordinates back into SVG user space, and that mapping was
   observed to need much more precise pointer placement in Edge than in
   Vivaldi/Chrome for the same page. A flex row of plain divs hit-tests
   with ordinary CSS box geometry instead, which is consistent everywhere. */
.kpi-trend-dots { position: absolute; top: 0; left: 0; right: 0; height: 44px; pointer-events: none; }
.kpi-trend-dot { position: absolute; width: 6px; height: 6px; border-radius: 50%; transform: translate(-50%, -50%); }
.kpi-trend-hits { position: absolute; top: 0; left: 0; right: 0; height: 44px; display: flex; }
.kpi-trend-hit { flex: 1 1 0%; height: 100%; }
.kpi-trend-axis { display: flex; justify-content: space-between; font-size: 0.62rem; color: var(--content-dim); opacity: 0.75; margin-top: 0.1rem; }
/* Secondary stat (e.g. "avg 21.4 m³") + its own vs-last-year comparison,
   below a hairline under the chart — always rendered (see
   analytics_kpi_tile()), even for a tile whose KPI_FIELDS entry has no
   `sub`, so every tile in the row keeps the same height regardless of
   whether it has a secondary stat this period. Deliberately NOT
   margin-top: auto — everything above it (label, value, the fixed-height
   .kpi-compare-row, the chart) is now a constant height across every
   tile, so the hairline sits at the same offset from the top in every
   tile without needing to be pinned to the bottom of a flex column
   (which varied with whatever content happened to be above it, and
   pushed the hairline itself around). */
.kpi-footer {
  padding-top: 0.5rem;
  border-top: 1px solid var(--panel-light-border);
  display: flex; flex-direction: column; gap: 0.2rem;
  min-height: 2.5rem;
}
.kpi-sub-value { font-size: 0.72rem; font-weight: 600; color: var(--content-dim); min-height: 1em; }
.kpi-sub-compare { display: flex; align-items: center; gap: 0.35rem; flex-wrap: wrap; min-height: 1.1rem; }

/* Duration table "Range (h)" column (R/views/analytics_dashboard_view.R,
   buildDurationBoxplotHtml()) — replaces the old separate Median/Min/Max
   columns with one in-cell box plot: the box for each row is positioned
   and sized against a scale shared by every row in the table (the lowest
   min and highest max — see window.anDurationRangeScale, computed in
   loadDurationStats()), not each row own min-max, so rows can be compared
   against each other. A dashed line marks the median and a solid dot the
   average, both on that same shared scale. */
/* Fixed-width edge labels (regardless of digit count) keep the track at
   the same x position in every row, so the boxes line up in a straight
   column and can be compared row by row — see the client screenshot this
   was raised against, where "3.0"/"14.5"/"9.7"/"4.0" of varying width
   pushed each row track sideways by a different amount. */
.dur-boxplot { display: flex; align-items: center; gap: 0.5rem; width: 100%; }
.dur-boxplot-edge { width: 2.2rem; flex-shrink: 0; font-size: 0.75rem; color: var(--content-dim); font-variant-numeric: tabular-nums; }
.dur-boxplot-edge:first-child { text-align: right; }
.dur-boxplot-edge:last-child { text-align: left; }
/* Grows to use the Range column's minWidth (see the median_h colDef) —
   room freed up by narrowing the Cases column — capped so the box does
   not stretch absurdly wide on a very large screen. */
.dur-boxplot-track { position: relative; flex: 1 1 auto; min-width: 64px; max-width: 160px; height: 20px; }
/* left/width set inline per cell (see buildDurationBoxplotHtml()) — the box
   spans this row own min-max against the table shared scale, so it is not
   always full width like a plain range-filled track. */
.dur-boxplot-box { position: absolute; top: 6px; bottom: 6px; background: var(--panel-light-border); border: 1px solid var(--content-dim); border-radius: 2px; opacity: 0.6; min-width: 2px; }
/* Taller than the avg dot on purpose (20px vs the dot's 8px) so it still
   pokes out top and bottom when the median sits at nearly the same
   position as the average, instead of being fully hidden behind the dot. */
.dur-boxplot-median {
  position: absolute; top: 50%; width: 0; height: 20px;
  border-left: 2px dashed var(--content-ink);
  transform: translate(-1px, -50%);
}
.dur-boxplot-avg {
  position: absolute; top: 50%; width: 8px; height: 8px;
  background: var(--accent); border-radius: 50%;
  transform: translate(-50%, -50%);
  box-shadow: 0 0 0 1.5px var(--content-bg);
}

/* Foldable, horizontal filter box (R/views/analytics_dashboard_view.R). White
   background + bold uppercase field labels to match the white "card" look
   used by Planning's .plan-card/.plan-label (public/plan_propose.css) —
   that CSS isn't loaded on this page, so the equivalent look is reproduced
   here under the analytics-* names already used by this view. */
.analytics-filter-box {
  background: #fff;
  border: 1px solid var(--panel-light-border);
  border-radius: 6px;
  margin-bottom: 0.75rem;
}
.analytics-filter-header {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.6rem 0.9rem;
  cursor: pointer;
  user-select: none;
}
.analytics-filter-header:hover { background: #f7fbfc; }
.analytics-filter-chevron {
  font-size: 0.6rem;
  color: var(--content-dim);
  transition: transform 0.15s;
  flex-shrink: 0;
}
.analytics-filter-header.open .analytics-filter-chevron { transform: rotate(90deg); }
.analytics-filter-title {
  font-size: 0.95rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--content-ink);
}
.analytics-filter-body { padding: 0 0.9rem 0.75rem; }
.analytics-filter-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.75rem;
  margin-bottom: 0.5rem;
}
.analytics-filter-row-actions { align-items: center; }
.analytics-filter-row .form-row { min-width: 140px; }
.analytics-filter-row .form-row-wide { min-width: 220px; flex: 1 1 220px; }
/* Vessel filter's own row (see analytics_dashboard_view.R) — spans the full
   filter-row width instead of sharing it, since it is the widest/most-used
   picker (all vessels by default). */
.analytics-filter-row .form-row-full { min-width: 220px; flex: 1 1 100%; }
.analytics-compare-toggle { display: flex; align-items: center; gap: 0.35rem; font-size: 0.9rem; }
.btn-small { padding: 0.15rem 0.6rem; font-size: 0.8rem; }

/* Field labels inside the filter/tab boxes — matches .plan-label exactly.
   Excludes .analytics-compare-toggle, which is a checkbox wrapper label,
   not a field caption. */
.analytics-filter-row label:not(.analytics-compare-toggle),
.analytics-tab-panel label:not(.analytics-compare-toggle) {
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--content-dim);
  display: block;
  margin-bottom: 0.2rem;
}

.analytics-tabs {
  background: #fff;
  border: 1px solid var(--panel-light-border);
  border-radius: 6px;
  margin-bottom: 0.75rem;
}
.analytics-tab-bar {
  display: flex;
  gap: 0.25rem;
  border-bottom: 1px solid var(--panel-light-border);
  padding: 0 0.9rem;
}
.analytics-tab-btn {
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  padding: 0.6rem 0.85rem;
  font-weight: 600;
  font-size: 0.85rem;
  color: var(--content-dim);
  cursor: pointer;
}
.analytics-tab-btn.active { color: var(--content-ink); border-bottom-color: var(--accent-mid); }
.analytics-tab-panel { padding: 0.9rem; }

.analytics-drill-link { color: var(--accent-mid); text-decoration: underline; cursor: pointer; }
.analytics-drill-breadcrumb { margin-bottom: 0.5rem; }
.analytics-drill-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  background: var(--panel-light-border, #dfe8eb);
  color: var(--content-ink);
  border-radius: 4px;
  padding: 0.2rem 0.5rem;
  font-size: 0.82rem;
}
.analytics-drill-clear {
  background: none;
  border: none;
  cursor: pointer;
  font-size: 1rem;
  line-height: 1;
  color: var(--content-ink);
  padding: 0;
}

/* Attribute/chart toolbar + chart cards. */
.analytics-chart-toolbar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: flex-end;
  margin: 0.5rem 0 0.75rem;
}
.analytics-chart-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: 1rem;
  margin-bottom: 1.5rem;
}
.analytics-chart-card {
  background: #ffffff;
  border: 1px solid var(--panel-light-border);
  border-radius: 6px;
  box-shadow: 0 1px 3px rgba(20, 50, 60, 0.08);
  overflow: hidden;
}
/* Table-only cards (Duration, Cleaning Plans in Selection) match the
   dashboard's .dash-card border exactly — no radius/shadow mismatch against
   the plain bordered look of the app's other data-table boxes. */
.analytics-chart-card-plain {
  border-radius: 4px;
  box-shadow: none;
}
.analytics-chart-card-header {
  padding: 0.6rem 1rem;
  border-bottom: 1px solid var(--panel-light-border);
  background: #f7fafb;
  display: flex; align-items: center; justify-content: space-between; gap: 0.75rem;
}
.analytics-chart-card-header h3 {
  margin: 0; font-size: 0.9rem; font-weight: 700; text-transform: capitalize;
  color: var(--content-ink);
}
.analytics-chart-table-btn { flex-shrink: 0; white-space: nowrap; }
.analytics-chart-card-actions { display: flex; gap: 0.5rem; flex-shrink: 0; }

/* Column picker dialog (Cleaning Plans in Selection). */
.analytics-columns-hint { margin: 0 0 0.6rem; font-size: 0.84rem; color: var(--content-dim); }
.analytics-columns-list {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr));
  gap: 0.3rem 1rem; max-height: 55vh; overflow-y: auto;
}
.analytics-columns-item { display: flex; gap: 0.4rem; align-items: center; font-size: 0.88rem; cursor: pointer; }
.analytics-columns-error { color: var(--danger, #b3261e); font-size: 0.84rem; min-height: 1.2em; margin-top: 0.5rem; }
.analytics-chart-card-body { padding: 1rem; }

.analytics-chart-unit { font-size: 0.72rem; color: var(--content-dim); padding-left: 0.25rem; }
.analytics-bar-chart-frame { display: flex; align-items: stretch; height: 380px; }
.analytics-bar-yaxis {
  display: flex; flex-direction: column; justify-content: space-between;
  align-items: flex-end; padding: 0.5rem 0.5rem 80px 0;
  font-size: 0.72rem; color: var(--content-dim); flex-shrink: 0;
}
/* Grid, not flex, for the plot: with flex the bar column and its label sit
   in the SAME flex item, so align-items:flex-end pins the *label's* bottom
   to the container edge — the bar's own baseline then floats wherever the
   label's height happens to push it, which drifts from wherever the axis
   line is drawn and makes bars look like they don't start at 0. Grid gives
   the bar row a hard 300px track and the label row its own track below it,
   so the bar's bottom edge is always exactly the row boundary — the same
   pixel the axis line (::after, at top: 0.5rem + 300px) is drawn at. */
.analytics-bar-chart {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(48px, 96px);
  grid-template-rows: 300px auto;
  align-items: end;
  justify-items: center;
  column-gap: 0.9rem;
  height: 380px;
  padding-top: 0.5rem;
  overflow-x: auto;
  overflow-y: hidden;
  flex: 1;
  position: relative;
}
/* The y-axis vertical line only covers the plot row (0..300px) — negative
   consumption isn't possible, so it must not run down into the label row
   as a plain full-height border-left would, which reads as the axis
   continuing below 0. */
.analytics-bar-chart::before {
  content: "";
  position: absolute;
  left: 0; top: 0.5rem;
  width: 1px;
  height: 300px;
  background: var(--content-dim);
}
.analytics-bar-chart::after {
  content: "";
  position: absolute;
  left: 0; right: 0;
  top: calc(0.5rem + 300px);
  height: 1px;
  background: var(--content-dim);
}
/* display:contents lets a group's own bar-pair/label children become
   direct grid items of .analytics-bar-chart above, so grid-auto-flow:column
   places each group's bar-pair in row 1 and its label directly below it in
   row 2, one grid column per group. */
.analytics-bar-group { display: contents; }
.analytics-bar-pair { display: flex; align-items: flex-end; gap: 3px; height: 100%; align-self: stretch; }
.analytics-bar {
  width: 40px; background: var(--accent-mid); border-radius: 2px 2px 0 0;
  transition: filter 0.1s ease; cursor: default;
}
.analytics-bar:hover, .analytics-bar-prev:hover { filter: brightness(0.85); }
.analytics-bar-prev { background: var(--panel-light-border); }
.analytics-bar-label {
  font-size: 0.72rem; color: var(--content-dim); margin-top: 0.4rem;
  max-width: 92px; text-align: center; overflow-wrap: anywhere;
  cursor: default;
  /* Overrides the grid's align-items:end (needed for the bar row above) —
     the label row is only ever taller than one line when a year sub-line
     shows under the first bar of a year, and without this every other
     bar's (shorter, single-line) label bottom-aligns to that taller row
     instead of lining its month text up with the others. */
  align-self: start;
}
.analytics-bar-label-main {
  display: block;
  white-space: normal; overflow-wrap: break-word;
}
/* Year sub-line under a month/quarter bar label — shown only on the first
   bar of each year (like the scatter chart's x-axis year row), so a
   multi-year "group by month/quarter" bar chart reads the same way. */
.analytics-bar-label-year {
  font-weight: 700;
  margin-top: 0.15rem;
}
.analytics-chart-legend {
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: 0.5rem 1rem; font-size: 0.78rem; color: var(--content-dim); margin-top: 1.25rem;
}
.analytics-chart-legend span::before {
  content: ""; display: inline-block; width: 10px; height: 10px; margin-right: 0.3rem;
  border-radius: 2px; vertical-align: middle;
}
.analytics-legend-current::before { background: var(--accent-mid); }
.analytics-legend-prev::before { background: var(--panel-light-border); }
.analytics-legend-grade::before { background: var(--seg-color, var(--accent-mid)); }

.analytics-summary-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.85rem;
}
.analytics-summary-table th, .analytics-summary-table td {
  padding: 0.45rem 0.6rem;
  border-bottom: 1px solid var(--panel-light-border);
  text-align: left;
}
.analytics-summary-table th.num, .analytics-summary-table td.num {
  text-align: right;
}
.analytics-summary-table thead th {
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--content-dim);
  border-bottom: 2px solid var(--panel-light-border);
}
.an-summary-group-row td {
  font-weight: 700;
  background: var(--panel-light-bg, rgba(0, 0, 0, 0.03));
  border-bottom: 1px solid var(--panel-light-border);
}
.analytics-summary-empty {
  color: var(--content-dim);
  text-align: center;
  padding: 1rem;
}

.analytics-bar-stack { display: flex; flex-direction: column-reverse; width: 40px; }
.analytics-bar-seg {
  width: 100%; cursor: default;
  border-bottom: 1px solid #ffffff;
  transition: filter 0.1s ease;
}
.analytics-bar-seg:hover { filter: brightness(0.85); }
.analytics-bar-seg:first-child { border-bottom: none; }

.analytics-chart-tooltip {
  position: fixed;
  z-index: 500;
  background: #ffffff;
  border: 1px solid var(--panel-light-border);
  border-radius: 5px;
  box-shadow: 0 2px 8px rgba(20, 50, 60, 0.18);
  padding: 0.5rem 0.65rem;
  font-size: 0.78rem;
  color: var(--content-ink);
  line-height: 1.4;
  pointer-events: none;
  display: none;
  max-width: 260px;
}
.analytics-chart-tooltip .tt-title { font-weight: 700; margin-bottom: 0.15rem; }
.analytics-chart-tooltip .tt-row { color: var(--content-dim); }

.analytics-scatter-chart { width: 100%; height: 320px; overflow-x: auto; overflow-y: hidden; }
.analytics-scatter-chart circle { fill: var(--accent-mid); cursor: default; transition: r 0.1s ease, fill 0.1s ease; }
.analytics-scatter-chart circle:hover { r: 5.5; fill: var(--accent); }
.analytics-empty { color: var(--content-dim); font-size: 0.85rem; padding: 1.5rem 0; text-align: center; }

/* Tom Select look, matched to plan_propose.css so multi-selects (e.g. the
   analytics vessel picker) look the same as the rest of the app. Kept as a
   small subset here rather than loading all of plan_propose.css. */
.ts-wrapper { width: 100%; }
.ts-control {
  border: 1px solid var(--panel-light-border) !important;
  border-radius: 3px !important;
  font-size: .84rem !important;
  font-family: inherit !important;
  color: var(--content-ink) !important;
  background: #fff !important;
  padding: .25rem .5rem !important;
  min-height: 2rem !important;
  box-shadow: none !important;
}
.ts-control:focus-within { border-color: var(--accent-mid) !important; }
.ts-dropdown {
  border: 1px solid var(--panel-light-border) !important;
  border-radius: 3px !important;
  font-size: .84rem !important;
  font-family: inherit !important;
  box-shadow: 0 2px 6px rgba(0,0,0,.08) !important;
}
.ts-dropdown .option { padding: .3rem .6rem !important; color: var(--content-ink) !important; }
.ts-dropdown .option.active { background: var(--content-bg) !important; color: var(--accent-mid) !important; }
.ts-dropdown .option:hover { background: #f0f7fb !important; }
.ts-dropdown input.ts-search {
  border: none !important;
  border-bottom: 1px solid var(--panel-light-border) !important;
  border-radius: 0 !important;
  padding: .3rem .6rem !important;
  font-size: .84rem !important;
  font-family: inherit !important;
  width: 100% !important;
  box-shadow: none !important;
  outline: none !important;
}
.ts-control .item {
  background: var(--content-bg) !important;
  border: 1px solid var(--panel-light-border) !important;
  border-radius: 2px !important;
  padding: .05rem .35rem !important;
  font-size: .78rem !important;
  color: var(--content-ink) !important;
}
.ts-control .item .remove { color: var(--content-dim) !important; border-left: 1px solid var(--panel-light-border) !important; padding: 0 .25rem !important; margin-left: .25rem !important; }
.ts-control .item .remove:hover { color: #c62828 !important; background: #fef2f2 !important; }
.ts-wrapper.multi .ts-dropdown .option::before {
  content: ""; display: inline-block; width: 12px; height: 12px;
  border: 1px solid var(--panel-light-border); border-radius: 2px;
  margin-right: .4rem; vertical-align: middle; background: #fff;
}
.ts-wrapper.multi .ts-dropdown .option.selected::before {
  background: var(--accent-mid); border-color: var(--accent-mid);
  content: "✓"; color: #fff; font-size: 9px; line-height: 12px; text-align: center;
}
.ts-select-all-header { display: flex; border-bottom: 1px solid var(--panel-light-border); }
.ts-select-all-btn { flex: 1 1 50%; background: #fff; border: none; padding: .3rem .4rem; font-size: .78rem; font-family: inherit; color: var(--accent-mid); cursor: pointer; }
.ts-select-all-btn:hover { background: #f0f7fb; }
.ts-select-all-btn:first-child { border-right: 1px solid var(--panel-light-border); }

/* Collapses a multi-select's chips into a single "N selected" summary once
   there are more than a few (see makeCompactMultiSelect() in
   analytics_dashboard_view.R) — avoids a wall of chips for e.g. "all vessels". */
.ts-wrapper.ts-compact .ts-control .item { display: none; }
.ts-compact-summary { font-size: .82rem; color: var(--content-ink); padding: .05rem .2rem; }

/* Plain data table used by the analytics drill-down dialog. */
.data-table { width: 100%; border-collapse: collapse; font-size: 0.85rem; }
.data-table th, .data-table td { text-align: left; padding: 0.3rem 0.5rem; border-bottom: 1px solid var(--panel-light-border); }

/* Bordered/boxed table with an uppercase header row — the look used by the
   Plan vs Actual step table (public/plan_propose.css's .steps-table), used
   here too (analytics duration detail dialog) so the two "same style"
   tables actually match instead of only being described as matching. Only
   the base look is copied, not .pva-*'s stage-grouping styles — this
   table's rows aren't grouped. */
.steps-table { width: 100%; border-collapse: collapse; background: #fff; font-size: 0.82rem; }
.steps-table th, .steps-table td {
  padding: 0.38rem 0.5rem;
  border: 1px solid var(--panel-light-border);
  color: var(--content-ink);
  text-align: left;
  vertical-align: middle;
}
.steps-table thead th {
  background: var(--content-bg);
  font-weight: 700;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  vertical-align: bottom;
}
.steps-table th.num, .steps-table td.num { text-align: right; }

/* Summary: keep the label column on one line (labels are long). */
.summary-table td:first-child {
  white-space: nowrap;
}

/* Step-header-row-with-indented-sub-rows layout, matching the Plan vs
   Actual table's .pva-* classes (public/plan_propose.css) — used here by
   the analytics duration detail dialog, which nests items under their step
   in one flat <tbody> rather than one <tbody> per step, so the group
   separator is a border on every stage row instead of an adjacent-tbody
   selector. */
.pva-table .pva-stage-row td { background: var(--content-bg); font-weight: 600; border-top: 1px solid var(--panel-light-border); }
.pva-table .pva-sub-row td { border-top: none; }
.pva-table .pva-sub-label { padding-left: 1.5rem; font-weight: 400; color: var(--content-dim); }

/* Section caption above the stoppages overview in the analytics detail
   dialog — matches public/plan_propose.css's .plan-section-title, which
   isn't loaded on this page. */
.plan-section-title {
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--content-dim);
  margin-bottom: 0.65rem;
  padding-bottom: 0.35rem;
  border-bottom: 1px solid var(--panel-light-border);
}

/* Inline-editing rows: inputs fill cell width, no label spacing */
#seg-table td input {
  width: 100%;
  padding: 0.2rem 0.4rem;
  font-size: 0.84rem;
  font-family: inherit;
  border: 1px solid var(--accent-mid);
  border-radius: 3px;
  background: #fff;
  color: var(--content-ink);
  box-sizing: border-box;
}
#seg-table td input:focus { outline: none; border-color: var(--accent); }
.seg-editing td { background: #f7fbfc; }

/* ---------- Mobile menu toggle ---------- */
.menu-toggle {
  display: none;
  background: none;
  border: 1px solid var(--panel-light-border);
  color: var(--content-ink);
  width: 34px;
  height: 34px;
  border-radius: 3px;
  font-size: 1rem;
  cursor: pointer;
}

@media (max-width: 640px) {
  .menu-toggle { display: block; order: -1; margin-right: 0.75rem; }

  nav {
    position: fixed;
    top: 53px;
    left: 0;
    bottom: 0;
    background: var(--panel);
    transform: translateX(-100%);
    transition: transform 0.15s ease;
    z-index: 10;
    width: 220px;
  }

  nav.open {
    transform: translateX(0);
  }

  .status-text { display: none; }

  main { padding: 1rem 0.75rem; }

  /* Wide tables get a horizontal scroller instead of squeezing columns
     unreadable-thin on narrow screens. */
  main table {
    display: block;
    overflow-x: auto;
    white-space: nowrap;
  }
}

/* ---------- Login page ---------- */
.login-wrap {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
  background: var(--content-bg);
}

.login-card {
  background: #ffffff;
  border: 1px solid var(--panel-light-border);
  border-radius: 4px;
  padding: 2rem;
  min-width: 340px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.07);
}

.login-card h4 {
  margin-bottom: 1.25rem;
  font-size: 1rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--content-ink);
}

.login-card .form-group {
  margin-bottom: 0.75rem;
}

.login-card input[type="email"],
.login-card input[type="password"] {
  display: block;
  width: 100%;
  padding: 0.45rem 0.6rem;
  font-size: 0.9rem;
  border: 1px solid var(--panel-light-border);
  border-radius: 3px;
  background: #fff;
  color: var(--content-ink);
  font-family: inherit;
  box-sizing: border-box;
}

.login-card input:focus {
  outline: none;
  border-color: var(--accent-mid);
}

.login-card .btn-submit {
  display: block;
  width: 100%;
  padding: 0.5rem;
  background: var(--accent);
  color: #fff;
  border: none;
  border-radius: 3px;
  font-size: 0.9rem;
  font-weight: 600;
  cursor: pointer;
  font-family: inherit;
  margin-top: 0.25rem;
}

.login-card .btn-submit:hover {
  background: var(--accent-mid);
}

.login-card .btn-submit:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

.login-card .btn-ms {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0.45rem;
  margin-top: 0.75rem;
  background: #fff;
  border: 1px solid var(--panel-light-border);
  border-radius: 3px;
  font-size: 0.85rem;
  color: var(--content-ink);
  text-decoration: none;
  font-family: inherit;
  cursor: pointer;
}

.login-card .btn-ms:hover {
  background: var(--content-bg);
}

.login-card .alert-error {
  background: #fef2f2;
  border: 1px solid #fca5a5;
  color: #991b1b;
  border-radius: 3px;
  padding: 0.5rem 0.75rem;
  font-size: 0.82rem;
  margin-bottom: 0.75rem;
}

.login-card hr {
  border: none;
  border-top: 1px solid var(--panel-light-border);
  margin: 0.75rem 0;
}

/* Plain text/links in the login card otherwise inherit body's near-white
   --ink (meant for the dark sidebar theme) and become nearly invisible
   against the white card. */
.login-card p {
  color: var(--content-ink);
  margin-bottom: 0.75rem;
  line-height: 1.5;
}

.login-card p:last-child {
  margin-bottom: 0;
}

.login-card a {
  color: var(--accent-mid);
}

/* ---------- Badges (plan status) ---------- */
.badge{display:inline-block;padding:.15rem .55rem;border-radius:3px;font-size:.7rem;font-weight:700;letter-spacing:.06em;text-transform:uppercase;}
.badge-draft{background:#f0f4f6;color:var(--content-dim);border:1px solid var(--panel-light-border);}
.badge-submitted{background:#e0f2fe;color:#0369a1;border:1px solid #bae6fd;}
.badge-in-progress{background:#fef9c3;color:#854d0e;border:1px solid #fde68a;}
.badge-planning-complete{background:#f0fdf4;color:#166534;border:1px solid #bbf7d0;}
.badge-completed{background:#dcfce7;color:#166534;border:1px solid #bbf7d0;}

/* ---------- Dashboard ---------- */
.dash-action-row { margin-bottom: 1.25rem; display: flex; align-items: center; gap: 0.5rem; }
.btn-action-lg { display: inline-block; padding: .65rem 1.4rem; font-size: .95rem; }
.dash-card {
  background: #fff;
  border: 1px solid var(--panel-light-border);
  border-radius: 4px;
  padding: 1rem 1.25rem;
  margin-bottom: 1.25rem;
}
.dash-card h3 {
  font-size: .8rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--content-ink);
  margin-bottom: .75rem;
  padding-bottom: .5rem;
  border-bottom: 1px solid var(--panel-light-border);
}
/* table-layout: fixed so the Vessel and Voy# columns line up across the
   dashboard's stacked tables. With auto layout each table sized those columns
   to its own content, so the same column started at a different x in each.
   Every column needs an explicit width — an unwidthed column can be squeezed
   to ~0 (and its header text overlap the next column) once the other
   columns' fixed widths already exceed the card's available width, which
   happened to the Program column in the Running/Upcoming tables. The wrap
   div lets the table scroll horizontally rather than squashing columns on
   narrow screens. */
.dash-table-wrap { overflow-x: auto; }
.dash-table { width: 100%; min-width: 46rem; border-collapse: collapse; font-size: .84rem; table-layout: fixed; }
.dash-table th, .dash-table td { padding: .5rem .6rem; border-bottom: 1px solid var(--panel-light-border); text-align: left; color: var(--content-ink); vertical-align: top; }
.dash-table th.th-sortable { cursor: pointer; user-select: none; white-space: nowrap; }
.dash-table th.th-sortable:hover { color: var(--content-ink); }
.dash-table th.th-sortable::after { content: " ⇅"; opacity: .35; }
.dash-table th.th-sortable[aria-sort="ascending"]::after { content: " ▲"; opacity: 1; }
.dash-table th.th-sortable[aria-sort="descending"]::after { content: " ▼"; opacity: 1; }
.dash-table th { font-size: .7rem; text-transform: uppercase; letter-spacing: .05em; color: var(--content-dim); font-weight: 700; vertical-align: bottom; }
/* Hanging indent: the bullet sits on the cell's text origin (so it lines up
   with the column header) and the text is indented from it, with wrapped
   lines aligning to the text rather than to the bullet. A plain list-style
   marker hangs to the LEFT of the cell's text origin instead, which is what
   made the bullets look detached from the column. */
.program-list { margin: 0; padding-left: 0; list-style: none; }
.program-list li { padding-left: 1.1rem; text-indent: -1.1rem; }
.program-list li::before {
  content: "•";
  display: inline-block;
  width: 1.1rem;
  text-indent: 0;
}
.program-list li + li { margin-top: 0.35rem; }
.dash-empty { color: var(--content-dim); font-style: italic; }
tr.row-clickable { cursor: pointer; }
tr.row-clickable:hover td { background: #f7fbfc; }
.dash-col-narrow { white-space: nowrap; }
.col-vessel { width: 12rem; }
.col-voyage { width: 4.5rem; overflow-wrap: break-word; }
.col-program { width: 10rem; }
.col-stage { width: 16rem; }
.col-runfor { width: 8rem; }
.col-status { width: 11rem; }
.col-rowaction { width: 7.5rem; }

/* ---------- Print (Plan / Program / Execution Log / Plan vs Actual) ----------
   #print-area is populated on demand by plan_print.js just before
   window.print() and stays hidden the rest of the time. The @media print
   rule below is what actually swaps the screen away for it — everything
   else in the document is hidden while printing, print-area is shown. */
#print-area { display: none; }

.print-header { margin-bottom: 1rem; }
.print-header h1 { font-size: 1.1rem; margin: 0 0 .25rem; }
.print-header .print-meta { font-size: .82rem; color: #000; font-weight: 600; margin-bottom: .4rem; }
.print-program { margin-bottom: 1.25rem; }
.print-program h2 { font-size: .95rem; margin: 0 0 .4rem; border-bottom: 1px solid #999; padding-bottom: .2rem; }
.print-program .print-meta { font-size: .8rem; color: #000; font-weight: 600; margin-bottom: .4rem; }
.print-export-tag { font-size: .78rem; font-weight: 700; color: #000; text-transform: uppercase; letter-spacing: .02em; margin-bottom: .3rem; }
/* border-collapse: separate (not collapse) — with collapse, Chrome's
   headless Page.printToPDF sometimes overpaints the leading pixels of a
   <th>'s text with the shared cell border, clipping the first letter of
   headers like "Concentration"/"Duration" (seen in practice, not just in
   theory). Giving each cell its own non-shared border avoids that overlap;
   border-spacing: 0 plus the +-selector rules below keep the grid visually
   identical to collapse (no doubled interior lines). */
.print-table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: .78rem; margin-bottom: .5rem; }
.print-table th, .print-table td { border: 1px solid #999; padding: .25rem .4rem; text-align: left; vertical-align: top; }
.print-table th + th, .print-table td + td { border-left: none; }
.print-table tr + tr th, .print-table tr + tr td { border-top: none; }
.print-table th { background: #eee; }
.print-resource-list { margin: 0; padding-left: 1.1em; }
.print-resource-list li { white-space: nowrap; }
.print-table th.print-num, .print-table td.print-num { width: 1.6em; text-align: right; }
.print-section-label { font-weight: 600; font-size: .82rem; margin: .5rem 0 .2rem; }
/* Section headings in the combined "Print Plan" export (Planning /
   Execution Log / Plan vs Actual) — see printFullPlan() in plan_print.js. */
.print-section-title { font-size: 1.05rem; margin: 0 0 .75rem; }

@media print {
  /* body's normal look is the dark app theme (--bg/--ink) — without this,
     the whole page prints as a solid dark rectangle with light text, since
     hiding every element but #print-area still leaves body's own
     background/color/flex layout in effect. */
  body {
    background: #fff !important;
    color: #000 !important;
    display: block !important;
  }
  body > *:not(#print-area) { display: none !important; }
  #print-area { display: block !important; }
  /* Every program/section forces a fresh page by default; plan_print.js
     adds .print-first to the one immediately following the voyage header
     or a section title so that pairing doesn't produce a blank leading
     page (see printFirstClass() there). */
  #print-area .print-program,
  #print-area .print-page-break { page-break-before: always; }
  #print-area .print-program.print-first { page-break-before: avoid; }
}

.matrix-cb { cursor: pointer; width: 16px; height: 16px; }

/* Cleaning stages matrix: bottom-align header labels so single- and
   two-line headers share a baseline above the checkboxes. */
#stages-table .rt-th { align-items: flex-end; }

/* "Inserted from Excel report" flag on the plan Details tab — orange box so
   plans keyed in from old reports stand out. */
.plan-excel-flag {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin-top: 14px;
  padding: .6rem .8rem;
  background: #fff1e0;
  border: 1px solid #f59e0b;
  border-radius: 6px;
  color: var(--content-ink);
}
