/* ---------------------------------------------------------------------
   Telemetry Tunnel — chart-room design system.

   Direction: this reads like a page torn from a chart table, not a SaaS
   dashboard. Parchment paper, chart ink, a hazard-mark red used sparingly,
   a brass instrument accent for rules and borders. Numeric telemetry is
   set in a monospace face with tabular figures so live-updating values
   don't jitter the layout as digits change width.

   Multi-vessel UI redesign (WebClient_MultiVessel_UI_Context.md): the page
   is map-first now -- no entry form, no single global vessel. Vessels are
   added via the Add Vessel dialog and stack as compact collapsible blocks
   in the readpane.
   --------------------------------------------------------------------- */

@import url('https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,600&family=IBM+Plex+Sans:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500;600&display=swap');

:root {
  --paper: #E8E4D3;
  --paper-raised: #EFEBDD;
  --sea: #C9DDD6;
  --ink: #16232E;
  --ink-soft: #4A5D68;
  --hazard: #B8391F;
  --brass: #8B6F3E;

  --font-display: 'Fraunces', Georgia, serif;
  --font-body: 'IBM Plex Sans', system-ui, sans-serif;
  --font-mono: 'IBM Plex Mono', ui-monospace, monospace;

  color-scheme: light;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  -webkit-font-smoothing: antialiased;
}

body {
  background-image:
    linear-gradient(var(--paper), var(--paper)),
    repeating-linear-gradient(0deg, rgba(139,111,62,0.05) 0px, rgba(139,111,62,0.05) 1px, transparent 1px, transparent 32px);
  min-height: 100vh;
}

a { color: inherit; }

::selection { background: var(--hazard); color: var(--paper); }

:focus-visible {
  outline: 2px solid var(--hazard);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    transition-duration: 0.001ms !important;
  }
}

/* ---------- shared chrome ---------- */

.rule {
  border: none;
  border-top: 1px dashed var(--brass);
  opacity: 0.6;
  margin: 0;
}

.eyebrow {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.compass {
  display: inline-block;
  width: 1em;
  height: 1em;
  vertical-align: -0.15em;
}

.compass-spin {
  animation: spin 3.6s linear infinite;
  transform-origin: 50% 50%;
}

@keyframes spin {
  to { transform: rotate(360deg); }
}

button.primary {
  font-family: var(--font-body);
  font-weight: 600;
  font-size: 0.95rem;
  letter-spacing: 0.01em;
  padding: 10px 16px;
  background: var(--ink);
  color: var(--paper);
  border: 1px solid var(--ink);
  cursor: pointer;
  transition: background 120ms ease, transform 80ms ease, opacity 120ms ease;
}
button.primary:hover { background: #223243; }
button.primary:active { transform: translateY(1px); }
button.primary:disabled { opacity: 0.5; cursor: default; }

button.primary.small {
  padding: 7px 14px;
  font-size: 0.85rem;
}

button.secondary {
  font-family: var(--font-body);
  font-size: 0.88rem;
  padding: 9px 14px;
  background: transparent;
  color: var(--ink-soft);
  border: 1px solid var(--brass);
  cursor: pointer;
}
button.secondary:hover { border-color: var(--hazard); color: var(--hazard); }

.field { margin-bottom: 16px; }

.field label {
  display: block;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin-bottom: 6px;
}

.field input {
  width: 100%;
  font-family: var(--font-mono);
  font-size: 0.95rem;
  padding: 10px 12px;
  background: #fff;
  border: 1px solid var(--brass);
  color: var(--ink);
}

.field input:focus { border-color: var(--hazard); }

.field-hint {
  font-size: 0.78rem;
  color: var(--ink-soft);
  margin-top: 5px;
}

.form-error {
  font-family: var(--font-mono);
  font-size: 0.82rem;
  color: var(--hazard);
  margin-top: 14px;
  line-height: 1.5;
  display: none;
}
.form-error.visible { display: block; }

/* ---------- Add Vessel dialog ---------- */

#add-vessel-dialog {
  border: 1px solid var(--brass);
  background: var(--paper);
  padding: 0;
  width: 100%;
  max-width: 440px;
  box-shadow: 0 1px 0 rgba(22,35,46,0.06), 0 8px 24px rgba(22,35,46,0.18);
}
#add-vessel-dialog::backdrop {
  background: rgba(22,35,46,0.45);
}
#add-vessel-form {
  padding: 28px 28px 24px;
  position: relative;
}
#add-vessel-form h1 {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.4rem;
  margin: 0 0 4px;
}
#add-vessel-form .lede {
  color: var(--ink-soft);
  font-size: 0.9rem;
  line-height: 1.5;
  margin: 0 0 20px;
}
.dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 8px;
}

/* ---------- trackbar ---------- */

#view-track {
  display: none;
  min-height: 100vh;
  flex-direction: column;
}
#view-track.active { display: flex; }

.trackbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px 16px;
  padding: 14px 20px;
  border-bottom: 1px solid var(--brass);
  background: var(--paper);
}

