/* ==========================================================================
   LinZee - glass helm display
   Sunseeker 95. Built for an iPad on the boat first, a desktop browser second.
   No CDN, no webfonts, no images: this has to work at anchor with no signal.
   ========================================================================== */

/* --------------------------------------------------------------- tokens */
:root {
  /* night (default) - a helm display after dark */
  --abyss:      #04080f;
  --deep:       #06121f;
  --hull:       #0b1e31;

  --glass-top:  rgba(255,255,255,.095);
  --glass-bot:  rgba(255,255,255,.032);
  --edge:       rgba(255,255,255,.13);
  --edge-hi:    rgba(255,255,255,.38);
  --well:       rgba(0,0,0,.30);

  --ink:        #e9f1fa;
  --ink-2:      #9db3cb;
  --ink-3:      #637993;

  --cyan:       #4fd6ff;
  --teal:       #2fe3c4;
  --amber:      #ffb44d;
  --rose:       #ff5d73;
  --lime:       #82e87f;
  --violet:     #a98cff;

  --blur:       22px;
  --r-lg:       20px;
  --r-md:       14px;
  --r-sm:       10px;

  --shadow:     0 22px 44px -24px rgba(0,0,0,.85), 0 2px 6px -2px rgba(0,0,0,.5);
  --ease:       cubic-bezier(.22,.61,.36,1);

  /* Switch tokens. Everything that used to need a [data-theme] descendant
     selector is a token instead, so the palette lives in exactly one place per
     theme and no rule has to know which theme is active. */
  --blob-opacity: .5;
  --blob-blur:    70px;
  --pct-shadow:   0 1px 6px rgba(0,0,0,.55);
  --bit-off:      rgba(255,255,255,.07);
  --panel-solid:  #0d2033f2;
  --modal-ground: #0d2033;

  --pad:        clamp(14px, 2.2vw, 22px);
  --safe-t:     env(safe-area-inset-top, 0px);
  --safe-b:     env(safe-area-inset-bottom, 0px);
  --safe-l:     env(safe-area-inset-left, 0px);
  --safe-r:     env(safe-area-inset-right, 0px);
}

/* Light mode.
   Two things drive it. A dark helm screen is unreadable on a flybridge in full
   sun, and the iPad may simply be set to light. So the default is to follow the
   system, and the button below overrides it in either direction.

   The palette is written twice on purpose: once for "system says light and the
   user has not forced dark", once for "user chose light". A single selector
   list cannot express both, and giving a colour its only definition inside a
   media query means an explicit choice can never win. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
  --abyss:      #e7eef6;
  --deep:       #f4f8fc;
  --hull:       #d3e0ee;

  --glass-top:  rgba(255,255,255,.86);
  --glass-bot:  rgba(255,255,255,.58);
  --edge:       rgba(14,38,64,.14);
  --edge-hi:    rgba(255,255,255,.95);
  --well:       rgba(14,38,64,.055);

  --ink:        #0b1e33;
  --ink-2:      #3a5877;
  --ink-3:      #6b87a3;

  --cyan:       #0071ad;
  --teal:       #017a63;
  --amber:      #98570a;
  --rose:       #c01a35;
  --lime:       #2b7328;
  --violet:     #5334bd;

  --blob-opacity: .34;
  --blob-blur:    86px;
  --pct-shadow:   0 1px 4px rgba(255,255,255,.85);
  --bit-off:      rgba(14,38,64,.10);
  --panel-solid:  #ffffffee;
  --modal-ground: #ffffff;

  --shadow:     0 16px 34px -22px rgba(14,38,64,.40), 0 1px 3px rgba(14,38,64,.09);
  }
}

:root[data-theme="light"] {
  --abyss:      #e7eef6;
  --deep:       #f4f8fc;
  --hull:       #d3e0ee;

  --glass-top:  rgba(255,255,255,.86);
  --glass-bot:  rgba(255,255,255,.58);
  --edge:       rgba(14,38,64,.14);
  --edge-hi:    rgba(255,255,255,.95);
  --well:       rgba(14,38,64,.055);

  --ink:        #0b1e33;
  --ink-2:      #3a5877;
  --ink-3:      #6b87a3;

  --cyan:       #0071ad;
  --teal:       #017a63;
  --amber:      #98570a;
  --rose:       #c01a35;
  --lime:       #2b7328;
  --violet:     #5334bd;

  --blob-opacity: .34;
  --blob-blur:    86px;
  --pct-shadow:   0 1px 4px rgba(255,255,255,.85);
  --bit-off:      rgba(14,38,64,.10);
  --panel-solid:  #ffffffee;
  --modal-ground: #ffffff;

  --shadow:     0 16px 34px -22px rgba(14,38,64,.40), 0 1px 3px rgba(14,38,64,.09);
}

* { box-sizing: border-box; }

/* Any rule that sets `display` outranks the UA stylesheet's [hidden]{display:none},
   so the alarm and offline bars stayed visible with `hidden` set. Anything toggled
   by the hidden attribute needs this, and it is easy to reintroduce without it. */
[hidden] { display: none !important; }

html {
  margin: 0;
  /* The base wash lives on the root, so there is never an unpainted region no
     matter how the document scrolls. */
  background: var(--abyss);
  overflow-x: clip;
}

body {
  margin: 0;
  min-height: 100vh;
  position: relative;          /* anchors .ambient - see the note there */
}

body {
  font: 15px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
        "Helvetica Neue", Arial, sans-serif;
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  -webkit-text-size-adjust: 100%;
  overscroll-behavior-y: none;             /* no rubber-band on iPad */
  touch-action: manipulation;              /* kills the 300ms double-tap zoom */
  padding: calc(var(--safe-t) + 10px) calc(var(--safe-r) + var(--pad))
           calc(var(--safe-b) + 10px)      calc(var(--safe-l) + var(--pad));
  transition: background .5s var(--ease), color .5s var(--ease);
}

/* ------------------------------------------------------------- ambient */
/* Absolute, not fixed, and that is deliberate. iOS Safari does not reliably
   blur a position:fixed backdrop through -webkit-backdrop-filter: the panels
   can render with no backdrop at all, and the layer fails to repaint during
   scroll. Since the whole page is under two viewports tall, anchoring this to
   the document instead looks the same and removes the hazard on the one
   platform this has to be perfect on.
   Transform-only animation, so the blurred layers rasterise once and then ride
   the compositor. */
.ambient {
  position: absolute;
  inset: 0;
  z-index: -1;
  overflow: hidden;            /* clips the blobs without a page-level overflow */
  pointer-events: none;
  border-radius: inherit;
  background:
    radial-gradient(ellipse 120% 60% at 50% 0%, var(--hull) 0%, transparent 62%),
    linear-gradient(180deg, var(--deep) 0%, var(--abyss) 60%);
}

.blob {
  position: absolute;
  width: 55vmax;
  height: 55vmax;
  border-radius: 50%;
  filter: blur(var(--blob-blur));
  opacity: var(--blob-opacity);
  will-change: transform;
}
.blob-a { top:  4%; left:  -6%; background: radial-gradient(circle, rgba(79,214,255,.34), transparent 65%); animation: drift-a 42s var(--ease) infinite alternate; }
.blob-b { top: 34%; right: -12%; background: radial-gradient(circle, rgba(47,227,196,.24), transparent 65%); animation: drift-b 55s var(--ease) infinite alternate; }
.blob-c { bottom:-8%; left: 26%; background: radial-gradient(circle, rgba(169,140,255,.20), transparent 65%); animation: drift-c 68s var(--ease) infinite alternate; }

@keyframes drift-a { to { transform: translate3d(14vmax,  9vmax, 0) scale(1.14); } }
@keyframes drift-b { to { transform: translate3d(-13vmax, 12vmax, 0) scale(1.08); } }
@keyframes drift-c { to { transform: translate3d(9vmax, -11vmax, 0) scale(1.18); } }

/* A whisper of grain stops the big gradients from banding on a good screen. */
.grain {
  position: absolute;
  inset: 0;
  opacity: .035;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* --------------------------------------------------------------- glass */
/* The recipe: a top-lit translucent gradient, a blurred backdrop, a hairline
   border, and a 1px inset highlight on the top edge only. That inset line is
   what sells it - it reads as the lit bevel of a real pane. */
.panel,
.topbar,
.pill,
.icon-btn,
.bar,
/* Everything added later belongs to the same recipe. Left on a flat --well
   these read as grey boxes sitting on glass, which is the exact inconsistency
   worth avoiding. */
.alert-card,
.user-card,
.pos-cell,
.leg-row,
.wiz-rail,
.review,
.cal,
.map-wrap,
.profile-menu,
.pick-list {
  background: linear-gradient(158deg, var(--glass-top) 0%, var(--glass-bot) 100%);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(155%);
  backdrop-filter: blur(var(--blur)) saturate(155%);
  border: 1px solid var(--edge);
  box-shadow: var(--shadow), inset 0 1px 0 var(--edge-hi);
}

/* ---------------------------------------------------------------- shell */
.shell { display: flex; gap: 16px; align-items: flex-start; }
.content { flex: 1 1 auto; min-width: 0; }

/* ----------------------------------------------------------------- rail */
.rail {
  flex: 0 0 208px;
  position: sticky;
  top: calc(var(--safe-t) + 4px);
  padding: 16px 12px;
  border-radius: var(--r-lg);
  background: linear-gradient(158deg, var(--glass-top) 0%, var(--glass-bot) 100%);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(155%);
  backdrop-filter: blur(var(--blur)) saturate(155%);
  border: 1px solid var(--edge);
  box-shadow: var(--shadow), inset 0 1px 0 var(--edge-hi);
}
.rail .brand { padding: 4px 8px 18px; }

.nav { list-style: none; margin: 0; padding: 0; display: grid; gap: 4px; }
.nav-item {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 11px 12px;          /* 44px tall - an iPad tap target */
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--ink-2);
  font: inherit;
  font-size: 14px;
  text-align: left;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background .2s, color .2s;
}
.nav-item svg { width: 19px; height: 19px; flex: none; }
.nav-item span { flex: 1 1 auto; }
.nav-item:active { transform: scale(.98); }
.nav-item.is-on { background: var(--well); color: var(--ink); font-weight: 600; }
.nav-item.is-on svg { color: var(--cyan); }

.nav-badge {
  font-style: normal;
  font-size: 11px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  padding: 2px 7px;
  border-radius: 999px;
  background: var(--rose);
  color: #fff;
}

.view-title {
  margin: 0;
  font-size: clamp(16px, 2.2vw, 20px);
  font-weight: 600;
  letter-spacing: .01em;
  color: var(--ink);
  text-transform: none;
}

/* -------------------------------------------------------------- topbar */
.topbar {
  position: sticky;
  top: calc(var(--safe-t) + 4px);
  z-index: 20;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px;
  padding: 11px 16px;
  border-radius: var(--r-lg);
  margin-bottom: 16px;
}

.brand { display: flex; align-items: center; gap: 13px; min-width: 0; }
.burgee { width: 30px; height: 30px; flex: none; color: var(--cyan); }

.brand-text { min-width: 0; }
.brand-text h1 {
  margin: 0;
  font-size: 20px;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  line-height: 1.1;
}
.brand-text p {
  margin: 1px 0 0;
  font-size: 10.5px;
  letter-spacing: .19em;
  text-transform: uppercase;
  color: var(--ink-3);
  white-space: nowrap;
}

