:root{
  --bg:#0e1116; --panel:#0e1218; --panel2:#1b222b; --line:#37424f;
  --ink:#e7edf5; --dim:#8fa0b4; --accent:#4fc3f7; --warn:#ffb300; --bad:#ff5252;
  --live:#ff3b30;   /* live HUD readouts */
  --good:#66bb6a;
}
*{box-sizing:border-box}
html,body{height:100%;margin:0}
body{
  background:var(--bg); color:var(--ink);
  font:13px/1.45 ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;
  overflow:hidden;
}
#app{display:flex;height:100vh}

/* ---------------- panel ---------------- */
#panel{
  width:352px; flex:0 0 352px; height:100vh; overflow-y:auto;
  background:var(--panel); border-right:1px solid var(--line); padding:14px 14px 40px;
}
#panel header h1{font-size:15px;margin:0 0 2px;letter-spacing:.2px}
#panel header .sub{margin:0 0 14px;color:var(--dim);font-size:11px}

/* ---- group identity -----------------------------------------------------
   The panel and the cards were #151a21 and #1b222b - six points of lightness
   apart - so the groups dissolved into the background. Each now carries its
   own hue.

   HUE separates them; LIGHTNESS deliberately does not. Every group background
   sits at roughly the same luminance, so --ink and --dim keep the same contrast
   in all seven: the dim label colour measures about 5.9:1 against each of these
   backgrounds, comfortably past the 4.5:1 wanted for small text, and the white
   value readouts are far above it. Tinting by brightness instead would have
   made some cards easier to read than others, which is the trap here.

   Each group also drives its slider accent-color and heading from the same
   token, so a control visually belongs to its card. The accents are picked
   bright enough to carry a 2px slider track against the tinted ground.

   Measured, not eyeballed. The panel was darkened to #0e1218 at the same time,
   because a first pass left the nearest card only 19 channel-steps off it -
   WORSE than the 24 that made the old flat cards merge. Final numbers:

     every card vs the panel      min 49 channel-steps  (was 24, and merged)
     closest two cards            25                    (cam/budget)
     label text on any card       >= 5.19:1             (AA small text: 4.5)
     value text on any card       >= 11.77:1
     accent on its own card       >= 7.37:1             (UI marks: 3.0)
     luminance spread, all cards  0.0195 .. 0.0257      (tight = even reading) */
.group{
  background:var(--g-bg,var(--panel2)); border:1px solid var(--line);
  border-left:3px solid var(--g-accent,var(--accent)); border-radius:8px;
  padding:11px 11px 13px; margin-bottom:11px;
  box-shadow:0 1px 3px #0006;
}
.group h2{
  font-size:11px; text-transform:uppercase; letter-spacing:.9px;
  color:var(--g-accent,var(--accent)); margin:0 0 10px; font-weight:600;
}
.group .fld input[type=range]{accent-color:var(--g-accent,var(--accent))}