.trackbar-title {
  font-family: var(--font-display);
  font-size: 1.3rem;
  font-weight: 600;
  margin: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.trackmain {
  flex: 1;
  display: flex;
  min-height: 0;
}

@media (max-width: 860px) {
  .trackmain { flex-direction: column; }
}

.chartpane {
  flex: 1 1 62%;
  position: relative;
  min-height: 320px;
  background: var(--sea);
  border-right: 1px solid var(--brass);
}

@media (max-width: 860px) {
  .chartpane { border-right: none; border-bottom: 1px solid var(--brass); }
}

#map { position: absolute; inset: 0; }

.chart-watermark {
  position: absolute;
  right: 14px;
  bottom: 14px;
  z-index: 400;
  opacity: 0.55;
  pointer-events: none;
}
.chart-watermark svg { width: 40px; height: 40px; }

/* ---------- readpane: vessel list ---------- */

.readpane {
  flex: 1 1 38%;
  max-width: 400px;
  padding: 16px 16px 24px;
  overflow-y: auto;
  background: var(--paper);
}

@media (max-width: 860px) {
  .readpane { max-width: none; }
}

.readgroup-note {
  font-family: var(--font-body);
  font-size: 0.78rem;
  font-style: italic;
  color: var(--ink-soft);
  margin: 0 0 14px;
  line-height: 1.4;
}

#readpane-empty.hidden { display: none; }

#vessel-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

/* One vessel block: a native <details> so the whole block collapses as a
   single unit (§1.5) -- no per-panel nested toggles. */
.vessel {
  border: 1px solid rgba(139,111,62,0.5);
  background: var(--paper-raised);
}

.vessel > summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  user-select: none;
}
.vessel > summary::-webkit-details-marker { display: none; }
.vessel > summary:focus-visible { outline-offset: -2px; }

.v-swatch { flex: none; width: 20px; height: 20px; }
.v-swatch svg { width: 100%; height: 100%; filter: drop-shadow(0 1px 1px rgba(22,35,46,0.3)); }

.v-name {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1rem;
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: pointer;
}
.v-name:hover { text-decoration: underline; text-underline-offset: 2px; }

.v-status {
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--ink-soft);
  flex: none;
}
.v-status-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--ink-soft); }
.v-status[data-state="live"] .v-status-dot {
  background: var(--hazard);
  animation: pulse 1.8s ease-in-out infinite;
}
.v-status[data-state="connecting"] .v-status-dot,
.v-status[data-state="reconnecting"] .v-status-dot { background: var(--brass); }
@keyframes pulse {
  0%   { box-shadow: 0 0 0 0 rgba(184,57,31,0.45); }
  70%  { box-shadow: 0 0 0 6px rgba(184,57,31,0); }
  100% { box-shadow: 0 0 0 0 rgba(184,57,31,0); }
}

.v-remove {
  flex: none;
  width: 22px;
  height: 22px;
  border: 1px solid transparent;
  background: transparent;
  color: var(--ink-soft);
  font-family: var(--font-mono);
  font-size: 0.9rem;
  line-height: 1;
  cursor: pointer;
  border-radius: 2px;
}
.v-remove:hover { border-color: var(--brass); color: var(--hazard); }

.v-chevron {
  flex: none;
  width: 10px;
  height: 10px;
  border-right: 1.5px solid var(--ink-soft);
  border-bottom: 1.5px solid var(--ink-soft);
  transform: rotate(-45deg);
  transition: transform 140ms ease;
  margin: 0 2px 0 0;
}
.vessel[open] > summary .v-chevron { transform: rotate(45deg); }

.v-body {
  padding: 2px 12px 12px;
  border-top: 1px dashed rgba(139,111,62,0.5);
  display: flex;
  flex-direction: column;
}

/* "No publisher" state (WebClient_Fixes_Context.md item 2): the block
   stays up showing its last-known readings rather than disappearing,
   visibly dimmed as a whole -- distinct from an individual field's own
   .stale treatment, which means "this one field specifically was never
   received." Here every reading is suspect because nothing new can arrive
   right now. */
.vessel.no-publisher .v-line-value {
  opacity: 0.5;
}

.v-banner {
  font-family: var(--font-mono);
  font-size: 0.76rem;
  padding: 8px 10px;
  border: 1px solid var(--hazard);
  color: var(--hazard);
  background: rgba(184,57,31,0.06);
  margin: 10px 0 0;
  line-height: 1.5;
  display: none;
}
.v-banner.visible { display: block; }

/* Uniform one-line-per-field rows: Position, Depth, Course, Speed, Wind,
   Track, Viewers all use this same label+value shape (§1.3, revised to
   drop the earlier separate section-title lines). */
.v-line {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 7px 0;
  border-bottom: 1px solid rgba(139,111,62,0.25);
}
.v-line:last-child { border-bottom: none; }
.v-line-label {
  font-family: var(--font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-soft);
  flex: none;
  width: 54px;
}
.v-line-value {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 0.85rem;
  color: var(--ink);
}
.v-line-value .unit { font-size: 0.7rem; color: var(--ink-soft); margin-left: 2px; }
.v-line-value.stale { color: var(--ink-soft); }
.v-line-value.multi { display: flex; align-items: baseline; gap: 16px; flex-wrap: wrap; }