.topbar-right { display: flex; align-items: center; gap: 10px; flex: none; }

/* ---------------------------------------------------------- status pill */
.pill {
  display: inline-flex;
  align-items: baseline;
  gap: 8px;
  padding: 7px 13px;
  border-radius: 999px;
  font-size: 12.5px;
  white-space: nowrap;
}
.pill .dot {
  width: 8px; height: 8px;
  border-radius: 50%;
  align-self: center;
  background: var(--ink-3);
  box-shadow: 0 0 0 0 currentColor;
}
.pill-label { font-weight: 600; letter-spacing: .02em; }
.pill-meta  { color: var(--ink-3); font-size: 11px; font-variant-numeric: tabular-nums; }

.pill-live .dot  { background: var(--lime); animation: beat 2.4s ease-in-out infinite; }
.pill-live       { color: var(--lime); }
.pill-live .pill-label { color: var(--ink); }
.pill-stale .dot { background: var(--amber); }
.pill-stale      { color: var(--amber); }
.pill-stale .pill-label { color: var(--ink); }
.pill-dead .dot  { background: var(--rose); }
.pill-dead       { color: var(--rose); }
.pill-dead .pill-label { color: var(--ink); }

@keyframes beat {
  0%,100% { box-shadow: 0 0 0 0 rgba(130,232,127,.55); }
  50%     { box-shadow: 0 0 0 6px rgba(130,232,127,0); }
}

/* ------------------------------------------------------------ icon btn */
.icon-btn {
  width: 42px; height: 42px;           /* >= 44px touch target with the border */
  flex: none;
  display: grid;
  place-items: center;
  border-radius: 50%;
  color: var(--ink-2);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: color .25s, transform .18s var(--ease);
}
.icon-btn:active { transform: scale(.93); }
.icon-btn svg { width: 20px; height: 20px; }
/* The icon reports the MODE the button is in - auto, light or dark - not the
   colour currently on screen. Showing a sun while in auto would suggest a
   choice the user has not made. */
.icon-btn svg { display: none; }
.icon-btn[data-mode="auto"]  .ico-auto  { display: block; }
.icon-btn[data-mode="light"] .ico-day   { display: block; }
.icon-btn[data-mode="dark"]  .ico-night { display: block; }

