/* The page is a map with a band of controls on top of it. That split is the
   whole layout: everything that is not one of the six things you touch while
   planning lives in a sheet that slides up over the map and goes away again.
   On a wide window the same band stands up as a column on the left -- same
   DOM, same order, one media query at the bottom of the band rules -- because
   a band across 1440 px spent a third of a laptop's height on six buttons
   stretched to 500 px each, and the desktop build is a laptop window. */
:root {
  --bg: #101418;
  --panel: #171c22;
  --panel2: #1e242c;
  --line: #2a323c;
  --text: #e6edf3;
  --dim: #8b98a5;
  --accent: #4da3ff;
  --accent2: #ffb84d;
  --warn: #ff8f6b;
  --ok: #5ad19a;
  --capture: #4da3ff;
  --pad: 14px;
  --bar: 30dvh;
}
* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body {
  font: 14px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  background: var(--bg); color: var(--text);
  -webkit-text-size-adjust: 100%;
}

#app { display: flex; flex-direction: column; height: 100dvh; }
/* The band is allowed to grow past its third when a plan has a lot to say, but
   never past half -- the map is the thing you are working in. */
#bar { flex: 0 0 auto; min-height: var(--bar); max-height: 50dvh; overflow-y: auto;
       background: var(--panel); border-bottom: 1px solid var(--line);
       padding: var(--pad) var(--pad) 10px; display: flex; flex-direction: column; gap: 9px; }
/* The band SCROLLS when it runs out of room; it does not squash what is in it.
   A flex child shrinks by default, so once the band hit its half-screen cap a
   long alert flattened the numbers above it to a nine-pixel stripe -- content
   still there, nothing readable. */
#bar > * { flex-shrink: 0; }
@media (min-width: 1000px) {
  #app { flex-direction: row; }
  #bar { flex: 0 0 320px; min-height: 0; max-height: none; height: 100dvh;
         border-bottom: 0; border-right: 1px solid var(--line); gap: 12px; }
}
#stage { flex: 1 1 auto; position: relative; min-height: 0; }
#map { position: absolute; inset: 0; background: #0b0e11; }
#map[hidden] { display: none; }
#scene, #lidar { position: absolute; inset: 0; width: 100%; height: 100%; display: block;
         background: #0b0e11; cursor: grab; touch-action: none; }
#scene:active, #lidar:active { cursor: grabbing; }
#scene[hidden], #lidar[hidden] { display: none; }

/* ---------- the band ---------- */
.barrow { display: flex; align-items: center; gap: 8px; }
.barrow.foot { margin-top: auto; padding-top: 2px; }
.barrow.foot button { flex: 1 1 auto; }
.barrow.foot #undo { flex: 0 0 auto; font-size: 17px; line-height: 1; padding: 7px 11px; }