.g-controls{--g-bg:#182a42; --g-accent:#7cc0ff}   /* blue    */
.g-tx      {--g-bg:#2a2140; --g-accent:#c4a6ff}   /* violet  */
.g-weed    {--g-bg:#18321f; --g-accent:#7ede9c}   /* green   */
.g-env     {--g-bg:#0d3038; --g-accent:#5fe0e8}   /* teal    */
.g-cam     {--g-bg:#3a1f31; --g-accent:#f5a3d2}   /* rose    */
.g-model   {--g-bg:#33290f; --g-accent:#f0c96a}   /* amber   */
.g-budget  {--g-bg:#3d1d1d; --g-accent:#ff9e94}   /* red     */

.fld{display:block;margin:0 0 10px;font-size:11px;color:var(--dim)}
.fld input[type=range]{width:100%;margin:5px 0 2px;accent-color:var(--accent)}
.fld select,.fld input[type=file]{
  width:100%;margin-top:4px;background:#00000059;color:var(--ink);
  border:1px solid var(--line);border-radius:5px;padding:5px 6px;font-size:12px;
}
.fld output{display:block;color:var(--ink);font-size:11px;font-variant-numeric:tabular-nums}
.fld output em{color:var(--warn);font-style:normal}

.row{margin:8px 0 0}
.row.btns{display:flex;gap:7px}
button{
  flex:1;background:#ffffff14;color:var(--ink);border:1px solid var(--line);
  border-radius:5px;padding:6px 8px;font-size:11px;cursor:pointer;
}
button:hover{background:#ffffff24}
button.danger{background:#5a2020;border-color:#7a2c2c}
button.danger:hover{background:#7a2c2c}
.chk{display:flex;align-items:center;gap:6px;color:var(--dim);font-size:11px;cursor:pointer}
.hint{color:var(--dim);font-size:10.5px;margin:8px 0 0;line-height:1.4}
.hint em{color:var(--ink);font-style:normal}

/* ---------------- sticks ---------------- */
.sticks{display:flex;gap:11px;justify-content:space-between}
.stickwrap{flex:1}
.stick{
  position:relative;width:100%;aspect-ratio:1;background:#0c1015;
  border:1px solid var(--line);border-radius:9px;cursor:grab;touch-action:none;
  overflow:hidden;
}
.stick:active{cursor:grabbing}
.crosshair{position:absolute;inset:0}
/* Wider and brighter. At 1px and #1e2731 these measured 1.26:1 against the
   stick face - effectively invisible. 3px at #5a7590 gives 3.98:1, past the
   3:1 wanted for a UI line, while staying well below the knob in weight so the
   stick position remains what the eye lands on.

   Centred with a transform: a 3px line pinned at top:50% would otherwise hang
   one pixel below true centre, which on a control whose whole job is showing
   neutral is exactly the wrong pixel to lose. */
.crosshair::before,.crosshair::after{
  content:"";position:absolute;background:#5a7590;
}
.crosshair::before{left:0;right:0;top:50%;height:3px;transform:translateY(-50%)}
.crosshair::after{top:0;bottom:0;left:50%;width:3px;transform:translateX(-50%)}
.knob{
  position:absolute;width:26%;aspect-ratio:1;border-radius:50%;
  background:radial-gradient(circle at 35% 32%,#7fd6ff,#1f7fa8);
  border:1px solid #0a1218;left:50%;top:50%;transform:translate(-50%,-50%);
  box-shadow:0 2px 7px #0008;
}
.sticklabel{display:flex;flex-direction:column;gap:1px;margin-top:5px;text-align:center}
.sticklabel .ax{color:var(--dim);font-size:9.5px}

.readouts{display:grid;grid-template-columns:1fr 1fr;gap:4px 9px;margin-top:11px}
.readouts.wide{grid-template-columns:1fr 1fr}
.readouts div{display:flex;justify-content:space-between;align-items:baseline;
  border-bottom:1px dotted #232c37;padding-bottom:2px}
.readouts label{color:var(--dim);font-size:10.5px}
.readouts span{font-variant-numeric:tabular-nums;font-size:11.5px}

.verdict{
  margin-top:10px;padding:7px 9px;border-radius:5px;font-size:11px;
  background:#00000059;border:1px solid var(--line);color:var(--dim);
}
.verdict.ok{border-color:#2e5b34;color:var(--good)}
.verdict.bad{border-color:#6b2626;color:var(--bad)}

/* ---------------- segmented ---------------- */
.seg{display:flex;border:1px solid var(--line);border-radius:6px;overflow:hidden}
/* four modes now, in a 352px panel: trim the padding so "Underwater" still
   fits on one line rather than wrapping the segment into two rows */
.seg button{border:0;border-right:1px solid var(--line);border-radius:0;
  background:#00000038;padding:6px 3px;font-size:10.5px;white-space:nowrap;min-width:0}
.seg button:last-child{border-right:0}
.seg button.on{background:var(--accent);color:#08202b;font-weight:600}

/* ---------------- viewport ---------------- */
#viewport{flex:1;position:relative;min-width:0}
#gl{width:100%;height:100%;display:block}

#hud{position:absolute;left:0;top:0;padding:12px;pointer-events:none;
  font-variant-numeric:tabular-nums;font-size:11px;color:#cfe6f5;text-shadow:0 1px 3px #000}
#hudAttitude{
  position:relative;width:86px;height:86px;border:1px solid #4a5a6a80;border-radius:50%;
  overflow:hidden;background:#0b131b66;margin-bottom:7px;
}
#hudHorizon{position:absolute;left:-40%;right:-40%;height:200%;top:-50%;
  background:linear-gradient(#2b4a63 50%,#3b2f26 50%);opacity:.85}
#hudMark{position:absolute;left:50%;top:50%;width:34px;height:1px;background:#ffd54f;
  transform:translate(-50%,-50%);box-shadow:0 0 4px #000}
/* Live readouts in red. Explicitly text-shadow:none rather than just dropping
   the declaration - #hud sets a shadow and it would otherwise inherit. */
#hudText{white-space:pre;line-height:1.5;color:var(--live);
  text-shadow:none;font-weight:600}

#camtip{position:absolute;right:12px;bottom:10px;color:#8fa0b4;font-size:10.5px;
  pointer-events:none;text-shadow:0 1px 3px #000}

footer .hint strong{color:var(--warn);font-weight:600}

/* ---- heading tape -------------------------------------------------------
   Yaw had no visual indicator at all: the rudder is the one control whose
   effect you cannot see on the boat itself from astern, because a submarine
   turning flat gives you no attitude cue to read it by. A sliding tape shows
   both the heading and, by how fast it moves, the rate of turn. */
#hudCompass{position:relative;width:200px;height:26px;margin-bottom:7px;overflow:hidden;
  background:#0b131b66;border:1px solid #4a5a6a80;border-radius:4px}
#hudTape{position:absolute;top:0;left:0;height:100%;will-change:transform}
#hudTape .ct{position:absolute;top:0;height:100%;transform:translateX(-50%)}
#hudTape .ct i{position:absolute;left:50%;top:3px;width:1px;height:5px;
  background:#7fa8c4;transform:translateX(-50%)}
#hudTape .ct.maj i{height:8px;background:#cfe6f5}
#hudTape .ct b{position:absolute;left:50%;top:12px;transform:translateX(-50%);
  font-size:9.5px;font-weight:600;color:#cfe6f5;line-height:1}
#hudTape .ct.card b{color:#4fc3f7}
#hudLubber{position:absolute;left:50%;top:0;width:1px;height:100%;background:#ffd54f;
  transform:translateX(-50%);box-shadow:0 0 4px #000}
#hudLubber::after{content:"";position:absolute;left:50%;top:0;transform:translateX(-50%);
  border-left:4px solid transparent;border-right:4px solid transparent;
  border-top:5px solid #ffd54f}

/* A latched pump button. Held buttons showed their state through your finger
   being on them; a latching switch has to say so itself, or a pump left running
   is invisible until the tank has emptied. */
button.latched{background:#ffb30030;border-color:var(--warn);color:#ffd98a;font-weight:600}
button.latched:hover{background:#ffb30045}
button.danger.latched{background:#ff525238;border-color:var(--bad);color:#ffb9b9}
button.danger.latched:hover{background:#ff525252}

/* ---- help ---------------------------------------------------------------
   An iframe rather than injected markup: help.html is a whole standalone page,
   so it brings its own styling and cannot leak into or inherit from the panel.
   No markdown parser, no duplicated copy, and the file still opens on its own. */
[hidden]{display:none!important}
.titlerow{display:flex;align-items:center;gap:8px}
.titlerow h1{flex:1}
#btnHelp{flex:0 0 auto;padding:4px 9px;font-size:11px;border-color:var(--accent);
  color:var(--accent);background:#4fc3f71f}
#btnHelp:hover{background:#4fc3f733}
#helpModal{position:fixed;inset:0;z-index:100;display:flex;align-items:center;
  justify-content:center;padding:24px;background:#000000b8}
.help-card{display:flex;flex-direction:column;width:min(820px,100%);height:min(88vh,100%);
  background:var(--panel);border:1px solid var(--line);border-radius:10px;overflow:hidden;
  box-shadow:0 18px 48px #000a}
.help-card header{display:flex;align-items:center;gap:10px;padding:9px 12px;
  border-bottom:1px solid var(--line);background:#182a42}
.help-card header strong{font-size:13px}
.help-card header .dim{flex:1;color:var(--dim);font-size:11px}
.help-card header button{flex:0 0 auto;padding:4px 12px;font-size:11px}
#helpFrame{flex:1;width:100%;border:0;background:#0e1218}

/* ---- load feedback ------------------------------------------------------
   The full hull is 13.8 MB. On a 10 Mbit/s line that is thirteen seconds of
   empty lake, which reads as a broken page rather than a download. */
#loader{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);z-index:40;
  pointer-events:none;background:#0e1218e6;border:1px solid var(--line);border-radius:9px;
  padding:14px 20px;min-width:300px;text-align:center;box-shadow:0 12px 34px #0009}
#loaderText{display:block;font-size:12px;color:var(--ink);font-variant-numeric:tabular-nums}
#loader .track{display:block;height:4px;margin-top:10px;border-radius:3px;
  background:#00000066;overflow:hidden}
#loaderBar{display:block;height:100%;width:0;background:var(--accent);
  border-radius:3px;transition:width .12s linear}
/* no Content-Length (chunked or on-the-fly compression): sweep instead of lying */
#loader.indeterminate #loaderBar{width:38%;transition:none;animation:sweep 1.1s ease-in-out infinite}
@keyframes sweep{0%{margin-left:-38%}100%{margin-left:100%}}