/* ---------------------------------------------------------------- bars */
.bar {
  border-radius: var(--r-md);
  padding: 12px 16px;
  margin-bottom: 14px;
  font-size: 13.5px;
  color: var(--ink-2);
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.bar strong { color: var(--ink); font-weight: 600; }
.bar-warn  { border-color: rgba(255,180,77,.42); }
.bar-alarm { border-color: rgba(255,93,115,.5); animation: alarm-glow 2s ease-in-out infinite; }
.bar { flex-direction: column; align-items: stretch; }
.bar-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.bar-sub { color: var(--ink-3); }
.alarm-list {
  list-style: none;
  margin: 10px 0 0;
  padding: 0;
  display: grid;
  gap: 5px;
}
.alarm-list li {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 8px 12px;
  border-radius: var(--r-sm);
  background: var(--well);
  font-size: 13px;
  color: var(--ink);
}
.alarm-list .since { color: var(--ink-3); font-size: 11.5px; white-space: nowrap; font-variant-numeric: tabular-nums; }

.bar-count {
  font-size: 20px; font-weight: 700; color: var(--rose);
  font-variant-numeric: tabular-nums;
}
@keyframes alarm-glow {
  0%,100% { box-shadow: var(--shadow), inset 0 1px 0 var(--edge-hi), 0 0 0 0 rgba(255,93,115,.30); }
  50%     { box-shadow: var(--shadow), inset 0 1px 0 var(--edge-hi), 0 0 22px 2px rgba(255,93,115,.22); }
}

/* ---------------------------------------------------------------- views */
.view[hidden] { display: none !important; }

/* ---------------------------------------------------------------- deck */
.deck {
  display: grid;
  gap: 16px;
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
}

.panel {
  border-radius: var(--r-lg);
  padding: var(--pad);
  min-width: 0;
}

.panel-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 16px;
}
.panel h2 {
  margin: 0;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: .17em;
  text-transform: uppercase;
  color: var(--ink-2);
}
.panel-note {
  font-size: 11px;
  color: var(--ink-3);
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------- numbers */
/* tabular-nums is not cosmetic here: without it every digit change reflows the
   line and the whole display twitches once a second. */
.num {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
  letter-spacing: -.01em;
  transition: color .5s var(--ease);
}
.num-xl {
  font-size: clamp(38px, 6.4vw, 54px);
  font-weight: 250;
  line-height: 1;
  letter-spacing: -.03em;
}
.num-dim { color: var(--ink-2); }
.unit { font-size: 12px; color: var(--ink-3); margin-left: 3px; }

/* a change flash, one beat, never looping */
.num.bump { animation: bump .75s var(--ease); }
@keyframes bump {
  0%   { color: var(--cyan); text-shadow: 0 0 18px rgba(79,214,255,.5); }
  100% { color: inherit;     text-shadow: none; }
}

.hero-metric { margin-bottom: 4px; }
.hero-value { display: flex; align-items: flex-end; gap: 8px; flex-wrap: wrap; }
.hero-value .unit { font-size: 14px; padding-bottom: 6px; }
.hero-label {
  margin: 6px 0 0;
  font-size: 12px;
  color: var(--ink-3);
}
.hero-delta {
  font-size: 12px;
  font-weight: 600;
  padding: 3px 9px;
  border-radius: 999px;
  background: var(--well);
  color: var(--amber);
  margin-bottom: 8px;
  font-variant-numeric: tabular-nums;
}

.spark { display: block; width: 100%; height: 46px; margin-top: 12px; opacity: .95; }

/* --------------------------------------------------------------- tanks */
.tank-row {
  display: grid;
  gap: 12px;
  /* Fixed count, not auto-fit: auto-fit dropped to 3 + 1 on a phone, which
     reads as a bug rather than a layout. */
  grid-template-columns: repeat(5, minmax(0, 1fr));
}

.tank { text-align: center; min-width: 0; }

.tube {
  position: relative;
  /* Capped and centred: given a full-width row on iPad portrait each tube would
     stretch to ~170px and read as a box rather than a tube. */
  max-width: 124px;
  margin: 0 auto;
  height: clamp(132px, 19vh, 178px);
  border-radius: var(--r-md);
  overflow: hidden;
  background: var(--well);
  border: 1px solid var(--edge);
  box-shadow: inset 0 2px 10px rgba(0,0,0,.35);
}

.tube-fill {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 0;
  transition: height 1.1s var(--ease), background .6s linear;
  background: linear-gradient(180deg, var(--liq, var(--cyan)) 0%,
                                      color-mix(in srgb, var(--liq, var(--cyan)) 62%, #000) 100%);
}
/* meniscus - the lit surface of the liquid */
.tube-fill::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 2px;
  background: #fff;
  opacity: .55;
}
/* the specular streak that makes it read as a glass tube and not a bar chart */
.tube::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(102deg,
    rgba(255,255,255,.16) 0 9%,
    rgba(255,255,255,.05) 9% 15%,
    transparent 15% 84%,
    rgba(255,255,255,.07) 84% 100%);
}
/* quarter marks */
.tube-ticks { position: absolute; inset: 0; pointer-events: none; }
.tube-ticks i {
  position: absolute; left: 0; right: 0;
  height: 1px;
  background: var(--edge);
  opacity: .5;
}
.tube-ticks i:nth-child(1) { bottom: 25%; }
.tube-ticks i:nth-child(2) { bottom: 50%; }
.tube-ticks i:nth-child(3) { bottom: 75%; }

.tube-pct {
  position: absolute;
  left: 0; right: 0; top: 9px;
  font-size: 15px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  text-shadow: var(--pct-shadow);
}

.tank-name {
  margin: 9px 0 2px;
  font-size: 11.5px;
  color: var(--ink-2);
  line-height: 1.25;
}
.tank-abs { font-size: 11px; color: var(--ink-3); font-variant-numeric: tabular-nums; }

.tank.warn  .tube { border-color: rgba(255,180,77,.55); }
.tank.crit  .tube { border-color: rgba(255,93,115,.65); }
.tank.crit  .tube-pct { color: var(--rose); }
.pct-sign { font-size: 10px; color: var(--ink-3); margin-left: 1px; }

/* ------------------------------------------------------------------ ac */
.leg-grid { display: grid; gap: 8px; margin-top: 16px; }
.leg {
  display: grid;
  grid-template-columns: 52px 1fr auto 1fr auto;
  align-items: baseline;
  gap: 6px;
  padding: 8px 11px;
  border-radius: var(--r-sm);
  background: var(--well);
  font-size: 14px;
}
.leg-tag {
  font-size: 10.5px;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.balance { margin-top: 14px; font-size: 11.5px; color: var(--ink-3); }
.balance-track {
  height: 4px;
  border-radius: 999px;
  background: var(--well);
  overflow: hidden;
  margin-bottom: 7px;
}
.balance-track i {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 999px;
  background: var(--amber);
  transition: width .9s var(--ease), background .4s;
}

/* ------------------------------------------------------------------ dc */
.bank-list { display: grid; gap: 9px; }
.bank {
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: center;
  gap: 11px;
  padding: 11px 13px;
  border-radius: var(--r-sm);
  background: var(--well);
}
.bank-name { font-size: 12.5px; color: var(--ink-2); min-width: 0; }
.bank-v { font-size: 19px; font-variant-numeric: tabular-nums; font-weight: 500; }
.bank-a {
  font-size: 12px;
  font-variant-numeric: tabular-nums;
  padding: 3px 9px;
  border-radius: 999px;
  white-space: nowrap;
  min-width: 74px;
  text-align: center;
}
.bank-a.chg { color: var(--lime); background: rgba(130,232,127,.13); }
.bank-a.dis { color: var(--amber); background: rgba(255,180,77,.13); }
.bank-a.idle { color: var(--ink-3); background: var(--well); }

/* ------------------------------------------------------------- gensets */
.gen-row { display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(112px, 1fr)); }
.gen { text-align: center; min-width: 0; }
.gen-ring { position: relative; width: 100%; max-width: 116px; margin: 0 auto; }
.gen-ring svg { width: 100%; height: auto; display: block; transform: rotate(-90deg); }
.gen-ring circle { fill: none; stroke-width: 7; stroke-linecap: round; }
.gen-ring .track { stroke: var(--well); }
.gen-ring .arc {
  stroke: var(--teal);
  transition: stroke-dashoffset 1.1s var(--ease), stroke .5s;
}
.gen.due .arc  { stroke: var(--amber); }
.gen.over .arc { stroke: var(--rose); }
.gen-centre {
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  gap: 1px;
}
.gen-centre b {
  font-size: 19px;
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.gen-centre span { font-size: 9.5px; color: var(--ink-3); letter-spacing: .09em; text-transform: uppercase; }
.gen-name { margin: 9px 0 1px; font-size: 12px; color: var(--ink-2); }
.gen-total { font-size: 11px; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.gen-state {
  display: flex; align-items: center; justify-content: center; gap: 7px;
  font-size: 11px; color: var(--ink-3); margin-bottom: 9px;
  font-variant-numeric: tabular-nums;
}
.gen-state b {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--ink-3); opacity: .4;
  transition: background .3s, opacity .3s, box-shadow .3s;
}
.gen-state.on { color: var(--lime); }
.gen-state.on b { background: var(--lime); opacity: 1; box-shadow: 0 0 8px rgba(130,232,127,.6); }

.gen-load { margin-top: 9px; font-variant-numeric: tabular-nums; }
.gen-load .num { font-size: 20px; font-weight: 500; }
.gen-load .unit { font-size: 11px; }
.gen-load .gen-kw { display: block; font-size: 10.5px; color: var(--ink-3); margin-top: 2px; }
.gen-load.off .num { color: var(--ink-3); }
.gen-load.warn .num { color: var(--amber); }
.gen-load.crit .num { color: var(--rose); }

.gen-batt {
  margin-top: 7px;
  font-size: 13px;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
  padding: 4px 8px;
  border-radius: 999px;
  background: var(--well);
  display: inline-block;
}
.gen-batt .unit { font-size: 10px; }
.gen-batt.warn { color: var(--amber); background: rgba(255,180,77,.14); }
.gen-batt.crit { color: var(--rose);  background: rgba(255,93,115,.16); }

/* ------------------------------------------------------------ mini list */
.mini-list { list-style: none; margin: 16px 0 0; padding: 0; display: grid; gap: 5px; }
.mini-list li {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  font-size: 12.5px;
  color: var(--ink-2);
  padding: 7px 11px;
  border-radius: var(--r-sm);
  background: var(--well);
}
.mini-list b { color: var(--ink); font-weight: 600; font-variant-numeric: tabular-nums; }

/* --------------------------------------------------------- disclosures */
.panel details summary {
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
  -webkit-tap-highlight-color: transparent;
}
.panel details summary::-webkit-details-marker { display: none; }
.panel details summary::after {
  content: "";
  width: 7px; height: 7px;
  border-right: 1.6px solid var(--ink-3);
  border-bottom: 1.6px solid var(--ink-3);
  transform: rotate(45deg);
  transition: transform .3s var(--ease);
  flex: none;
  align-self: center;
}
.panel details[open] summary::after { transform: rotate(-135deg); }
.panel details[open] summary { margin-bottom: 16px; }

/* ------------------------------------------------------------- lamps */
.lamps { display: grid; gap: 9px; grid-template-columns: repeat(auto-fit, minmax(158px, 1fr)); }
.lamp {
  display: flex; align-items: center; gap: 10px;
  padding: 11px 13px;
  border-radius: var(--r-sm);
  background: var(--well);
  font-size: 13px;
  color: var(--ink-2);
  min-width: 0;
}
.lamp b {
  width: 11px; height: 11px; flex: none;
  border-radius: 50%;
  background: var(--ink-3);
  opacity: .35;
  transition: background .3s, opacity .3s, box-shadow .3s;
}
.lamp.on { color: var(--ink); }
.lamp.on b { background: var(--lime); opacity: 1; box-shadow: 0 0 10px rgba(130,232,127,.6); }
.lamp span { min-width: 0; overflow: hidden; text-overflow: ellipsis; }

/* ----------------------------------------------------------- signals */
.how { font-size: 13px; color: var(--ink-2); line-height: 1.6; margin: 0 0 14px; max-width: 66em; }
.how b { color: var(--ink); }
.btn {
  font: inherit; font-size: 13px;
  color: var(--ink-2);
  background: var(--well);
  border: 1px solid var(--edge);
  border-radius: var(--r-sm);
  padding: 9px 15px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.btn:active { transform: scale(.97); }
.chk { display: inline-flex; align-items: center; gap: 8px; font-size: 13px;
       color: var(--ink-2); white-space: nowrap; flex: 0 0 auto; }
.chk input { width: 17px; height: 17px; accent-color: var(--cyan); }

.bits i.tappable { cursor: pointer; }
.bits i.tappable:active { transform: scale(.86); }
.bits i.named { outline: 1px solid var(--teal); outline-offset: 1px; }

.chg-row {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 9px 12px;
  border-radius: var(--r-sm);
  background: var(--well);
  margin-bottom: 6px;
  font-size: 13px;
}
.chg-when { font-variant-numeric: tabular-nums; color: var(--ink-3); font-size: 12px; white-space: nowrap; }
.chg-ref  { font-variant-numeric: tabular-nums; color: var(--ink-2); white-space: nowrap; }
.chg-val  { font-size: 11px; padding: 2px 8px; border-radius: 999px; white-space: nowrap; }
.chg-val.on  { color: var(--lime); background: rgba(130,232,127,.14); }
.chg-val.off { color: var(--ink-3); background: var(--well); }
.chg-name { flex: 1 1 150px; color: var(--ink); min-width: 0; }
.chg-row .btn { margin-left: auto; padding: 6px 12px; font-size: 12px; }
.bits i.ghost { background: transparent; }
.word-named { margin-top: 8px; font-size: 11px; color: var(--teal); line-height: 1.5; }
#sigToken {
  font: inherit; font-size: 13px; color: var(--ink);
  background: var(--well); border: 1px solid var(--edge);
  border-radius: var(--r-sm); padding: 9px 12px; flex: 1 1 220px; min-width: 0;
}
#sigToken:focus { outline: none; border-color: var(--cyan); }

/* ------------------------------------------------------------ alarm log */
.log-controls { display: flex; gap: 9px; margin-bottom: 12px; flex-wrap: wrap; }
.log-controls input, .log-controls select {
  font: inherit;
  font-size: 13px;
  color: var(--ink);
  background: var(--well);
  border: 1px solid var(--edge);
  border-radius: var(--r-sm);
  padding: 9px 12px;          /* keeps the tap target near 40px on iPad */
  min-width: 0;
  -webkit-appearance: none;
  appearance: none;
}
.log-controls input { flex: 1 1 190px; }
.log-controls select { flex: 0 0 auto; padding-right: 30px;
  background-image: linear-gradient(45deg, transparent 50%, var(--ink-3) 50%),
                    linear-gradient(135deg, var(--ink-3) 50%, transparent 50%);
  background-position: right 14px center, right 9px center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat; }
.log-controls input:focus, .log-controls select:focus {
  outline: none; border-color: var(--cyan); }

/* The table scrolls inside its own box; the page never scrolls sideways. */
.log-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
table.log { width: 100%; border-collapse: collapse; font-size: 13px; min-width: 520px; }
button.sort {
  font: inherit;
  font-size: 10.5px;
  letter-spacing: .13em;
  text-transform: uppercase;
  font-weight: 600;
  color: var(--ink-3);
  background: none;
  border: 0;
  padding: 4px 0 8px;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  -webkit-tap-highlight-color: transparent;
}
button.sort:hover { color: var(--ink-2); }
button.sort.is-on { color: var(--cyan); }
button.sort i {
  width: 0; height: 0;
  border-left: 3.5px solid transparent;
  border-right: 3.5px solid transparent;
  opacity: 0;
  transition: opacity .2s;
}
button.sort.is-on i { opacity: 1; }
button.sort.asc  i { border-bottom: 4.5px solid currentColor; }
button.sort.desc i { border-top: 4.5px solid currentColor; }
th.num-col button.sort { justify-content: flex-end; width: 100%; }

.quality {
  margin: 0 0 12px;
  padding: 9px 12px;
  border-radius: var(--r-sm);
  background: var(--well);
  border: 1px solid rgba(255,180,77,.3);
  font-size: 12px;
  color: var(--ink-2);
}

table.log th {
  text-align: left;
  font-size: 10.5px;
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--ink-3);
  font-weight: 600;
  padding: 0 10px 8px;
  border-bottom: 1px solid var(--edge);
  white-space: nowrap;
}
table.log td {
  padding: 9px 10px;
  border-bottom: 1px solid var(--edge);
  color: var(--ink-2);
  vertical-align: top;
}
table.log tr:last-child td { border-bottom: 0; }
table.log td.txt { color: var(--ink); }
.empty-row { padding: 26px 10px !important; color: var(--ink-2); text-align: center; }
.empty-note { display: block; margin-top: 7px; font-size: 12px; color: var(--ink-3);
              line-height: 1.5; max-width: 46em; margin-left: auto; margin-right: auto; }
table.log td.when, table.log td.num-col, th.num-col {
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
th.num-col, td.num-col { text-align: right; }

/* On a phone the four columns cannot fit, and a table you have to drag
   sideways is a table you cannot read. Each event becomes a two-line block
   instead - what happened, then when and for how long. The header row stops
   being column labels and becomes a row of sort controls, so sorting survives
   the reflow. */
@media (max-width: 560px) {
  .log-scroll { overflow-x: visible; }
  table.log { min-width: 0; display: block; font-size: 13px; }

  table.log thead, table.log thead tr { display: block; }
  table.log thead tr {
    display: flex;
    flex-wrap: wrap;
    gap: 0 16px;
    border-bottom: 1px solid var(--edge);
    margin-bottom: 2px;
  }
  table.log th { padding: 0 0 7px; border-bottom: 0; }
  th.num-col button.sort { justify-content: flex-start; width: auto; }

  table.log tbody, table.log tbody tr, table.log tbody td { display: block; }
  table.log tbody tr {
    display: grid;
    grid-template-columns: auto 1fr auto;
    grid-template-areas:
      'text  text  text'
      'state when  dur';
    align-items: baseline;
    gap: 5px 9px;
    padding: 11px 0;
    border-bottom: 1px solid var(--edge);
  }
  table.log tbody tr:last-child { border-bottom: 0; }
  table.log tbody td { padding: 0; border-bottom: 0; }
  table.log td.txt              { grid-area: text; line-height: 1.45; }
  table.log tbody td:nth-child(1) {
    grid-area: when; font-size: 12px; color: var(--ink-3);
    /* A full MM/DD/YYYY h:mm:ss AM stamp beside a long state chip is a hair
       wider than a 375px phone. Let it break at its own space rather than
       push the row sideways. */
    white-space: normal; min-width: 0;
  }
  table.log tbody td:nth-child(2) { grid-area: state; }
  table.log tbody td:nth-child(4) { grid-area: dur; font-size: 12px; }
  /* the empty/no-results cell is the row's only cell - let it span all three */
  table.log td.empty-row { grid-area: 1 / 1 / 2 / -1; }
}
.state {
  display: inline-block;
  font-size: 10.5px;
  letter-spacing: .06em;
  text-transform: uppercase;
  padding: 3px 8px;
  border-radius: 999px;
  background: var(--well);
  white-space: nowrap;
}
.state.Active      { color: var(--rose); background: rgba(255,93,115,.14); }
.state.Acknowledge { color: var(--amber); background: rgba(255,180,77,.14); }
.state.Normal      { color: var(--ink-3); }
.src-derived {
  font-size: 9.5px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--violet); margin-left: 7px;
}

.pager { display: flex; align-items: center; gap: 7px; margin-top: 14px; flex-wrap: wrap; }
.pager button {
  font: inherit;
  font-size: 13px;
  color: var(--ink-2);
  background: var(--well);
  border: 1px solid var(--edge);
  border-radius: var(--r-sm);
  padding: 9px 14px;
  min-width: 44px;            /* iPad tap target */
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: color .2s, border-color .2s, transform .15s var(--ease);
}
.pager button:active:not(:disabled) { transform: scale(.96); }
.pager button:disabled { opacity: .35; cursor: default; }
.pager-pos { font-size: 12.5px; color: var(--ink-3); margin: 0 4px;
             font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------- bit words */
.words { display: grid; gap: 12px; grid-template-columns: repeat(auto-fit, minmax(224px, 1fr)); }
.word { padding: 11px 13px; border-radius: var(--r-sm); background: var(--well); }
.word-head {
  display: flex; justify-content: space-between; gap: 8px;
  font-size: 11px; color: var(--ink-3); margin-bottom: 9px;
  font-variant-numeric: tabular-nums;
}
.bits { display: grid; grid-template-columns: repeat(16, 1fr); gap: 3px; }
.bits i {
  height: 17px;
  border-radius: 3px;
  background: var(--bit-off);
  transition: background .35s, box-shadow .35s;
}
.bits i.on { background: var(--cyan); box-shadow: 0 0 9px rgba(79,214,255,.55); }
.bits i.on.named { background: var(--teal); box-shadow: 0 0 9px rgba(47,227,196,.55); }

.learn-row {
  display: flex; justify-content: space-between; gap: 10px;
  font-size: 12.5px; padding: 7px 0;
  border-bottom: 1px solid var(--edge);
}
.learn-row:last-child { border-bottom: 0; }
.learn-row span { color: var(--ink-2); }
.learn-row em { color: var(--ink-3); font-style: normal; font-size: 11px; }
.empty { color: var(--ink-3); font-size: 12.5px; margin: 0; }

/* -------------------------------------------------------------- footer */
.footer {
  margin: 20px 2px 4px;
  font-size: 11px;
  color: var(--ink-3);
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  font-variant-numeric: tabular-nums;
}
.sep { opacity: .5; }

/* --------------------------------------------------------- responsive */
/* iPad portrait (768/820/1024 wide) -> two columns. */
@media (min-width: 700px) {
  .deck { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .panel-tanks, .panel-words, .panel-learn, .panel-log { grid-column: 1 / -1; }
  .leg-grid { grid-template-columns: 1fr; }
}

/* iPad landscape 11" (1180) and up -> three. */
@media (min-width: 1100px) {
  .deck { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .panel-tanks { grid-column: span 2; }
  .panel-bilge { grid-column: span 1; }
}

/* Desktop wide -> four, tanks take half the width. */
@media (min-width: 1560px) {
  body { padding-left: max(var(--pad), calc((100vw - 1720px) / 2));
         padding-right: max(var(--pad), calc((100vw - 1720px) / 2)); }
  .deck { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .panel-tanks { grid-column: span 2; }
}

/* Under 900px the rail goes horizontal above the content. Two destinations do
   not justify hiding navigation behind a hamburger, and a visible target is
   easier to hit on a moving boat. */
@media (max-width: 899px) {
  .shell {
    flex-direction: column;
    gap: 12px;
    /* stretch, NOT the flex-start inherited from the wide layout. As a column
       with flex-start every child sized itself to its own content instead of
       the screen: panels came out 280px inside a 362px shell, and the map
       changed width depending on how many filter chips happened to be showing.
       Every view was narrower than the phone it was on. */
    align-items: stretch;
  }
  .rail {
    flex: 1 1 auto;
    width: 100%;
    position: static;
    display: flex;
    align-items: center;
    gap: 14px;
    padding: 10px 14px;
  }
  .rail .brand { padding: 0; flex: 0 0 auto; }
  .nav { grid-auto-flow: column; gap: 6px; margin-left: auto; }
  .nav-item { padding: 10px 14px; }
}

/* Five tubes will not fit a phone; go two rows of three and let the last wrap. */
@media (max-width: 620px) {
  .tank-row { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@media (max-width: 560px) {
  .rail { flex-wrap: wrap; }
  .nav { margin-left: 0; width: 100%; grid-auto-columns: 1fr; }
  .nav-item { justify-content: center; }
  .brand-text p { display: none; }
}

/* Phone: the brand block and the status block cannot share one row without
   overlapping, so give each its own. */
@media (max-width: 480px) {
  .topbar { flex-wrap: wrap; row-gap: 10px; }
  .topbar-right { margin-left: auto; }
}
@media (max-width: 340px) {
  .pill-meta { display: none; }
}

/* Landscape on a phone-height viewport: shorten the tall bits. */
@media (max-height: 500px) and (orientation: landscape) {
  .tube { height: 104px; }
  .num-xl { font-size: 32px; }
  .spark { height: 34px; }
}

/* ------------------------------------------------------- preferences */
@media (prefers-reduced-motion: reduce) {
  .blob { animation: none; }
  .pill-live .dot { animation: none; }
  .bar-alarm { animation: none; }
  .num.bump { animation: none; }
  *, *::before, *::after {
    transition-duration: .01ms !important;
    animation-duration: .01ms !important;
  }
}

/* backdrop-filter has been in Safari since 9 and Chrome since 76, but if it is
   missing the panels would be near-invisible over the gradient. Opt into a
   solid fallback rather than shipping unreadable glass. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .panel, .topbar, .pill, .icon-btn, .bar { background: var(--panel-solid); }
}

/* color-mix is recent; fall back to a flat liquid fill where it is missing. */
@supports not (background: color-mix(in srgb, red 50%, blue)) {
  .tube-fill { background: var(--liq, var(--cyan)); }
}


/* ========================================================== the verdict ==
   The one thing an owner should be able to read from across the galley. It
   answers "is anything wrong" before any number is read, which is what the
   previous layout never did. */
.verdict {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
  padding: 20px var(--pad);
  border-radius: var(--r-lg);
  background: linear-gradient(158deg, var(--glass-top) 0%, var(--glass-bot) 100%);
  -webkit-backdrop-filter: blur(var(--blur)) saturate(155%);
  backdrop-filter: blur(var(--blur)) saturate(155%);
  border: 1px solid var(--edge);
  box-shadow: var(--shadow), inset 0 1px 0 var(--edge-hi);
  transition: border-color .4s;
}
.verdict-main { display: flex; align-items: center; gap: 16px; min-width: 0; }
.verdict-dot {
  width: 14px; height: 14px; border-radius: 50%;
  flex: none;
  background: var(--ink-3);
  transition: background .4s, box-shadow .4s;
}
.verdict h2 {
  margin: 0;
  font-size: clamp(19px, 2.9vw, 26px);
  font-weight: 600;
  letter-spacing: -.01em;
  text-transform: none;
  color: var(--ink);
}
.verdict p { margin: 3px 0 0; font-size: 13px; color: var(--ink-3); }

.verdict.ok        { border-color: rgba(130,232,127,.34); }
.verdict.ok .verdict-dot {
  background: var(--lime); box-shadow: 0 0 0 5px rgba(130,232,127,.16);
  animation: beat 2.6s ease-in-out infinite;
}
.verdict.attention { border-color: rgba(255,180,77,.45); }
.verdict.attention .verdict-dot { background: var(--amber); box-shadow: 0 0 0 5px rgba(255,180,77,.16); }
.verdict.alarm     { border-color: rgba(255,93,115,.55); animation: alarm-glow 2s ease-in-out infinite; }
.verdict.alarm .verdict-dot { background: var(--rose); box-shadow: 0 0 0 5px rgba(255,93,115,.18); }

.verdict-source { text-align: right; }
.vs-label { display: block; font-size: 10.5px; letter-spacing: .16em;
            text-transform: uppercase; color: var(--ink-3); }
.verdict-source strong {
  display: block; font-size: clamp(15px, 2vw, 19px); font-weight: 600;
  color: var(--ink); margin-top: 3px;
}
.vs-load { font-size: 12.5px; color: var(--ink-2); font-variant-numeric: tabular-nums; }

/* ===================================================== needs attention == */
.panel-attn { grid-column: 1 / -1; border-color: rgba(255,180,77,.34); }
.attn-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.attn-list li {
  display: flex; align-items: flex-start; gap: 13px;
  padding: 12px 14px;
  border-radius: var(--r-sm);
  background: var(--well);
}
.attn-list .sev {
  width: 9px; height: 9px; border-radius: 50%; flex: none; margin-top: 5px;
}
.attn-list .sev.high   { background: var(--rose); box-shadow: 0 0 8px rgba(255,93,115,.6); }
.attn-list .sev.medium { background: var(--amber); }
.attn-list .sev.low    { background: var(--ink-3); }
.attn-list b { display: block; font-size: 14px; font-weight: 600; color: var(--ink); }
.attn-list span { display: block; font-size: 12.5px; color: var(--ink-2); margin-top: 2px; }

/* ============================================== basis / provenance note ==
   Anywhere a number is derived or a threshold is assumed, say so in place.
   Mixing "measured" and "guessed" without marking which is which is how this
   dashboard previously implied a service interval nobody had confirmed. */
.basis {
  margin: 8px 0 0;
  font-size: 11px;
  line-height: 1.5;
  color: var(--ink-3);
}

/* ================================================== consumables / tanks == */
.tank-endur {
  margin-top: 5px;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--teal);
  font-variant-numeric: tabular-nums;
  line-height: 1.35;
}
.tank-endur.none { color: var(--ink-3); font-weight: 400; }
.tank-endur small { display: block; font-weight: 400; color: var(--ink-3); font-size: 10px; }

/* ============================================================== power == */
.pw-head { display: flex; align-items: flex-end; gap: 18px; flex-wrap: wrap;
           margin-bottom: 16px; }
.pw-load { flex: 0 0 auto; }
.pw-head .spark { flex: 1 1 190px; min-width: 150px; margin: 0; }
.pw-detail { margin-top: 14px; }
.pw-detail summary {
  cursor: pointer; list-style: none;
  font-size: 11px; letter-spacing: .13em; text-transform: uppercase;
  color: var(--ink-3);
  -webkit-tap-highlight-color: transparent;
}
.pw-detail summary::-webkit-details-marker { display: none; }
.pw-detail summary::after {
  content: ""; display: inline-block; margin-left: 7px;
  width: 5px; height: 5px;
  border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg) translateY(-2px);
}
.pw-detail[open] summary { margin-bottom: 12px; }
.pw-detail[open] summary::after { transform: rotate(-135deg) translateY(-2px); }


/* ================================================ owner-priority layout ==
   Order on the page is decision order: what forces an action, then what
   explains it, then reference. "Switched on" is genuinely useful but nobody
   opens this app to find out whether a charger is on, so it goes last.

   Spans are set so nothing important ends up in a tall narrow column - three
   stacked generator cards in a third of the width was mostly empty space. */
@media (min-width: 1100px) {
  .panel-tanks { grid-column: span 2; }
  .panel-bilge { grid-column: span 1; }
  .panel-power { grid-column: span 2; }
  .panel-status { grid-column: span 1; }
  .panel-gen   { grid-column: 1 / -1; }
}
@media (min-width: 1560px) {
  .panel-tanks { grid-column: span 2; }
  .panel-power { grid-column: span 2; }
  .panel-gen   { grid-column: span 4; }
}

/* Two lamps per row halves the height of a panel that was eight rows tall. */
.lamps { grid-template-columns: repeat(auto-fit, minmax(132px, 1fr)); }
@media (min-width: 1100px) {
  .panel-status .lamps { grid-template-columns: 1fr; }
}

/* Generators side by side once the panel is full width. */
@media (min-width: 1100px) {
  .panel-gen .gen-row { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}


/* ====================================================== density =========
   Target: an iPad in landscape is 1180 x 820, so the whole status picture has
   to live in about 750px of content height. The original spacing was airy
   enough to need a scroll for the one screen that should never need one.

   Three changes do most of the work: the banks become a row instead of a
   stack, the lamps become wrapping chips instead of eight full-width rows,
   and the tank tubes lose the height they did not need to be readable. */

/* Banks: four stacked rows cost ~160px for four numbers. */
.bank-list { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 7px; }
.bank {
  grid-template-columns: 1fr;
  justify-items: center;
  text-align: center;
  gap: 3px;
  padding: 9px 8px;
}
.bank-name { font-size: 10.5px; letter-spacing: .04em; }
.bank-v { font-size: 17px; }
.bank-a { min-width: 0; font-size: 11px; padding: 2px 7px; }
@media (max-width: 700px) {
  .bank-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* Lamps: chips that wrap, not a column of eight rows. */
.lamps { display: flex; flex-wrap: wrap; gap: 7px; }
.lamp {
  flex: 0 1 auto;
  gap: 8px;
  padding: 8px 12px;
  font-size: 12.5px;
}
.lamp b { width: 9px; height: 9px; }

/* Tubes were 178px tall to look generous; 130 reads just as well. */
.tube { height: clamp(100px, 14vh, 132px); }
.tank-name { margin: 7px 0 1px; }

/* Genset rings at 116px across three cards is a lot of ink for three numbers. */
.gen-ring { max-width: 92px; }
.gen-centre b { font-size: 17px; }
.gen-state { margin-bottom: 7px; }
.gen-load { margin-top: 7px; }
.gen-load .num { font-size: 17px; }

.verdict { padding: 16px var(--pad); }
.deck { gap: 14px; }
.panel-head { margin-bottom: 12px; }

/* Short viewport - iPad landscape, or any laptop - tighten further so the
   status view fits without scrolling. */
@media (max-height: 900px) {
  :root { --pad: 14px; }
  .deck { gap: 11px; }
  .verdict { padding: 13px var(--pad); }
  .verdict h2 { font-size: 21px; }
  .tube { height: 96px; }
  .num-xl { font-size: 34px; }
  .spark { height: 36px; }
  .gen-ring { max-width: 76px; }
  .gen-centre b { font-size: 15px; }
  .panel-head { margin-bottom: 9px; }
  .hero-metric { margin-bottom: 0; }
  .basis { margin-top: 6px; font-size: 10.5px; }
  .tank-endur { font-size: 11px; }
}


/* ================================== the generator panel when idle ======
   Nothing is running, so the three rings have no state to show. Collapse to
   the header line - which already carries "all stopped, X due first in N h" -
   and hand the height back to the screen. One tap on the header, or any
   generator starting, brings it back. */
.panel-gen.idle .gen-row { display: none; }
.panel-gen.idle .basis { display: none; }
.panel-gen.idle .panel-head { margin-bottom: 0; }
.panel-gen.idle { cursor: pointer; }
.panel-gen.idle.open .gen-row { display: grid; }
.panel-gen.idle.open .basis { display: block; }
.panel-gen.idle.open .panel-head { margin-bottom: 9px; }
.panel-gen.idle .panel-note::after {
  content: "";
  display: inline-block; margin-left: 8px;
  width: 5px; height: 5px;
  border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg) translateY(-2px);
}
.panel-gen.idle.open .panel-note::after { transform: rotate(-135deg) translateY(-1px); }

.lamps-off {
  flex: 1 1 100%;
  margin: 4px 0 0;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--ink-3);
}


/* ================================================= administrator gate ==
   Deliberately quiet: an owner should never wonder what it is for. The Signals
   tab holds register-level engineering and the bit-labelling tools, which are
   not part of an owner's job. */
.admin-link {
  font: inherit; font-size: 11px;
  color: var(--ink-3);
  background: none; border: 0; padding: 0;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.admin-link:hover { color: var(--ink-2); text-decoration: underline; }
.admin-form {
  flex: 1 1 100%;
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-top: 10px;
}
.admin-form input {
  font: inherit; font-size: 13px; color: var(--ink);
  background: var(--well); border: 1px solid var(--edge);
  border-radius: var(--r-sm); padding: 8px 11px;
  flex: 0 1 240px; min-width: 0;
}
.admin-form input:focus { outline: none; border-color: var(--cyan); }
.admin-msg { font-size: 11.5px; color: var(--ink-3); }
.admin-msg.bad { color: var(--rose); }
.admin-msg.good { color: var(--lime); }


/* ============================================ grouped indicator lamps == */
.lamps { display: block; }
.lamp-group + .lamp-group { margin-top: 11px; }
.lamp-group h3 {
  margin: 0 0 6px;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.lamp-row { display: flex; flex-wrap: wrap; gap: 6px; }
.lamp { padding: 7px 11px; font-size: 12px; }
.lamp b { width: 8px; height: 8px; }
/* Off is legible, not invisible: an owner needs to read "anchor light off" as
   easily as "charger on", so the label keeps its contrast and only the dot
   goes dark. */
.lamp:not(.on) span { color: var(--ink-2); }


/* ================================================= generators as rows ==
   Always visible. Collapsing them by default was clever and wrong - a
   generator's state is something an owner should see without asking. */
.panel-gen.idle .gen-row, .panel-gen.idle .basis { display: block; }
.panel-gen.idle { cursor: default; }
.panel-gen.idle .panel-note::after { content: none; }

.gen-row { display: grid; gap: 6px; grid-template-columns: 1fr !important; }
.genrow {
  display: grid;
  grid-template-columns: 12px 8.5em 5.5em 1fr auto auto auto;
  align-items: center;
  gap: 12px;
  padding: 10px 13px;
  border-radius: var(--r-sm);
  background: var(--well);
  font-size: 12.5px;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}
.gr-dot {
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--ink-3); opacity: .4;
  transition: background .3s, opacity .3s, box-shadow .3s;
}
.genrow.on .gr-dot { background: var(--lime); opacity: 1; box-shadow: 0 0 8px rgba(130,232,127,.6); }
.gr-name { color: var(--ink); font-weight: 600; }
.genrow.on .gr-state { color: var(--lime); }
.gr-load { color: var(--ink); }
.gr-load.warn { color: var(--amber); }
.gr-load.crit { color: var(--rose); }
.gr-svc b { color: var(--ink); font-weight: 600; }
.gr-svc.warn b { color: var(--amber); }
.gr-svc.crit b { color: var(--rose); }
.gr-total, .gr-batt { color: var(--ink-3); white-space: nowrap; }
.gr-batt.warn { color: var(--amber); }
.gr-batt.crit { color: var(--rose); }

@media (max-width: 760px) {
  .genrow { grid-template-columns: 12px 1fr auto; row-gap: 4px; }
  .gr-load { grid-column: 2 / -1; }
  .gr-svc  { grid-column: 2 / -1; }
  .gr-total { display: none; }
}


/* ================================================== final layout pass ==
   Measured on iPad landscape: the indicator panel came out 344px against
   Power's 244 beside it, and the taller one sets the row - so 100px was being
   spent on nothing. Full width with the groups as columns costs about 100px
   total instead.

   Zero scroll and always-visible generators cannot both be had on an 820px
   screen with this much real content, so the goal is that the FOLD FALLS IN A
   SENSIBLE PLACE: verdict, consumables, ingress and power all above it, the
   generators almost entirely visible, and only the indicators needing a nudge. */
@media (min-width: 1100px) {
  .panel-power  { grid-column: 1 / -1; }
  .panel-status { grid-column: 1 / -1; }
  .panel-gen    { grid-column: 1 / -1; }
  /* Groups side by side rather than stacked. */
  .panel-status .lamps { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; }
  .lamp-group + .lamp-group { margin-top: 0; }
  /* Power is full width now, so the load and the trace can share a line with
     the banks rather than sitting above them. */
  .panel-power .pw-head { margin-bottom: 12px; }
}
@media (min-width: 1560px) {
  .panel-power { grid-column: span 4; }
  .panel-status { grid-column: span 4; }
  .panel-gen { grid-column: span 4; }
  .panel-status .lamps { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* A few honest trims: the provenance notes stay, they just stop taking three
   lines each. */
@media (max-height: 900px) {
  .tube { height: 88px; }
  .tank-endur small { display: none; }     /* kept in the title attribute */
  .panel-gen .basis { display: none; }     /* the assumption is in the item text too */
  .genrow { padding: 8px 12px; }
  .gen-row { gap: 5px; }
  .verdict { padding: 12px var(--pad); }
  .lamp { padding: 6px 10px; }
}


/* Last 40px: the generator panel ended 38px past an 820px fold, so all three
   rows now clear it. Nothing is removed - padding, gaps and the trace lose a
   few pixels each. */
@media (max-height: 900px) {
  .panel { padding: 12px; }
  .deck { gap: 9px; }
  .spark { height: 32px; }
  .bank { padding: 7px; }
  .pw-head { margin-bottom: 10px; }
  .panel-bilge .basis { font-size: 10px; line-height: 1.45; margin-top: 5px; }
  .verdict { padding: 11px 14px; }
  .verdict h2 { font-size: 20px; }
}


/* ============================================== shore supply strip ======
   Shore state belongs in the Power band, where someone actually looks, not as
   chips at the bottom of the page. */
.shore-strip {
  display: flex; flex-wrap: wrap; gap: 8px;
  margin-bottom: 10px;
}
.ss-item {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 7px 12px;
  border-radius: var(--r-sm);
  background: var(--well);
  font-size: 12.5px;
  color: var(--ink-2);
}
.ss-item b {
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--ink-3); opacity: .4;
  transition: background .3s, opacity .3s, box-shadow .3s;
}
.ss-item.on { color: var(--ink); }
.ss-item.on b { background: var(--lime); opacity: 1; box-shadow: 0 0 8px rgba(130,232,127,.6); }
.ss-item.bad { color: var(--rose); }
.ss-item.bad b { background: var(--rose); opacity: 1; box-shadow: 0 0 8px rgba(255,93,115,.6); }
/* Not identified is a real state and should look like one - neither good nor
   bad, and clearly not a measurement. */
.ss-item.unknown { color: var(--ink-3); border: 1px dashed var(--edge); background: none; }
.ss-item.unknown b { background: none; border: 1.5px solid var(--ink-3); opacity: .7; }


/* The shore strip cost 28px and pushed the generators back past the fold.
   Recovered from padding and the trace rather than from any content. */
@media (max-height: 900px) {
  .panel { padding: 11px; }
  .spark { height: 26px; }
  .tube { height: 80px; }
  .genrow { padding: 7px 12px; }
  .shore-strip { margin-bottom: 8px; gap: 6px; }
  .ss-item { padding: 6px 10px; font-size: 12px; }
  .verdict { padding: 10px 14px; }
  .tank-name { margin: 5px 0 1px; }
}


/* ============================================ readable traces ==========
   The trace carries its own scale now, so it needs room for the value labels
   on the right and the clock line underneath. */
.spark { height: 56px; }
.pw-head { position: relative; }
.panel-bilge .hero-metric { position: relative; }
.spark-read {
  position: absolute;
  bottom: 100%;
  margin-bottom: 4px;
  padding: 4px 9px;
  border-radius: var(--r-sm);
  background: var(--ink);
  color: var(--abyss);
  font-size: 11px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  pointer-events: none;
  z-index: 5;
  box-shadow: 0 4px 12px -4px rgba(0,0,0,.5);
}
@media (max-height: 900px) {
  .spark { height: 48px; }
}


/* ============================== rail carries everything chrome ==========
   The top header and the footer are gone. What lived there - the live status,
   the theme control, the administrator entry - now sits at the foot of the
   rail, so the content area is nothing but content. The view title went with
   the header: the highlighted nav item already says which view you are in. */
.rail { display: flex; flex-direction: column; }
.rail-foot {
  margin-top: auto;
  padding-top: 14px;
  display: flex;
  flex-direction: column;
  gap: 9px;
}
.rail-foot .pill {
  justify-content: flex-start;
  width: 100%;
  box-shadow: none;
  border-color: var(--edge);
  background: var(--well);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  flex-wrap: wrap;
  row-gap: 1px;
}
.rail-foot .pill-meta { flex: 1 1 100%; padding-left: 16px; }
.rail-tools { display: flex; align-items: center; gap: 8px; }
.rail-tools .icon-btn { width: 36px; height: 36px; }
.rail-tools .icon-btn svg { width: 18px; height: 18px; }
.rail-foot .admin-link { margin-left: auto; padding: 0 4px; }
.rail-foot .admin-form { margin-top: 2px; flex-direction: column; align-items: stretch; }
.rail-foot .admin-form input { flex: 1 1 auto; }
.rail-foot .admin-form .btn { width: 100%; }

/* The rail is a full-height column on wide screens so its foot sits at the
   bottom rather than under the nav. */
@media (min-width: 900px) {
  .shell { align-items: stretch; }
  .rail { min-height: calc(100vh - var(--safe-t) - var(--safe-b) - 20px); }
}

/* Horizontal rail: the live pill and tools join the row instead of stacking. */
@media (max-width: 899px) {
  .rail-foot {
    margin-top: 0;
    padding-top: 0;
    flex-direction: row;
    align-items: center;
    gap: 8px;
    margin-left: auto;
  }
  .rail-foot .pill { width: auto; flex-wrap: nowrap; }
  .rail-foot .pill-meta { flex: 0 0 auto; padding-left: 0; }
  .rail-foot .admin-form {
    position: absolute; right: 14px; top: 100%; margin-top: 6px; z-index: 30;
    background: var(--deep); border: 1px solid var(--edge);
    border-radius: var(--r-sm); padding: 10px; width: 260px;
  }
  .rail { position: relative; }
  .nav { margin-left: 0; }
}
@media (max-width: 560px) {
  .rail-foot { flex: 1 1 100%; margin-left: 0; }
}

/* ============================================================ alerts & users
   The wizard is the only full-screen surface in the app. That is deliberate:
   building an alert is a decision with consequences at 3am, and it deserves
   the whole window rather than a cramped popover. */

.panel-head { display: flex; align-items: center; justify-content: space-between;
              gap: 12px; margin-bottom: 12px; }
.panel-head h2 { margin: 0; }
.panel-note { margin: 0 0 14px; padding: 10px 13px; border-radius: var(--r-sm);
              background: var(--well); border: 1px solid rgba(255,180,77,.32);
              font-size: 12.5px; color: var(--ink-2); line-height: 1.6; }
.panel-note code { font-size: 11.5px; background: rgba(127,127,127,.16);
                   padding: 1px 5px; border-radius: 4px; }

.btn-go { background: var(--cyan); color: #04222b; border-color: transparent;
          font-weight: 600; }
.btn-go:hover { filter: brightness(1.08); }
.btn-sm { font-size: 11.5px; padding: 4px 9px; }
.btn-bad { color: var(--rose); }
.spacer { flex: 1 1 auto; }

.alert-list { display: grid; gap: 10px; }
.alert-card { padding: 13px 15px; border-radius: var(--r-sm); }
.alert-card.is-off { opacity: .55; }
.alert-card-top { display: flex; align-items: center; gap: 9px; flex-wrap: wrap; }
.alert-card-top b { font-size: 14px; }
.alert-desc { margin: 8px 0 0; font-size: 12.5px; color: var(--ink-2); line-height: 1.6; }
.alert-result { margin: 7px 0 0; font-size: 12px; color: var(--ink-3); line-height: 1.6; }
.kind-tag { font-size: 10px; letter-spacing: .08em; text-transform: uppercase;
            padding: 2px 7px; border-radius: 999px; background: rgba(127,127,127,.16);
            color: var(--ink-3); }
.kind-alarm     { color: var(--rose);   background: rgba(255,93,115,.13); }
.kind-datapoint { color: var(--cyan);   background: rgba(80,200,230,.13); }
.kind-offline   { color: var(--amber);  background: rgba(255,180,77,.13); }

.user-form, .admin-form { display: grid; gap: 8px; margin-bottom: 14px; }
.user-form label { display: grid; gap: 4px; font-size: 12px; color: var(--ink-3); }
.admin-form-row { display: flex; gap: 7px; }
tr.is-off td { opacity: .55; }

/* ------------------------------------------------------------- the wizard */
.modal-open { overflow: hidden; }
.wiz-backdrop { position: fixed; inset: 0; z-index: 60; display: flex;
                align-items: center; justify-content: center; padding: 22px;
                background: rgba(6,10,16,.62); }
.wiz { display: grid; grid-template-columns: 216px 1fr; width: min(920px, 100%);
       max-height: min(680px, 100%); border-radius: var(--r-lg); overflow: hidden;
       /* panel-solid, not the glass token: this sits ON TOP of the page, and a
          translucent modal lets the text behind it show through its own text.
          Glass works for a panel with the page's own gradient behind it, not
          for a surface covering live content. */
       background: var(--modal-ground); border: 1px solid var(--edge);
       box-shadow: 0 30px 80px rgba(0,0,0,.45); }

/* The rail's tint is layered OVER an opaque ground, not used as the ground:
   var(--well) is translucent, so on its own it would show the page through. */
.wiz-rail { padding: 22px 18px;
            background: linear-gradient(var(--well), var(--well)), var(--modal-ground);
            border-right: 1px solid var(--edge); display: flex; flex-direction: column; }
.wiz-rail h2 { margin: 0 0 18px; font-size: 15px; }
.wiz-steps { list-style: none; margin: 0; padding: 0; display: grid; gap: 3px; }
.wiz-steps li { display: flex; align-items: center; gap: 9px; padding: 7px 8px;
                border-radius: var(--r-sm); font-size: 12.5px; color: var(--ink-3); }
.wiz-steps li i { width: 17px; height: 17px; flex: 0 0 17px; border-radius: 50%;
                  border: 1.5px solid currentColor; opacity: .5; }
.wiz-steps li.is-on { color: var(--ink); background: rgba(127,127,127,.1); }
.wiz-steps li.is-on i { opacity: 1; border-color: var(--cyan); }
.wiz-steps li.is-done { color: var(--ink-2); }
.wiz-steps li.is-done i { opacity: 1; background: var(--cyan); border-color: var(--cyan); }
.wiz-hint { margin: auto 0 0; font-size: 11.5px; color: var(--ink-3); line-height: 1.6; }

.wiz-body { position: relative; padding: 24px 26px 0; display: flex;
            flex-direction: column; min-height: 0; }
.wiz-close { position: absolute; top: 12px; right: 14px; background: none; border: 0;
             color: var(--ink-3); font-size: 24px; line-height: 1; cursor: pointer; }
.wiz-close:hover { color: var(--ink); }
.wiz-pane { flex: 1 1 auto; overflow-y: auto; padding-bottom: 16px; min-height: 0; }
.wiz-pane h3 { margin: 0 0 5px; font-size: 17px; }
.wiz-sub { margin: 0 0 16px; font-size: 12.5px; color: var(--ink-3); line-height: 1.65;
           max-width: 54ch; }

.choice-grid { display: grid; gap: 9px; }
.choice { text-align: left; padding: 14px 15px; border-radius: var(--r-sm);
          background: var(--well); border: 1px solid var(--edge); cursor: pointer;
          font: inherit; color: var(--ink-2); display: grid; gap: 4px; }
.choice:hover { border-color: rgba(127,127,127,.42); }
.choice.is-on { border-color: var(--cyan); background: rgba(80,200,230,.09); }
.choice b { font-size: 13.5px; color: var(--ink); }
.choice span { font-size: 12px; line-height: 1.6; }

.field { display: grid; gap: 5px; font-size: 12px; color: var(--ink-3);
         margin-bottom: 14px; max-width: 420px; }
.field-wide { max-width: none; }
.field select, .field-input { font: inherit; font-size: 13px; padding: 8px 10px;
    border-radius: var(--r-sm); background: var(--well); color: var(--ink);
    border: 1px solid var(--edge); width: 100%; }
.field select:focus, .field-input:focus { outline: none; border-color: var(--cyan); }
.field-why { margin: -8px 0 18px; font-size: 11.5px; color: var(--ink-3);
             line-height: 1.65; max-width: 56ch; }
.cond-row { display: flex; gap: 11px; flex-wrap: wrap; }
.cond-row .field { max-width: 220px; }
.wiz-now { font-size: 12px; color: var(--cyan); margin: 0; }
.timing { margin-bottom: 6px; }

.seg { display: inline-flex; gap: 0; margin-bottom: 13px; border-radius: var(--r-sm);
       overflow: hidden; border: 1px solid var(--edge); }
.seg-btn { font: inherit; font-size: 12.5px; padding: 7px 13px; cursor: pointer;
           background: var(--well); color: var(--ink-3); border: 0; }
.seg-btn.is-on { background: var(--cyan); color: #04222b; font-weight: 600; }

.pick-list { max-height: 250px; overflow-y: auto; margin-top: 9px;
             border-radius: var(--r-sm); }
.pick { display: flex; align-items: center; gap: 9px; padding: 8px 11px;
        border-bottom: 1px solid var(--edge); font-size: 12.5px; cursor: pointer; }
.pick:last-child { border-bottom: 0; }
.pick.is-on { background: rgba(80,200,230,.07); }
.pick span { flex: 1 1 auto; }
.sev { font-size: 9.5px; letter-spacing: .07em; text-transform: uppercase;
       font-style: normal; color: var(--ink-3); }
.sev-critical { color: var(--rose); }
.sev-warning  { color: var(--amber); }
.wiz-count { font-size: 11.5px; color: var(--ink-3); margin: 7px 0 0; }

.phone-list { display: grid; gap: 8px; margin-bottom: 12px; }
.phone-row { display: flex; gap: 7px; align-items: center; max-width: 420px; }

.review { padding: 15px 17px; border-radius: var(--r-sm); }
.review-name { margin: 0 0 6px; font-size: 15px; font-weight: 600; color: var(--ink); }
.review-line { margin: 0; font-size: 13px; line-height: 1.7; color: var(--ink-2); }

.wiz-foot { display: flex; align-items: center; gap: 9px; padding: 14px 0;
            border-top: 1px solid var(--edge); }
.wiz-err { flex: 1 1 auto; font-size: 12px; color: var(--rose); line-height: 1.5; }

/* A phone gets the wizard as a full sheet - a 216px rail beside content on a
   390px screen would leave neither of them usable. */
@media (max-width: 640px) {
  .wiz-backdrop { padding: 0; }
  .wiz { grid-template-columns: 1fr; grid-template-rows: auto 1fr;
         max-height: 100%; height: 100%; border-radius: 0; border: 0; }
  .wiz-rail { padding: 16px 18px 12px; border-right: 0;
              border-bottom: 1px solid var(--edge); }
  .wiz-rail h2 { margin-bottom: 11px; }
  .wiz-steps { grid-auto-flow: column; grid-auto-columns: 1fr; gap: 0; }
  .wiz-steps li span { display: none; }
  .wiz-steps li { justify-content: center; padding: 5px 0; }
  .wiz-hint { display: none; }
  .wiz-body { padding: 18px 18px 0; }
  .alert-card-top { gap: 6px; }
}

/* ================================================================== the gate
   A full surface, not a popover. Signing in is the whole task at that moment,
   and on a phone a small form floating over a dashboard is a form you fight. */
body.gated { overflow: hidden; }
.gate {
  position: fixed; inset: 0; z-index: 90;
  display: flex; align-items: center; justify-content: center;
  padding: 24px;
  background: var(--modal-ground);
  /* Cover the app completely. A translucent gate would show the data it is
     supposed to be withholding. */
}
.gate-card {
  width: min(380px, 100%);
  display: flex; flex-direction: column;
}
/* Centred above the form. The two fields and the button are a single narrow
   column, and a left-aligned mark beside them reads as though it belongs to
   the email field rather than to the page. */
.gate-brand { display: flex; flex-direction: column; align-items: center;
              gap: 9px; margin-bottom: 28px; text-align: center; }
.gate-brand .burgee { width: 44px; height: 44px; color: var(--cyan); flex: 0 0 44px; }
.gate-brand h1 { margin: 0; font-size: 21px; letter-spacing: .14em; }
.gate-brand p  { margin: 2px 0 0; font-size: 10.5px; letter-spacing: .17em;
                 text-transform: uppercase; color: var(--ink-3); }

.gate-field { font-size: 12px; color: var(--ink-3); margin-bottom: 5px; }
.gate-card input {
  font: inherit;
  /* 16px minimum. Below that, iOS Safari zooms the page when the field takes
     focus and the form ends up half off-screen - the single most common way a
     login looks broken on an iPhone. */
  font-size: 16px;
  padding: 12px 13px;
  margin-bottom: 16px;
  border-radius: var(--r-sm);
  background: var(--well);
  color: var(--ink);
  border: 1px solid var(--edge);
  width: 100%;
  -webkit-appearance: none;      /* iOS otherwise imposes its own inset look */
  appearance: none;
}
.gate-card input:focus { outline: none; border-color: var(--cyan); }
.gate-go {
  font: inherit; font-size: 15px; font-weight: 600;
  padding: 13px 16px;            /* a comfortable thumb target, not a 28px bar */
  border-radius: var(--r-sm);
  background: var(--cyan); color: #04222b; border: 0;
  cursor: pointer; margin-top: 4px;
  -webkit-tap-highlight-color: transparent;
}
.gate-go:hover { filter: brightness(1.07); }
.gate-go:disabled { opacity: .6; cursor: default; }
.gate-msg { margin: 13px 0 0; font-size: 12.5px; color: var(--ink-3);
            min-height: 1.2em; line-height: 1.5; }
.gate-msg.bad { color: var(--rose); }

/* Landscape on a phone leaves almost no vertical room; tighten rather than
   scroll, because a login that scrolls hides its own button. */
@media (max-height: 460px) {
  .gate { padding: 14px; align-items: flex-start; }
  .gate-brand { margin-bottom: 14px; }
  .gate-card input { margin-bottom: 10px; padding: 9px 12px; }
}

/* The channel picker needs to fit above the destination fields without the
   step scrolling, so its cards are tighter than the trigger cards. */
.choice-grid-tight .choice { padding: 10px 13px; gap: 2px; }
.choice-grid-tight .choice b { font-size: 12.5px; }
.choice-grid-tight .choice span { font-size: 11.5px; line-height: 1.5; }

/* ============================================== rail: masthead and profile */
/* The live pill sits in the masthead as one line. It answers the question you
   ask before any number on the page - is this current? - so it belongs with
   the name, not at the foot of the rail where it was easy to miss. */
.pill-line {
  margin: -8px 8px 16px;
  width: calc(100% - 16px);
  justify-content: flex-start;
  white-space: nowrap;
  overflow: hidden;
}
.pill-line .pill-meta { margin-left: auto; padding-left: 8px; }

.profile {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 8px; border-radius: var(--r-sm);
  background: none; border: 1px solid transparent; cursor: pointer;
  font: inherit; color: var(--ink); text-align: left;
}
.profile:hover { background: var(--well); border-color: var(--edge); }
.avatar {
  flex: 0 0 30px; width: 30px; height: 30px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--cyan); color: #04222b;
  font-size: 11.5px; font-weight: 700; letter-spacing: .02em;
}
.profile-who { min-width: 0; display: flex; flex-direction: column; }
.profile-who b {
  font-size: 12px; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.profile-who span { font-size: 10.5px; color: var(--ink-3); }
.profile-chev { width: 15px; height: 15px; flex: 0 0 15px; color: var(--ink-3); }
.profile[aria-expanded="true"] .profile-chev { transform: rotate(180deg); }

.profile-menu { margin-top: 6px; padding: 10px; border-radius: var(--r-sm); }
.profile-menu-row { display: flex; flex-direction: column; gap: 7px; margin-bottom: 10px; }
.profile-menu-row > span { font-size: 10.5px; letter-spacing: .1em;
                           text-transform: uppercase; color: var(--ink-3); }
.theme-seg { display: flex; border-radius: var(--r-sm); overflow: hidden;
             border: 1px solid var(--edge); }
.theme-opt { flex: 1 1 0; font: inherit; font-size: 11.5px; padding: 6px 0;
             background: none; border: 0; color: var(--ink-3); cursor: pointer; }
.theme-opt.is-on { background: var(--cyan); color: #04222b; font-weight: 600; }
.profile-signout {
  width: 100%; font: inherit; font-size: 12.5px; padding: 8px;
  border-radius: var(--r-sm); background: none; color: var(--rose);
  border: 1px solid var(--edge); cursor: pointer;
}
.profile-signout:hover { background: rgba(255,93,115,.10); }

/* ==================================================== map and network views */
.panel-aside { font-size: 11px; color: var(--ink-3); }

.pos-grid { display: grid; gap: 9px; margin-top: 13px;
            grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); }
.pos-cell { padding: 10px 12px; border-radius: var(--r-sm); }
.pos-cell span { display: block; font-size: 10.5px; letter-spacing: .09em;
                 text-transform: uppercase; color: var(--ink-3); margin-bottom: 3px; }
.pos-cell b { font-size: 15px; font-variant-numeric: tabular-nums; }

/* sig-bar, not bar: `.bar` is already the full-width offline banner, and its
   display:flex and padding leaked in here and turned a 3px meter segment into
   a wide block. */
.bars { display: inline-flex; align-items: flex-end; gap: 2px; height: 14px; }
.bars .sig-bar { display: block; width: 3px; height: 4px; padding: 0;
                 background: var(--bit-off); border-radius: 1px; }
.bars .sig-bar:nth-child(2) { height: 6px; }
.bars .sig-bar:nth-child(3) { height: 8px; }
.bars .sig-bar:nth-child(4) { height: 11px; }
.bars .sig-bar:nth-child(5) { height: 14px; }
.bars .sig-bar.on { background: var(--lime); }

/* The rail collapses to a horizontal bar below 900px, so the masthead pill and
   the profile have to sit in that row rather than stack. */
@media (max-width: 900px) {
  .pill-line { margin: 0 0 0 auto; width: auto; }
  .profile-who { display: none; }
  .profile { width: auto; padding: 5px; }
  .profile-menu { position: absolute; right: 8px; top: 54px; z-index: 40;
                  width: 210px; background: var(--modal-ground); }
}

/* The InControl setup form, shown in place of the map until it is connected. */
.ic-setup { display: block; max-width: 420px; margin-top: 4px; }
.ic-setup h3 { margin: 0 0 5px; font-size: 15px; }
.ic-setup .field-input { margin-bottom: 12px; }

/* ===================================================== users as cards */
.user-list { display: grid; gap: 9px; }
.user-card { padding: 12px 14px; border-radius: var(--r-sm); }
.user-card.is-off { opacity: .55; }
.user-card-top { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.user-card-top b { font-size: 13.5px; min-width: 0; overflow-wrap: anywhere; }
.avatar-sm { flex: 0 0 26px; width: 26px; height: 26px; font-size: 10.5px; }
.user-meta { margin: 8px 0 0; font-size: 11.5px; color: var(--ink-3); line-height: 1.55; }
.user-actions { display: flex; gap: 7px; margin-top: 11px; flex-wrap: wrap; }

/* ============================================ phone: the nav as a tab strip
   Seven destinations will not fit across a 390px screen. Squeezed into equal
   columns each one was about 55px wide, which is below a comfortable tap
   target and left no room for a label - so switching tabs meant aiming. It is
   now a strip that scrolls horizontally, with each chip sized to be hit
   rather than aimed at, and the current one scrolled into view. */
@media (max-width: 560px) {
  .rail { flex-wrap: wrap; row-gap: 8px; }
  .nav {
    display: flex;
    grid-auto-flow: unset;
    width: 100%;
    margin: 0;
    gap: 6px;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    scroll-snap-type: x proximity;
    scrollbar-width: none;
    /* Bleed to the panel edges so the first and last chips are reachable and
       it is visibly a scrolling strip rather than a clipped row. */
    padding: 2px 12px;
    margin-inline: -12px;
  }
  .nav::-webkit-scrollbar { display: none; }
  .nav li { flex: 0 0 auto; scroll-snap-align: center; }
  .nav-item {
    flex-direction: column;
    gap: 3px;
    min-height: 52px;
    min-width: 62px;
    padding: 7px 12px;
    justify-content: center;
  }
  .nav-item svg { width: 19px; height: 19px; }
  .nav-item span { font-size: 10px; letter-spacing: .02em; }
  .nav-badge { position: absolute; top: 3px; right: 7px; }
  .nav-item { position: relative; }

  /* The rail wraps to two rows on a phone, so the menu has to clear both. */
  .profile-menu { top: auto; bottom: 8px; right: 8px; }
}

/* Interfaces that are disabled or unplugged, folded away by default. */
.wan-fold { margin-top: 10px; }
.wan-fold summary { cursor: pointer; list-style: none; padding: 7px 2px;
                    font-size: 11px; letter-spacing: .1em; text-transform: uppercase;
                    color: var(--ink-3); -webkit-tap-highlight-color: transparent; }
.wan-fold summary::-webkit-details-marker { display: none; }
.wan-fold summary::after { content: ""; display: inline-block; margin-left: 7px;
    width: 5px; height: 5px; border-right: 1.5px solid currentColor;
    border-bottom: 1.5px solid currentColor; transform: rotate(45deg) translateY(-2px); }
.wan-fold[open] summary::after { transform: rotate(225deg) translateY(1px); }
.wan-fold-body { display: grid; gap: 9px; margin-top: 4px; }
.kind-network { color: var(--violet); background: rgba(160,140,255,.13); }

/* ======================================================= the map, with Leaflet
   Leaflet ships light-themed chrome, so its controls and popups are restated
   in the app's tokens - otherwise the map is a white rectangle bolted onto a
   dark dashboard. */
.leaflet-host { width: 100%; height: clamp(300px, 52vh, 560px); }
.map-wrap { position: relative; border-radius: var(--r-sm); overflow: hidden; }
.leaflet-container { background: var(--well); font: inherit; }
.leaflet-control-zoom a,
.leaflet-control-layers-toggle,
.leaflet-bar a {
  background-color: var(--panel-solid); color: var(--ink);
  border-color: var(--edge);
}
.leaflet-control-zoom a:hover, .leaflet-bar a:hover { background-color: var(--well); }
.leaflet-control-layers { background: var(--panel-solid); color: var(--ink);
                          border: 1px solid var(--edge); border-radius: var(--r-sm); }
.leaflet-control-layers-expanded { padding: 9px 11px; font-size: 12.5px; }
.leaflet-popup-content-wrapper, .leaflet-popup-tip {
  background: var(--modal-ground); color: var(--ink);
  box-shadow: 0 8px 24px rgba(0,0,0,.35);
}
.leaflet-popup-content { font-size: 12.5px; line-height: 1.55; }
.leaflet-control-attribution {
  background: rgba(127,127,127,.20) !important; color: var(--ink-2);
  font-size: 9.5px;
  /* On a phone this wrapped onto two lines and sat over the scale bar. */
  max-width: 62%; line-height: 1.35;
}
.leaflet-control-attribution a { color: var(--cyan); }
.leaflet-control-scale-line { background: rgba(127,127,127,.20); color: var(--ink-2);
                              border-color: var(--edge); }

/* A heading arrow, not a pin: on the water, which way she points is half the
   information, and a teardrop marker points at the ground.
   
   It also has to be FINDABLE. A 10px cyan dot disappeared into OpenStreetMap's
   own blues and greys, so this is bigger, ringed in white with a dark outline
   so it holds up on both light tiles and dark, and it pulses - the eye finds
   movement on a still map far faster than it finds a colour. */
.boat-mark { position: relative; }
.boat-dot {
  position: absolute; left: 50%; top: 50%; width: 16px; height: 16px;
  margin: -8px 0 0 -8px; border-radius: 50%;
  background: #16b9e0;
  border: 3px solid #fff;
  box-shadow: 0 0 0 1.5px rgba(4,20,32,.65), 0 2px 6px rgba(0,0,0,.45);
  z-index: 2;
}
/* The halo, on its own element so the pulse never scales the dot or the arrow. */
.boat-mark::after {
  content: ""; position: absolute; left: 50%; top: 50%;
  width: 44px; height: 44px; margin: -22px 0 0 -22px;
  border-radius: 50%; background: rgba(22,185,224,.30);
  animation: boat-pulse 2.6s ease-out infinite;
  pointer-events: none;
}
@keyframes boat-pulse {
  0%   { transform: scale(.35); opacity: .85; }
  70%  { transform: scale(1);   opacity: 0; }
  100% { transform: scale(1);   opacity: 0; }
}
/* Respect a system preference for less motion - a pulsing marker is exactly
   the sort of thing that setting exists for. */
@media (prefers-reduced-motion: reduce) {
  .boat-mark::after { animation: none; opacity: .5; transform: scale(.6); }
}
.boat-arrow { position: absolute; inset: 0; transform-origin: 50% 50%; z-index: 3; }
.boat-arrow::before {
  content: ""; position: absolute; left: 50%; top: -9px; margin-left: -7px;
  border-left: 7px solid transparent; border-right: 7px solid transparent;
  border-bottom: 13px solid #16b9e0;
  filter: drop-shadow(0 0 1.5px rgba(4,20,32,.9));
}

/* ---------------------------------------------------- history controls */
.map-tools { display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
             margin-bottom: 12px; }
.range-row { display: flex; flex-wrap: wrap; gap: 6px; }
.chip { font: inherit; font-size: 12px; padding: 6px 11px; cursor: pointer;
        border-radius: 999px; background: var(--well); color: var(--ink-2);
        border: 1px solid var(--edge); -webkit-tap-highlight-color: transparent; }
.chip:hover { border-color: rgba(127,127,127,.45); }
.chip.is-on { background: var(--cyan); color: #04222b; border-color: transparent;
              font-weight: 600; }

/* A popover, anchored to the chip that opened it. As an inline block it
   pushed the map down the page every time it opened and yanked it back when it
   closed, which is disorienting when you are trying to read a chart. */
.cal {
  position: absolute; z-index: 45; top: 100%; left: 0; margin-top: 8px;
  width: 300px; padding: 13px;
  border-radius: var(--r-md);
}
.map-tools { position: relative; }
.cal-head { display: flex; align-items: center; justify-content: space-between;
            margin-bottom: 9px; }
.cal-head b { font-size: 13px; }
.cal-dow, .cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 3px; }
.cal-dow span { text-align: center; font-size: 10px; color: var(--ink-3);
                padding-bottom: 3px; }
.cal-day { position: relative; font: inherit; font-size: 12.5px; padding: 0;
           height: 34px; border-radius: 8px; cursor: pointer;
           background: none; border: 1px solid transparent; color: var(--ink-2);
           -webkit-tap-highlight-color: transparent; }
.cal-day:hover { border-color: var(--edge); }
.cal-day.today { font-weight: 700; color: var(--ink); }
.cal-day.future { opacity: .3; cursor: default; }
.cal-day.has { color: var(--ink); }
/* A dot under the numeral marks a day she actually moved. */
.cal-day.has i { position: absolute; left: 50%; bottom: 4px; margin-left: -2px;
                 width: 4px; height: 4px; border-radius: 50%; background: var(--cyan); }
.cal-day.in-range { background: rgba(80,200,230,.12); }
.cal-day.is-on { background: var(--cyan); color: #04222b; font-weight: 700; }
.cal-day.is-on i { background: #04222b; }
.cal-pad { height: 34px; }
.cal-foot { margin: 10px 0 0; font-size: 11px; color: var(--ink-3); line-height: 1.55; }

.trip-summary { margin-top: 14px; }
.trip-head { display: flex; align-items: baseline; gap: 10px; margin-bottom: 10px;
             flex-wrap: wrap; }
.trip-head b { font-size: 15px; }
.trip-head span { font-size: 12px; color: var(--ink-3); }
.leg-list { margin-top: 11px; display: grid; gap: 5px; }
.leg-row { display: flex; align-items: center; gap: 10px; padding: 8px 11px;
           border-radius: var(--r-sm); font-size: 12.5px; }
.leg-row b { flex: 0 0 18px; color: var(--ink-3); font-size: 11px; }
.leg-row span { flex: 1 1 auto; color: var(--ink-2); font-variant-numeric: tabular-nums; }
.leg-row em { font-style: normal; font-variant-numeric: tabular-nums; }

@media (max-width: 560px) {
  .leaflet-host { height: 60vh; }
  .cal { left: 0; right: 0; width: auto; }
  .map-tools { gap: 8px; }
}

/* ------------------------------------------------- suggested alerts */
.panel-head-sub { margin-top: 4px; margin-bottom: 10px; }
.panel-head-sub h3 { margin: 0; font-size: 13px; letter-spacing: .1em;
                     text-transform: uppercase; color: var(--ink-3); }
.preset-wrap { margin-bottom: 22px; }
.preset-grid { display: grid; gap: 9px;
               grid-template-columns: repeat(auto-fit, minmax(215px, 1fr)); }
.preset {
  position: relative; text-align: left; cursor: pointer; font: inherit;
  padding: 12px 14px; border-radius: var(--r-sm);
  display: grid; gap: 4px; color: var(--ink-2);
  background: linear-gradient(158deg, var(--glass-top) 0%, var(--glass-bot) 100%);
  border: 1px solid var(--edge);
  box-shadow: var(--shadow), inset 0 1px 0 var(--edge-hi);
  -webkit-tap-highlight-color: transparent;
}
.preset:hover:not(:disabled) { border-color: var(--cyan); }
.preset b { font-size: 13.5px; color: var(--ink); }
.preset-blurb { font-size: 11.5px; line-height: 1.5; }
.preset em { font-style: normal; font-size: 10.5px; color: var(--ink-3); }
.preset-sev { font-size: 9.5px; letter-spacing: .09em; text-transform: uppercase;
              color: var(--ink-3); }
.preset-sev.sev-critical { color: var(--rose); }
.preset-sev.sev-warning  { color: var(--amber); }
.preset.is-on { opacity: .55; cursor: default; }
.preset-have { position: absolute; top: 11px; right: 12px; font-size: 10px;
               letter-spacing: .08em; text-transform: uppercase; color: var(--lime); }
.preset-dest { margin-top: 13px; max-width: 460px; }
.preset-dest-row { display: flex; gap: 7px; }
.preset-dest-row .field-input { margin-bottom: 0; }

/* ==================================================== touch target sizing
   Audited at 390x844: buttons were landing at 27-32px. Apple's guidance is
   44pt and it is not arbitrary - below it, a thumb hits the gap between two
   controls as often as a control. Desktop keeps the compact sizes; anything
   with a coarse pointer or a narrow screen gets real targets.

   min-height rather than padding, so nothing reflows on desktop and the text
   stays vertically centred by the existing flex/line-height. */
@media (max-width: 900px), (pointer: coarse) {
  .btn,
  .btn-sm,
  .seg-btn,
  .chip,
  .theme-opt,
  button.sort,
  .profile-signout {
    min-height: 44px;
  }
  .btn-sm { padding-left: 13px; padding-right: 13px; }
  /* A checkbox row is a tap target too - the label is what gets aimed at. */
  .pick { min-height: 46px; }
  .cal-day, .cal-pad { height: 42px; }
  /* Sort headers gain height, so give the table a little breathing room
     rather than letting the row crowd the first result. */
  table.log th { padding-bottom: 4px; }
  /* Two actions per card sat shoulder to shoulder at 27px; let them be
     comfortable and wrap if the card is narrow. */
  .user-actions .btn, .alert-card-top .btn { flex: 0 0 auto; }
  /* The pager has its own rule and was the last thing under 44px. */
  .pager button { min-height: 44px; min-width: 44px; }
}

/* What a suggestion already decided, shown in the wizard rail so the two
   remaining questions have context. */
.wiz-preset { display: grid; gap: 4px; margin-bottom: 16px; padding: 10px 11px;
              border-radius: var(--r-sm); background: rgba(80,200,230,.10);
              border: 1px solid rgba(80,200,230,.28); }
.wiz-preset b { font-size: 12.5px; color: var(--ink); }
.wiz-preset span { font-size: 11px; line-height: 1.5; color: var(--ink-2); }
.wiz-preset em { font-style: normal; font-size: 10px; color: var(--ink-3); }

/* A plain checkbox row, for "show every reading". */
.tick { display: flex; align-items: center; gap: 9px; font-size: 12.5px;
        color: var(--ink-2); cursor: pointer; margin-bottom: 14px; }
.tick input { width: 17px; height: 17px; }