.plantitle { flex: 1; min-width: 0; text-align: left; background: none; border: 0; padding: 0;
             font-size: 17px; font-weight: 650; letter-spacing: -0.01em; color: var(--text);
             overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.plantitle:hover { color: var(--accent); }
.plantitle.dirty::after { content: ' •'; color: var(--accent); }
.barnav { display: flex; gap: 6px; flex: none; }
.barnav button { padding: 5px 10px; font-size: 12px; color: var(--dim); }
.tag:empty { display: none; }
.tag { margin-left: 5px; font-size: 10.5px; color: var(--dim);
       border: 1px solid var(--line); border-radius: 999px; padding: 0 5px; }
.tag.ok { color: var(--ok); border-color: transparent; font-size: 13px; padding: 0; }

.tip { margin: 0; color: var(--dim); font-size: 12.5px; }
.tip[hidden] { display: none; }
.tip b { display: block; color: var(--text); font-size: 13px; font-weight: 650; margin-bottom: 6px; }
.tip ol { margin: 0; padding-left: 18px; display: flex; flex-direction: column; gap: 6px; }
.tip li { line-height: 1.4; }
.tip li::marker { color: var(--accent); font-weight: 650; }
.tip li span { color: var(--text); }
.tipgo { display: block; margin-top: 7px; padding: 6px 12px; border-radius: 8px; border: 0; background: var(--accent); color: #fff; font: inherit; font-weight: 600; cursor: pointer; }
.tipgo:hover { filter: brightness(1.1); }

/* Four numbers, because a plan is four questions: is it worth flying, will it
   fit a battery, how sharp is it, and does it see everything. */
.readout { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1px;
           background: var(--line); border: 1px solid var(--line); border-radius: 9px; overflow: hidden; }
.readout div { background: var(--panel2); padding: 7px 4px; text-align: center; }
.readout b { display: block; font-size: 15px; font-weight: 650; font-variant-numeric: tabular-nums; }
.readout span { display: block; font-size: 9.5px; text-transform: uppercase;
                letter-spacing: 0.06em; color: var(--dim); margin-top: 1px; }
.readout.empty { display: block; padding: 14px; text-align: center; color: var(--dim); font-size: 12.5px; }
/* renderReadout hides the empty box, and `display: block` above was beating
   the hidden attribute -- so a fresh plan opened with a grey stripe saying
   nothing under the tip. */
.readout[hidden] { display: none; }

.readout b.bad { color: var(--warn); }
.readout b.ok { color: var(--ok); }
.readout b.dim { color: var(--dim); }


/* ---------- buttons and fields, everywhere ---------- */
button {
  background: var(--panel2); border: 1px solid var(--line); color: var(--text);
  border-radius: 8px; padding: 8px 12px; font: inherit; font-size: 13px; cursor: pointer;
  min-height: 38px;
}
button:hover:not(:disabled) { border-color: #3d4753; }
button.primary { background: var(--accent); border-color: var(--accent); color: #04101d; font-weight: 600; }
button.primary:hover:not(:disabled) { filter: brightness(1.08); }
button.block { width: 100%; }
button:disabled { opacity: 0.4; cursor: default; }
input[type=text], select {
  background: var(--panel); border: 1px solid var(--line); color: var(--text);
  border-radius: 8px; padding: 8px 10px; font: inherit; font-size: 13px; min-height: 38px;
}
input[type=text]:focus, select:focus { outline: none; border-color: var(--accent); }
input[type=range] { width: 100%; accent-color: var(--accent); }
input[type=checkbox] { accent-color: var(--accent); width: 18px; height: 18px; }

.row { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 12px; }
.row > span:first-child { color: var(--dim); font-size: 13px; white-space: nowrap; flex: none; }
.row select { flex: 1; max-width: 200px; }
.ctl { display: flex; align-items: center; gap: 9px; flex: 1; justify-content: flex-end; }
.ctl input[type=range] { max-width: 150px; }
.ctl output { width: 52px; text-align: right; font-variant-numeric: tabular-nums; font-size: 13px; flex: none; }
.hint { color: var(--dim); font-size: 12px; margin: 4px 0 0; }
.hint.gsd { color: var(--ok); margin-bottom: 12px; }
.hint.ok { color: var(--ok); }
.hint.bad { color: var(--warn); }
.warn { border: 1px solid #6d5a35; background: rgba(43,38,26,0.5); color: #ffe4bd;
        border-radius: 8px; padding: 9px 11px; font-size: 12px; margin: 10px 0; }
.warn[hidden] { display: none; }

fieldset.passes { border: 1px solid var(--line); border-radius: 9px; padding: 10px 12px 4px; margin: 4px 0 16px; }
fieldset.passes legend { color: var(--dim); font-size: 11px; text-transform: uppercase;
                         letter-spacing: 0.08em; padding: 0 5px; }
.chk { display: flex; gap: 10px; align-items: flex-start; margin-bottom: 12px; cursor: pointer; }
.chk input { margin-top: 2px; flex: none; }
.chk b { font-weight: 600; font-size: 13px; display: block; }
.chk em { color: var(--dim); font-style: normal; font-size: 11.5px; }

.passlist { margin: 10px 0; font-size: 12px; }
.passrow { display: flex; align-items: baseline; gap: 8px; padding: 4px 0; border-top: 1px solid var(--line); }
.passrow .sw { width: 9px; height: 9px; border-radius: 2px; flex: none; }
.passrow b { font-weight: 600; }
.passrow em { color: var(--dim); font-style: normal; flex: 1; }
.passrow .cnt { color: var(--dim); font-variant-numeric: tabular-nums; }

/* ---------- the map ---------- */
.leaflet-container { background: #0b0e11; font-family: inherit; }
.leaflet-control-attribution { background: rgba(16,20,24,0.8) !important; color: var(--dim) !important;
                               font-size: 10px !important; }
.leaflet-control-attribution a { color: var(--dim) !important; }
/* Leaflet's zoom buttons are white; every other control on the map is this
   panel colour, so the one white box read as a different app. */
.leaflet-bar { border: 1px solid var(--line) !important; border-radius: 8px !important;
               box-shadow: none !important; overflow: hidden; }
.leaflet-bar a, .leaflet-bar a:hover { background: rgba(16,20,24,0.9); color: var(--dim);
               border-bottom-color: var(--line); width: 38px; height: 34px; line-height: 34px; }
.leaflet-bar a:hover { color: var(--text); }
.leaflet-bar a.leaflet-disabled { background: rgba(16,20,24,0.9); color: #3d4753; }
#map.placing { cursor: crosshair; }

/* A tapped point. The ring is the thing itself; the number is how tall you said
   it was, which is the only number either kind of point carries. */
.pt { display: flex; align-items: center; justify-content: center;
      border-radius: 50%; border: 2px solid #04101d; color: #04101d;
      font-size: 10px; font-weight: 700; font-variant-numeric: tabular-nums; }
.pt.capture { background: var(--capture); }
.pt.painted { border-width: 1.5px; box-shadow: none; }
.pt.on { outline: 2px solid #fff; outline-offset: 1px; }
.pt.strike { background: #ff5d5d; color: #fff; }
.gpsdot { background: var(--capture); border: 2px solid #fff; border-radius: 50%;
          box-shadow: 0 0 0 1px rgba(0,0,0,0.5); }

/* Map and 3D side by side. The map answers "where", the 3D answers "at what
   height", and the two questions come up together often enough that switching
   between them is the annoying part. `--split` is where the divider sits, as a
   percentage along whichever axis the panes are stacked on -- left/right on a
   wide screen, top/bottom on a narrow one, driven by the same number. */
#stage { --split: 50%; }
#stage.split #map { inset: 0 calc(100% - var(--split)) 0 0; }
/* A canvas is a replaced element: with `width: auto` it falls back to its
   intrinsic size and ignores `right`, collapsing to nothing. Size it. */
#stage.split #scene, #stage.split #lidar {
                      inset: 0 0 0 var(--split); width: calc(100% - var(--split));
                      border-left: 1px solid var(--line); }
#stage.split #basetabs { right: calc(100% - var(--split) + 10px); }
#stage.split #groundtabs { right: 10px; }
/* Squeeze the map narrower than its own controls and the basemap picker lands
   on the zoom buttons. Below that width it hides rather than overlapping. */
#stage.split.tight #basetabs { display: none; }
/* Everything that belongs to the map stays over the map. `--split` is a
   percentage, so half of it is the middle of the map's half. */
#stage.split #stage.split #mocknote, #stage.split #toast { left: calc(var(--split) / 2); }
#stage.split .pointbar { right: calc(100% - var(--split) + 10px); }
@media (max-width: 720px) {
  /* Stacked, so the map's half is the top one and the horizontal centre is
     unchanged -- it is the vertical position that has to come up. */
  #stage.split #stage.split #mocknote, #stage.split #toast { left: 50%; }
  #stage.split .pointbar { right: 10px; bottom: calc(100% - var(--split) + 10px); }
}

/* A wide invisible grab strip over a hairline: easy to hit with a mouse
   without drawing a chunky bar across the view. */
#splitter { position: absolute; z-index: 600; touch-action: none;
            inset: 0 auto 0 var(--split); width: 13px; margin-left: -6px; cursor: col-resize; }
#splitter[hidden] { display: none; }
#splitter::after { content: ''; position: absolute; inset: 0 6px; background: var(--line); }
#splitter:hover::after, #splitter.dragging::after { background: var(--accent); inset: 0 5px; }

#viewtabs { position: absolute; top: 10px; right: 10px; z-index: 500;
            display: flex; background: rgba(16,20,24,0.9); border: 1px solid var(--line);
            border-radius: 8px; overflow: hidden; }
#viewtabs button { border: 0; border-radius: 0; background: transparent; color: var(--dim);
                   padding: 6px 12px; font-size: 12px; min-height: 0; }
#viewtabs button.on { background: var(--panel2); color: var(--text); font-weight: 600; }
#basetabs { position: absolute; top: 46px; right: 10px; z-index: 500;
            display: flex; background: rgba(16,20,24,0.9); border: 1px solid var(--line);
            border-radius: 8px; overflow: hidden; }
#basetabs[hidden] { display: none; }
#basetabs button { border: 0; border-radius: 0; background: transparent; color: var(--dim);
                   padding: 5px 10px; font-size: 11px; min-height: 0; }
#basetabs button.on { background: var(--panel2); color: var(--text); font-weight: 600; }

/* Where you are, under Leaflet's zoom control, where a locate button lives on
   every other map. */
#findme { position: absolute; top: 84px; left: 10px; z-index: 500;
          width: 40px; height: 40px; padding: 0; min-height: 0;
          display: flex; align-items: center; justify-content: center;
          background: rgba(16,20,24,0.9); color: var(--dim); }
#findme[hidden] { display: none; }
#findme:hover:not(:disabled) { color: var(--text); }
#findme svg { width: 19px; height: 19px; fill: none; stroke: currentColor; stroke-width: 1.6; }
#findme.busy { color: var(--accent); }

/* Whether the flight is drawn over the thing you are tapping. Not whether it
   exists -- the numbers above are always live -- just whether you want several
   hundred waypoints on the map right now. */
.maptoggle { position: absolute; top: 132px; left: 10px; z-index: 500;
             width: 40px; height: 40px; padding: 0; min-height: 0;
             display: flex; align-items: center; justify-content: center;
             background: rgba(16,20,24,0.9); color: var(--dim); }
.maptoggle svg { width: 20px; height: 20px; }
.maptoggle.on { color: var(--accent); border-color: #3d4753; }
.maptoggle[hidden] { display: none; }
/* The route toggle belongs to the map, so it sits with the map's own controls
   on the left. What the flight is drawn OVER belongs to the 3D view, and that
   view draws its altitude scale down the same left edge -- so the group takes
   the right-hand slot the basemap picker leaves empty whenever the 3D is up,
   and looks like the basemap picker because it is the same kind of choice. */
#groundtabs { position: absolute; top: 46px; right: 10px; z-index: 500;
              display: flex; background: rgba(16,20,24,0.9); border: 1px solid var(--line);
              border-radius: 8px; overflow: hidden; }
#groundtabs[hidden] { display: none; }
#groundtabs button { border: 0; border-radius: 0; background: transparent; color: var(--dim);
                     width: 38px; height: 34px; padding: 0; min-height: 0;
                     display: flex; align-items: center; justify-content: center; }