/* Wind's value side (.wind-row-content) is a multi-line flex container,
   not one line of text -- baseline alignment falls back to its bottom
   edge, pinning the "Wind" label to the bottom of the row instead of the
   top. Scoped to just this row so every other, correctly-behaving
   single-line row keeps its baseline alignment. */
.v-line:has(> .wind-row-content) {
  align-items: flex-start;
}

.subfield-label {
  font-family: var(--font-mono);
  font-size: 0.6rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin-right: 4px;
}

/* Wind row (§1.7): label lines up with the others; content is dial +
   stacked AWS, TWD (calc.), TWS (calc.) fields, beside the dial, not
   below it. */
.wind-row-content {
  display: flex;
  flex-wrap: nowrap;
  align-items: center;
  gap: 14px;
  flex: 1;
  min-width: 0;
}
.wind-dial { flex: none; }
.wind-stack {
  display: flex;
  flex-direction: column;
  gap: 6px;
  flex: none;
  white-space: nowrap;
}
.wind-stack-field { display: flex; align-items: baseline; gap: 0; }
.wind-stack-field .v-line-label { width: 38px; }
.wind-stack-field.calculated .v-line-value { color: var(--ink-soft); }

/* Last-updated line's awaiting-data state: AWAITING_DATA_TEXT is a full
   sentence, not a short value like every other line in this block, so it
   spans the row instead of sitting beside the "Updated" label. */
.v-line-value[data-role="last-update"].awaiting {
  display: block;
  width: 100%;
  color: var(--ink-soft);
}
.v-line:has(.v-line-value[data-role="last-update"].awaiting) .v-line-label {
  display: none;
}

.wind-arrow-group { transition: opacity 150ms ease; }
.wind-row-content.no-wind .wind-arrow-group { opacity: 0; }

/* Ring/ticks stay on the shared theme color -- they're the dial's frame, not
   per-vessel identity. The boat glyph and arrow's *fill*/*stroke* color is
   set per-vessel via inline style in windDialSVG(v.color) (app.js) so each
   dial matches that vessel's own chart-marker color; the rules below are
   only the fallback if that inline style is ever absent, plus the
   non-color properties (width, outline) inline style doesn't touch. */
.wind-ring { fill: none; stroke: var(--brass); stroke-opacity: 0.5; stroke-width: 1; stroke-dasharray: 2 3; }
.wind-tick { stroke: var(--brass); }
.wind-arrow-shaft { stroke: var(--brass); stroke-width: 3; stroke-linecap: round; }
.wind-arrowhead { fill: var(--brass); }
.wind-boat { fill: var(--hazard); stroke: var(--ink); stroke-width: 1.3; }

/* Track control (§1.8): show/hide toggle + a separate, deliberately
   distinct Erase button. */
.toggle {
  position: relative;
  flex: none;
  width: 32px;
  height: 17px;
  border: 1px solid var(--brass);
  background: var(--paper);
  cursor: pointer;
  padding: 0;
}
.toggle::after {
  content: '';
  position: absolute;
  top: 1px; left: 1px;
  width: 13px; height: 13px;
  background: var(--ink-soft);
  transition: transform 120ms ease, background 120ms ease;
}
.toggle[aria-pressed="true"] { background: rgba(184,57,31,0.08); border-color: var(--hazard); }
.toggle[aria-pressed="true"]::after { transform: translateX(15px); background: var(--hazard); }

.btn-ghost {
  font-family: var(--font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  background: transparent;
  border: 1px solid var(--brass);
  color: var(--ink-soft);
  padding: 4px 9px;
  cursor: pointer;
}
.btn-ghost:hover { border-color: var(--hazard); color: var(--hazard); }

.vessel-icon,
.v-swatch svg {
  filter: drop-shadow(0 1px 2px rgba(22,35,46,0.35));
}

/* ---------- chart tile-provider picker ---------- */

/* Lives in .trackbar now, not floating over the chart -- the header
   already supplies its own background/border, so this lays the two
   controls out inline instead of the stacked floating-card treatment. */
.chart-picker {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 16px;
}

.chart-picker-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.chart-picker-label {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-soft);
  flex: none;
}

.chart-picker select {
  flex: 1;
  min-width: 0;
  font-family: var(--font-body);
  font-size: 0.82rem;
  padding: 5px 6px;
  background: #fff;
  border: 1px solid var(--brass);
  color: var(--ink);
}

.chart-picker select:focus {
  border-color: var(--hazard);
}

.chart-picker-overlay {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 0.78rem;
  color: var(--ink-soft);
  cursor: pointer;
}

/* MapLibre's own controls (zoom, attribution) get the same chart-room
   treatment rather than their default white-on-white look. */
.maplibregl-ctrl-group {
  background: var(--paper);
  border: 1px solid var(--brass);
  box-shadow: none;
  border-radius: 0;
}

.maplibregl-ctrl-attrib {
  background: rgba(232,228,211,0.85);
  font-family: var(--font-body);
  font-size: 0.68rem;
  color: var(--ink-soft);
}

.maplibregl-ctrl-attrib a {
  color: var(--ink-soft);
}