#groundtabs button svg { width: 19px; height: 19px; }
#groundtabs button:hover:not(.on) { color: var(--text); }
#groundtabs button.on { background: var(--panel2); color: var(--accent); }
/* Third in the left stack, and only there when you have lost the site: a
   control offering to take you somewhere you already are is just clutter. */
#recentre { top: 226px; }
/* Each sync belongs to the pane it READS from, so it sits with that pane's own
   controls: on the right of the map under the basemap picker, on the right of
   the 3D under the ground picker. The left stack was seven buttons deep with
   these in it, and on a short window the bottom two fell off the pane. */
/* The link sits ON the divider in a split -- it ties the two panes, so it
   belongs to neither -- and under the view tabs otherwise. */
#linkBtn { top: 82px; left: auto; right: 10px; }
#stage.split #linkBtn { top: 10px; right: auto; left: var(--split); transform: translateX(-50%); }
#linkBtn.on { color: var(--accent); border-color: var(--accent); }
/* Under the two sync buttons on the 3D pane's own right-hand stack, because it
   acts on the 3D and nothing else. */
#looksBtn { top: 118px; left: auto; right: 10px; }
/* Under the wedge switch, in the 3D pane's own right-hand stack. */
#collideBtn { top: 154px; left: auto; right: 10px; }
/* On, this one is a warning and not a preference. */
#collideBtn.on { color: #ff6b6b; border-color: #6a3a3a; }
/* The lift-the-rings fix, under the collision switch. It is only ever on
   screen when there is something wrong, so it is coloured like the news. */
#liftBtn { top: 190px; left: auto; right: 10px; }
/* Paint, at the foot of the 3D stack. Lit while a drag paints rather than
   orbits, because the two feel the same until you look at the button. */
#paintBtn { top: 226px; left: auto; right: 10px; }
#paintBtn.on { color: var(--accent2); border-color: #6d5a35; }
#heatBtn { top: 262px; left: auto; right: 10px; }
#heatBtn.on { color: #ff8a4d; border-color: #6a4a3a; }
#cloudBtn { top: 298px; left: auto; right: 10px; }
.maptoggle.danger { color: #ff6b6b; border-color: #6a3a3a; }
/* Wires belong to both views at once -- they are a hazard, not a backdrop --
   so this is the one .maptoggle that stays put when the 3D comes up. */
#wiresBtn { top: 180px; }

/* Searching is how a mission usually starts -- you know the address before you
   know anything else -- so the box is always open, beside the zoom control.
   It was an icon that unfolded a bar, and the icon was not found. The plugin
   brings its own light theme; these rules only make it this app's. Its width
   is a share of the MAP pane, not the window (hence the container on #map):
   in a split the basemap tabs move up to the pane's top-right corner, and a
   box sized to the window ran into them. */
#map { container-type: inline-size; }
.leaflet-top.leaflet-left .leaflet-control-geocoder {
  position: absolute; top: 0; left: 50px; margin: 10px 0 0 0;
  width: min(380px, calc(100cqw - 260px)); border-radius: 8px;
  background: rgba(16,20,24,0.94); border: 1px solid var(--line); box-shadow: none;
  color: var(--text); display: flex; flex-wrap: wrap; align-items: center; }
.leaflet-control-geocoder.leaflet-control-geocoder-expanded .leaflet-control-geocoder-form {
  display: block; flex: 1; min-width: 0; }
.leaflet-control-geocoder-form input {
  width: 100%; height: 34px; padding: 0 10px; font: inherit; font-size: 13px;
  background: transparent; color: var(--text); border: 0; outline: none; }
.leaflet-control-geocoder-form input::placeholder { color: var(--dim); }
.leaflet-control-geocoder .leaflet-control-geocoder-icon { flex: none; width: 34px; height: 34px;
  background-color: transparent; filter: invert(0.7); }
.leaflet-control-geocoder-alternatives { width: 100%; margin: 0; padding: 0; list-style: none;
  border-top: 1px solid var(--line); }
.leaflet-control-geocoder-alternatives li { padding: 7px 10px; border-bottom: 1px solid var(--line);
  white-space: normal; line-height: 1.35; }
.leaflet-control-geocoder-alternatives li:last-child { border-bottom: 0; }
.leaflet-control-geocoder-alternatives a { color: var(--text); text-decoration: none; }
.leaflet-control-geocoder-alternatives li:hover,
.leaflet-control-geocoder-selected { background: var(--panel2); }
.leaflet-control-geocoder-address-detail { color: var(--dim); font-size: 12px; }
.leaflet-control-geocoder-error { width: 100%; padding: 7px 10px; color: var(--dim); margin: 0; }
.leaflet-control-geocoder-throbber .leaflet-control-geocoder-form input { color: var(--dim); }
#recentre.on, #recentre:hover { color: var(--capture); }
.maptoggle:hover { color: var(--text); }

/* Standing next to the thing rather than looking at it on a map: one big
   target that puts a point where you are, not where you tapped. */
#stage.editing #toast { bottom: 124px; }

/* The point you last touched, over the map because the point is on the map. */
.pointbar { position: absolute; left: 10px; right: 10px; bottom: 10px; z-index: 530;
            display: flex; align-items: center; gap: 9px;
            background: rgba(16,20,24,0.95); border: 1px solid var(--line);
            border-radius: 11px; padding: 8px 10px; }
.pointbar[hidden] { display: none; }
.pdot { width: 11px; height: 11px; border-radius: 50%; flex: none; background: var(--capture); }
.pname { font-size: 12.5px; color: var(--dim); flex: 1; min-width: 0;
         overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pheight { display: flex; align-items: center; gap: 4px; flex: none; }
.pheight button { width: 38px; padding: 0; font-size: 17px; line-height: 1; }
.pheight input { width: 56px; text-align: center; font-variant-numeric: tabular-nums; }
.pheight .unit { color: var(--dim); font-size: 12px; }
.px { width: 38px; padding: 0; font-size: 17px; line-height: 1; color: var(--dim); flex: none; }

/* The pretend receiver (js/gpsmock.js), which only exists when the address bar
   asks for it. Deliberately ugly: nothing here should ever be mistaken for a
   real fix, and on the day it shows up in a screenshot you want to notice. */
.mockpuck { display: flex; align-items: center; justify-content: center;
            background: #c98bff; border: 2px solid #fff; border-radius: 50%;
            box-shadow: 0 0 0 1px rgba(0,0,0,0.6); cursor: grab; }
.mockpuck:active { cursor: grabbing; }
.mockpuck b { font-size: 8px; font-weight: 800; color: #2a1240; letter-spacing: -0.02em; }
#mocknote { position: absolute; top: 10px; left: 50%; transform: translateX(-50%); z-index: 540;
            background: rgba(60,32,88,0.92); border: 1px solid #7a53a8; color: #e9d8ff;
            border-radius: 8px; padding: 5px 10px; font-size: 11px; }

#toast { position: absolute; bottom: 70px; left: 50%; transform: translateX(-50%); z-index: 560;
         background: rgba(16,20,24,0.95); border: 1px solid var(--line); border-radius: 9px;
         padding: 8px 14px; font-size: 12.5px; max-width: 84%; text-align: center; }
#toast[hidden] { display: none; }

/* ---------- the sheet ---------- */
#scrim { position: fixed; inset: 0; z-index: 900; background: rgba(0,0,0,0.5); }
#scrim[hidden] { display: none; }
#sheet { position: fixed; z-index: 910; left: 0; right: 0; bottom: 0; max-height: 86dvh;
         display: flex; flex-direction: column;
         background: var(--panel); border-top: 1px solid var(--line);
         border-radius: 16px 16px 0 0; }
#sheet[hidden] { display: none; }
.sheethead { display: flex; align-items: center; gap: 10px; flex: none;
             padding: 14px var(--pad) 10px; border-bottom: 1px solid var(--line); }
.sheethead h2 { flex: 1; margin: 0; font-size: 15px; font-weight: 650; }
.sheethead button { width: 38px; padding: 0; font-size: 20px; line-height: 1; color: var(--dim); }
.sheetbody { overflow-y: auto; padding: 14px var(--pad) calc(20px + env(safe-area-inset-bottom)); }
.sheetpane[hidden] { display: none; }
@media (min-width: 720px) {
  /* A sheet the width of a laptop is a banner. Keep it a panel. */
  #sheet { left: auto; right: 16px; bottom: 16px; width: 420px; border-radius: 14px;
           border: 1px solid var(--line); max-height: calc(100dvh - 32px); }
  /* And on a laptop it is a window, not a modal: you switch a plan on and look
     at the map, which is behind it. Dragging it is by the header, which is the
     one strip of a window nothing else claims. */
  .sheethead { cursor: grab; touch-action: none; user-select: none; }
  .sheethead button { cursor: pointer; }
  #sheet.dragging { transition: none; }
  #sheet.dragging .sheethead { cursor: grabbing; }
  /* Once it has been dragged the panel is placed by its top-left, so the right
     and bottom anchors have to let go or it stretches between the two. */
  #sheet.moved { right: auto; bottom: auto; }
}

/* ---------- saved plans ---------- */
.planrow { display: flex; gap: 6px; margin-bottom: 8px; }
.planrow input { flex: 1; min-width: 0; }
.planlist { margin: 8px 0; }
.planitem { display: flex; align-items: center; gap: 8px; padding: 8px 10px; border-radius: 8px;
            background: var(--panel2); border: 1px solid var(--line); margin-bottom: 6px; }
.planitem.on { border-color: var(--accent); }
.planmain { flex: 1; min-width: 0; }
.planmain b { display: block; font-size: 13px; font-weight: 600;
              overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.planmain em { font-style: normal; color: var(--dim); font-size: 11px; }
.planitem button { min-height: 32px; padding: 5px 10px; font-size: 12px; }
.planx { width: 32px; padding: 0; font-size: 16px; color: var(--dim); }
.planshow.on { border-color: var(--accent); color: var(--accent); }
/* A capture -- several plans flown as one job -- above the missions in it. */
.plangroup { display: flex; align-items: center; gap: 8px; margin: 12px 0 4px; padding: 0 2px; }
.plangroup b { font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; }
.plangroup em { flex: 1; font-style: normal; color: var(--dim); font-size: 11px;
                overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.plangroup button { min-height: 28px; padding: 4px 9px; font-size: 11px; }
.planitem.grouped { margin-left: 12px; }
.prelist { margin: 6px 0 0; padding-left: 18px; }
.prelist li { margin-bottom: 7px; line-height: 1.45; color: var(--dim); }
.prelist b { color: var(--fg); font-weight: 600; }
.syncrow { display: flex; align-items: center; gap: 10px; margin-top: 10px; }
.syncrow .hint { margin: 0; }

/* ---------- controller ---------- */
.substep { font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em;
           color: var(--dim); margin: 16px 0 8px; display: flex; align-items: center; gap: 8px; }
.substep .n { display: inline-flex; align-items: center; justify-content: center;
              width: 18px; height: 18px; border-radius: 50%;
              background: var(--panel2); color: var(--text); font-size: 11px; }
.devrow { display: grid; gap: 6px; margin-bottom: 8px; }
.devrow select:not([hidden]) { display: block; }
.slots { display: flex; flex-direction: column; gap: 5px; }
.slots .hint { padding: 8px 10px; margin: 0; }
.slot { display: flex; align-items: center; gap: 9px; padding: 8px 10px; border-radius: 8px;
        background: var(--panel2); border: 1px solid var(--line); cursor: pointer; }
.slot.on { border-color: var(--accent); }
.slot.unusable { opacity: 0.45; cursor: default; }
.slotmain { flex: 1; min-width: 0; }
.slotmain b { display: block; font-size: 12.5px; font-weight: 600;
              overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.slotmain em { font-style: normal; color: var(--dim); font-size: 11px; }
.slotmeta { font-size: 11px; color: var(--dim); font-variant-numeric: tabular-nums; }
.slotmeta.bad { color: var(--warn); }
.slotview { min-height: 30px; padding: 4px 10px; font-size: 11.5px; }
.slotview.on { background: var(--accent); border-color: var(--accent); color: #04101d; }
.kmzpick { display: block; margin-top: 8px; font-size: 12px; color: var(--dim); cursor: pointer; }
.kmzpick input { display: none; }
.kmzpick span { display: block; border: 1px dashed var(--line); border-radius: 8px; padding: 9px 10px; }
.kmzpick.loaded span { border-style: solid; border-color: var(--ok); color: var(--ok); }
.xferbox { margin: 8px 0; }
.xfer { display: flex; align-items: center; gap: 8px; padding: 8px 0; border-top: 1px solid var(--line); }
.xsrc, .xdst { flex: 1; min-width: 0; font-size: 12px; }
.xsrc b, .xdst b { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.xsrc em, .xdst em { font-style: normal; color: var(--dim); font-size: 11px; }
.xarrow { color: var(--dim); flex: none; }

details.help { margin-top: 14px; }
details.help summary { cursor: pointer; color: var(--dim); font-size: 12px; }
details.help p, details.help li { color: var(--dim); font-size: 12px; }
code { background: var(--panel2); border-radius: 4px; padding: 1px 5px; font-size: 11.5px; }

/* Thumb-sized where it is a thumb doing the pressing. */
@media (max-width: 720px) {
  /* Half of a narrow screen is not a map, so the panes stack instead. */
  #stage.split #map { inset: 0 0 calc(100% - var(--split)) 0; }
  #stage.split #scene, #stage.split #lidar {
                        inset: var(--split) 0 0 0; width: 100%; height: calc(100% - var(--split));
                        border-left: 0; border-top: 1px solid var(--line); }
  #stage.split #basetabs { right: 10px; }
  /* Stacked panes: the map is on top, so its control keeps the top-right. */
  #stage.split #linkBtn { top: var(--split); left: 50%; transform: translate(-50%, -50%); }
  #splitter { inset: var(--split) 0 auto 0; width: auto; height: 24px;
              margin: -12px 0 0 0; cursor: row-resize; }
  #splitter::after { inset: 11px 0; }
  #splitter:hover::after, #splitter.dragging::after { inset: 10px 0; }

  button, input[type=text], select { min-height: 44px; font-size: 15px; }
  .modes button { padding: 12px 8px; }
  .barnav button { min-height: 36px; }
  .pheight button, .px { width: 42px; }
  .readout b { font-size: 16px; }
  input[type=range] { height: 36px; }
}

/* Every height the flight uses, as a chip you can drag. Over the survey canvas
   rather than in it: a ring is thin to hit and can be behind a building, and a
   chip is always reachable and always legible. Same look as the flat view's,
   because it is the same gesture. */
/* Clear of the left button stack, which is 40 px wide at left:10 and was
   sitting on top of the lower chips. */
#levelchips { position: absolute; left: 60px; top: 0; bottom: 0; width: 0;
              z-index: 480; pointer-events: none; }
.levelchip { position: absolute; left: 0; display: flex; gap: 7px; align-items: baseline;
             white-space: nowrap; padding: 3px 9px 4px; border-radius: 7px;
             background: rgba(16,20,24,0.86); border: 1px solid var(--line);
             color: var(--text); font-size: 11px; cursor: ns-resize;
             pointer-events: auto; touch-action: none; }
.levelchip::before { content: '\2261'; color: var(--dim); margin-right: 1px; }
.levelchip:hover { border-color: #3d4753; background: rgba(22,28,34,0.95); }
.levelchip.dragging { border-color: var(--accent); color: var(--accent); }
.levelchip b { font-weight: 600; font-variant-numeric: tabular-nums; }
.levelchip span { color: var(--dim); }
/* The map pane owns the left edge in split view -- and its buttons with it --
   so there the chips sit just inside the survey pane with nothing to clear. */
#stage.split #levelchips { left: calc(var(--split) + 12px); }

/* The one hover tip for every icon control; see "tooltips" in app.js. */
#hovertip { position: fixed; z-index: 2000; pointer-events: none; max-width: 260px;
            background: #0b0e11; color: var(--text); border: 1px solid var(--line);
            border-radius: 6px; padding: 5px 8px; font-size: 12px; line-height: 1.35; }
#hovertip[hidden] { display: none; }

/* Over the 3D pane while it has nothing to draw. In a split it sits over the
   3D half only, which is the right half on a laptop and the bottom on a phone. */
#empty3d { position: absolute; inset: 0; z-index: 400; display: flex; flex-direction: column;
           align-items: center; justify-content: center; gap: 6px; padding: 24px;
           text-align: center; pointer-events: none; }
#empty3d[hidden] { display: none; }
#empty3d p { margin: 0; max-width: 320px; }
#empty3d button { margin-top: 8px; pointer-events: auto; }
#stage.split #empty3d { left: var(--split); }
@media (max-width: 720px) { #stage.split #empty3d { left: 0; top: var(--split); } }

/* The survey's loading state. A card in the middle of the 3D pane while there
   is nothing to see, a pill in its corner when the wait does not block the
   picture. The bar is a real fraction when bytes are counted and a sweep when
   only time is. */
#load3d { position: absolute; inset: 0; z-index: 450; display: flex; align-items: center;
          justify-content: center; pointer-events: none; background: rgba(11,14,17,0.55); }
#load3d[hidden] { display: none; }
#stage.split #load3d { left: var(--split); }
@media (max-width: 720px) { #stage.split #load3d { left: 0; top: var(--split); } }
#load3d .lcard { width: min(340px, calc(100% - 32px)); padding: 16px 18px 14px; border-radius: 12px;
                 background: rgba(16,20,24,0.94); border: 1px solid var(--line);
                 box-shadow: 0 12px 40px rgba(0,0,0,0.45); }
#load3d .lstage { font-size: 14px; font-weight: 600; color: var(--text); }
#load3d .ldetail { margin-top: 8px; font-size: 12px; color: var(--dim); line-height: 1.4; }
#load3d .ldetail:empty { display: none; }
#load3d .lbar { margin-top: 12px; height: 4px; border-radius: 2px; background: var(--panel2); overflow: hidden; }
#load3d .lbar i { display: block; height: 100%; width: 0; border-radius: 2px;
                  background: linear-gradient(90deg, var(--accent), #8fd0ff); transition: width 0.2s; }
#load3d .lbar.sweep i { width: 35%; animation: load3dSweep 1.3s ease-in-out infinite; }
@keyframes load3dSweep { from { transform: translateX(-100%); } to { transform: translateX(290%); } }
#load3d.minor { background: none; align-items: flex-end; justify-content: flex-start; padding: 0 0 14px 14px; }
#load3d.minor .lcard { width: 260px; padding: 10px 12px; box-shadow: none; }
#load3d.minor .lstage { font-size: 12px; }
#load3d.minor .ldetail { margin-top: 4px; font-size: 11px; }
#load3d.minor .lbar { margin-top: 8px; height: 3px; }

/* Painted / Will be captured, bottom-centre of the 3D pane: the same kind of
   two-way choice as the Map / Split / 3D tabs, so it looks like them. */
#paintView { position: absolute; bottom: 16px; left: 50%; transform: translateX(-50%); z-index: 500;
             display: flex; background: rgba(16,20,24,0.92); border: 1px solid var(--line);
             border-radius: 8px; overflow: hidden; }
#paintView[hidden] { display: none; }
#stage.split #paintView { left: calc(var(--split) + (100% - var(--split)) / 2); }
#paintView button { border: 0; border-radius: 0; background: transparent; color: var(--dim);
                    padding: 7px 14px; font-size: 12.5px; min-height: 0; }
#paintView button.on { background: var(--panel2); color: var(--text); font-weight: 600; }
#paintView button[data-view="painted"].on { color: #9fd8ff; }
#paintView button[data-view="captured"].on { color: #ffb84d; }

/* The detail slider in the band: the one planning control worth having out of
   Advanced, so it gets the band's width. */
.detailrow { margin: 0; }
.detailrow[hidden] { display: none; }
.detailrow .ctl input[type=range] { max-width: none; }
.detailrow .ctl output { width: 84px; color: var(--text); font-weight: 600; }
