/* 3DMap — app chrome. Dark, out of the way; the terrain is the subject. */

:root {
  --bg: #0d1117;
  --panel: #161b22e6;
  --line: #ffffff1f;
  --text: #e6edf3;
  --dim: #9aa7b4;
  --accent: #4c9aff;
  color-scheme: dark;
}

* { box-sizing: border-box; }

/* Any `display` set by a class beats the UA stylesheet's `[hidden] { display: none }`, so a
   flex or grid row stays visible however often you set `hidden`. State it once, with weight. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--bg);
  color: var(--text);
  font: 13px/1.5 ui-sans-serif, system-ui, -apple-system, sans-serif;
  overflow: hidden;
}

/* `fixed`, not `relative; height:100%`: main.js pins width/height/left/top to
   `window.visualViewport` every time it fires, which is what actually tracks iOS Safari's
   chrome (see the sync block near the top of main.js) — plain percentage height does not move
   when that chrome shows or hides. `inset:0` is the fallback for the instant before that JS runs
   and for engines without `visualViewport`. */
#app { position: fixed; inset: 0; }

.view { position: absolute; inset: 0; }
.view[hidden] { display: none; }

/* ---- viewer ------------------------------------------------------------- */

#gl { display: block; width: 100%; height: 100%; touch-action: none; }

/* ---- floating chrome ---------------------------------------------------- */

.hud {
  position: absolute;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: 10px;
  -webkit-backdrop-filter: blur(12px);   /* Safari never recognised the unprefixed property */
  backdrop-filter: blur(12px);
  padding: 10px 12px;
}

/* `env(safe-area-inset-*)` is only non-zero because of `viewport-fit=cover` in index.html — the
   notch, the Dynamic Island and the home-indicator gesture bar all live outside the address bar's
   own reserved space, and without `cover` the browser already keeps content clear of them. Every
   edge-anchored `.hud` piece below adds its inset in so `cover` doesn't put one under the notch. */
#titlebar {
  top: calc(12px + env(safe-area-inset-top)); left: calc(12px + env(safe-area-inset-left));
  display: flex; align-items: center; gap: 12px;
}
#titlebar h1 { font-size: 13px; font-weight: 600; margin: 0; letter-spacing: .02em; }
#region { color: var(--dim); max-width: 26ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---- the controls sheet (T24) -------------------------------------------- */
/* One section at a time, centred over the block, sitting on the tab strip below it. 360 rather
   than T18's 288: the panel is no longer a column down one side that has to leave the terrain
   room, so it can be wide enough for the sun rig's four-item row and a four-up swatch grid.
   The sheet has to sit on top of a strip whose height is set by its own contents, so that height
   is a constant here and `verify_ui.mjs` measures the rendered strip against it — the failure it
   guards is a sheet that overlaps the tabs after a font or an icon changes. */
:root {
  --bar-gap: 12px;      /* strip's inset from the bottom of the viewport */
  --bar: 67px;          /* and its height: 6px padding + a 53px tab + 6px + 2px of border */
  --bar-lift: 10px;     /* clear air between the strip and the sheet above it */
}
#panel {
  /* Where everything above the strip starts. Derived rather than written out twice, because the
     phone breakpoint moves `--bar-gap` and both the `bottom` and the `max-height` below have to
     follow it. */
  --above-bar: calc(var(--bar-gap) + var(--bar) + var(--bar-lift));
  left: 50%; transform: translateX(-50%);
  bottom: calc(var(--above-bar) + env(safe-area-inset-bottom));
  width: 360px;
  /* The head stays put and the body scrolls under it: with four sections the head is the only
     thing that says which one you are looking at, and a sun rig that scrolls it off the top
     leaves an unlabelled sheet. `1fr` is the body — three of the four are `display: none`, so
     the grid only ever has the two rows. */
  display: grid; grid-template-rows: auto minmax(0, 1fr);
  /* `minmax(0, 1fr)`, not the default `auto`: a grid track sized to `auto` takes the widest
     item's min-content width, so one long <option> or a canvas's intrinsic 300px pushes the
     whole column past the panel's own width and everything to the right is clipped. */
  grid-template-columns: minmax(0, 1fr);
  padding: 0;
  border-radius: 16px;
  overflow: hidden;                 /* the rounded corners clip the body's own scroller */
  /* 420 is the design's; the second term is what is actually left above the strip on a short
     window, which is the one that binds on a laptop in landscape. */
  max-height: min(420px, calc(100% - 12px - var(--above-bar)
                              - env(safe-area-inset-top) - env(safe-area-inset-bottom)));
  box-shadow: 0 12px 40px #00000066;
  z-index: 11;
}

#panel-head {
  display: flex; align-items: center; gap: 8px;
  padding: 12px 14px;
  /* Scrolled content passes *under* it, hence the near-opaque fill over `.hud`'s translucent one. */
  background: #161b22f5;
  border-bottom: 1px solid var(--line);
  color: var(--text); font-size: 12.5px; font-weight: 600; letter-spacing: .03em;
}
#panel-title { flex: none; }
/* Takes the slack between the title and the ✕, and gives it up first: the title names the
   section and must never ellipsise, the chip is a bonus. */
#panel-chip { flex: 1 1 auto; text-align: right; }
#panel-close {
  flex: none;
  padding: 4px 9px;
  line-height: 1;
  font-size: 14px;
  color: var(--dim);
  background: none; border: 0;
}
#panel-close:hover { color: var(--text); background: none; }

/* ---- the tab strip (T24) -------------------------------------------------- */
/* Always on screen in the viewer. It is both the way into a section and the only sign that the
   sections exist, so unlike T18's ☰ it has no hidden state to be in. */

#tabbar {
  left: 50%; transform: translateX(-50%);
  bottom: calc(var(--bar-gap) + env(safe-area-inset-bottom));
  display: flex; gap: 4px;
  padding: 6px;
  border-radius: 16px;
  z-index: 12;
}
.tab {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  /* Deliberately over the 44px iOS minimum in both axes: this is the one piece of chrome that is
     never not on screen, and on a phone it is the whole navigation. */
  min-width: 62px; padding: 7px 10px 6px;
  background: none; border: 0; border-radius: 11px;
  color: var(--dim);
  font: inherit; font-size: 10.5px;
  cursor: pointer;
  transition: background .15s, color .15s;
}
.tab:hover { background: #ffffff12; color: var(--text); }
.tab[aria-selected="true"] { background: #4c9aff1f; color: var(--accent); }
.tab[aria-selected="true"]:hover { background: #4c9aff2e; color: var(--accent); }
.tab:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.tab svg { flex: none; }

/* Nothing in the panel may be wider than the panel: a flex item's default `min-width: auto`
   lets a <select> with a long option push the whole row past the right edge. */
#panel select, #panel input[type=range], #panel input[type=datetime-local] { max-width: 100%; }
.field > select, .field > input[type=range] { width: 100%; }
.field .row > * { min-width: 0; }

/* ---- tab panels (T24) ----------------------------------------------------- */
/* One per tab; the three that are not selected are `hidden`, never unmounted — every control
   keeps its id, its value and its listener, and `sun.onChange` goes on writing into a panel
   nobody is looking at. `hidden` is also what keeps a Tab press out of them, which T18 needed
   `inert` for: a collapsed-but-displayed group is still in the tab order, a `display: none` one
   is not. */

.group-body {
  min-height: 0;                 /* bounds the `1fr` track, so this is what scrolls */
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-gutter: stable;
}
.group-inner {
  display: grid; gap: 10px; grid-template-columns: minmax(0, 1fr);
  padding: 12px 14px 16px;
}

/* The one line the head still carries from T18's collapsed headers: what this section is worth
   knowing at a glance, beside its own title. */
.chip {
  font: 10.5px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  color: #6b7684;
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Two rigs in one panel — the sun and the sky — with nothing but this between them. */
.divider { height: 1px; background: #ffffff14; margin: 2px 0; }
.checkrow { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
/* Label and its readout on one line, so the value sits beside the name rather than stealing
   width from the slider under it. */
.label-row { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.export-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.export-row button { padding: 7px 0; text-align: center; }
/* T21: a resolution picker beside its own button, on a second row rather than crowding the PNG /
   URL / FBX row into four unequal columns. */
.video-row { grid-template-columns: 1fr auto; margin-top: 8px; }
.video-row select { padding: 0 8px; }

/* Colour, intensity, ambient — four items on one line. The basis is small because a range
   input's *automatic* minimum size is Chrome's intrinsic ~129px, which is what breaks the flex
   line; at 60px all four fit, and they wrap gracefully rather than overflow if a narrower panel
   ever makes them not. */
.light-row { flex-wrap: wrap; }
.light-row > input[type=range] { flex: 1 1 60px; }
.light-row > .sublabel { flex: none; }

/* ---- soil swatches (T24) -------------------------------------------------- */
/* The eight soils as pictures. `#skirt` is still the control and still holds the value — these
   are `role="radio"` buttons that write into it (`soilGrid()` in main.js) — so `?skirt=`, the
   exporters and the acceptance harness never learn that the dropdown stopped being visible.
   Built in JS from `SOILS`, so the day a ninth swatch ships the grid grows on its own. */

.swatches { display: grid; grid-template-columns: repeat(4, 1fr); gap: 7px; }
.swatch {
  position: relative;
  aspect-ratio: 1;
  padding: 0;
  border: 1.5px solid var(--line); border-radius: 9px;
  background: #ffffff0a center / cover no-repeat;
  cursor: pointer;
  overflow: hidden;
}
.swatch:hover { border-color: #ffffff4d; }
.swatch[aria-checked="true"] { border-color: var(--accent); }
.swatch:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Diagonal hatch for "no skirt" and a flat wash for the plaster: neither is a photograph, so
   neither has a thumbnail to show. */
.swatch-none { background-image: repeating-linear-gradient(45deg, #ffffff14 0 6px, transparent 6px 12px); }
.swatch-plaster { background-color: #f2efe6; }
/* Over the picture, not beside it — a caption row under eight tiles is taller than the tiles. */
.swatch span {
  position: absolute; inset: auto 0 0 0;
  padding: 10px 3px 3px;
  font-size: 8.5px; line-height: 1.1; text-align: center; color: #fff;
  background: linear-gradient(to top, #000000c4, transparent);
}

@media (prefers-reduced-motion: reduce) {
  .tab { transition: none; }
}

/* Above the titlebar's row rather than below the block: the bottom of the screen is the tab
   strip and the sheet now, and a centred toast down there lands on top of one or the other. */
#status {
  top: calc(12px + env(safe-area-inset-top)); left: 50%; transform: translateX(-50%);
  max-width: calc(100% - 24px - env(safe-area-inset-left) - env(safe-area-inset-right));
  padding: 8px 14px;
  z-index: 13;
  transition: opacity .2s;
  display: flex; align-items: center; gap: 8px;
}
#status[hidden] { display: none; }
#status.error { border-color: #f8514966; color: #ff9c94; }
#status-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#status-cancel {
  flex: none; width: 20px; height: 20px; padding: 0; border: none; border-radius: 50%;
  background: transparent; color: inherit; opacity: .65;
  font-size: 13px; line-height: 1; cursor: pointer;
}
#status-cancel:hover { opacity: 1; background: #ffffff1a; }
#status-cancel[hidden] { display: none; }

/* ---- picker ------------------------------------------------------------- */

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

/* The shift-drag rectangle: a plain overlay, so dragging never touches a map layer. */
#drag-rect {
  position: absolute;
  border: 1.5px solid var(--accent);
  background: #4c9aff2e;
  pointer-events: none;
  z-index: 5;
}
#drag-rect[hidden] { display: none; }

#search-panel {
  top: calc(12px + env(safe-area-inset-top)); left: calc(56px + env(safe-area-inset-left));
  width: 340px;
  padding: 8px;
  z-index: 10;
}
#search { width: 100%; }

#results { list-style: none; margin: 6px 0 0; padding: 0; max-height: 45vh; overflow-y: auto; }
#results:empty { margin: 0; }
#results li {
  padding: 6px 8px;
  border-radius: 6px;
  cursor: pointer;
  display: grid;
}
#results li:hover { background: #ffffff1f; }
#results li span { color: var(--dim); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

#picker-actions {
  bottom: calc(12px + env(safe-area-inset-bottom)); left: 50%; transform: translateX(-50%);
  display: flex; align-items: center; gap: 10px;
  z-index: 10;
}
#selection { color: var(--dim); font: 11px/1.4 ui-monospace, Menlo, monospace; }

/* MapLibre's own chrome, toned down to match. */
.maplibregl-ctrl-attrib { font-size: 10px; }
.maplibregl-ctrl-attrib.maplibregl-compact { background: var(--panel); }

/* ---- controls ----------------------------------------------------------- */

/* Same `minmax(0, 1fr)` as #panel, one level down: without it a .field's single auto track is
   sized to its widest child's min-content and the dials spill out of the panel. */
.field { display: grid; gap: 5px; grid-template-columns: minmax(0, 1fr); }
.field > * { min-width: 0; }
/* Not `.field > label`: the exaggeration and skirt labels now sit inside a `.label-row` beside
   their readout. Uppercase and 11px are gone — the group titles carry the hierarchy now, and a
   panel where nine labels all shout is the thing T18 set out to fix. */
.field label:not(.inline) { color: var(--dim); font-size: 10.5px; letter-spacing: .03em; }
.field .row { display: flex; align-items: center; gap: 8px; }
.field output { font: 11px/1 ui-monospace, Menlo, monospace; min-width: 4ch; text-align: right; color: var(--text); }

select, button, input[type=text], input[type=search] {
  font: inherit;
  color: var(--text);
  background: #ffffff14;
  border: 1px solid var(--line);
  border-radius: 7px;
  padding: 6px 9px;
}
button { cursor: pointer; }
button:hover, select:hover { background: #ffffff24; }
button.primary { background: var(--accent); border-color: transparent; color: #06121f; font-weight: 600; }
button.primary:hover { background: #6badff; }
button:disabled { opacity: .45; cursor: default; }

input[type=range] { flex: 1; accent-color: var(--accent); }

.note { color: var(--dim); font-size: 11px; }
.note.warn { color: #f0c674; }

label.inline {
  display: flex; align-items: center; gap: 6px;
  color: var(--text); font-size: 12px; text-transform: none; letter-spacing: 0;
  cursor: pointer;
}
label.inline input { accent-color: var(--accent); margin: 0; }
.sublabel { color: var(--dim); font-size: 10.5px; min-width: 4ch; }
/* A row whose control is disabled but still in place — the sun-disc size, when the disc is off. */
.row.off { opacity: .45; }

/* ---- sun dials (T9) ------------------------------------------------------ */
/* Ports of SunTopClass / SunProfileClass: drawn on a canvas, dragged with the pointer. */

.dials { display: flex; gap: 6px; }
.dials canvas {
  flex: 1 1 0;
  height: 96px;
  min-width: 0;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #ffffff0a;
  cursor: grab;
  touch-action: none;          /* the drag is ours, not the browser's scroll */
  /* iOS otherwise starts a selection / callout on a press-and-hold and takes the finger with
     it mid-drag. `touch-action` alone does not cover that — see the touch path in sun.js. */
  user-select: none;
  -webkit-user-select: none;
  -webkit-touch-callout: none;
}
.dials canvas:active { cursor: grabbing; }

#sun-color, #sky-ground {
  flex: none;
  width: 30px; height: 24px;
  padding: 2px;
  background: #ffffff14;
  border: 1px solid var(--line);
  border-radius: 6px;
}
#sun-time { flex: 1; min-width: 0; font-size: 11px; padding: 4px 6px; }
/* A full-width grid item, so centring it puts the readout under the pair of dials it describes
   rather than against the left-hand one. */
#sun-readout { min-width: 0; text-align: center; color: var(--dim); }

/* ---- phone ---------------------------------------------------------------- */
/* One breakpoint, and it is about width, not about touch: the chrome here is laid out in
   fixed pixels against a 1280px window, and below ~560px the pieces meet in the middle.
   `main.js` opens collapsed under the same 560px — the acceptance viewports start at 640. */

@media (max-width: 560px) {
  #tabbar { gap: 1px; padding: 5px; }
  .tab { min-width: 54px; padding-left: 5px; padding-right: 5px; }
  /* Clamped rather than fixed: 340 is the design's, but it has to fit a 320px phone too, and a
     sheet that is 12px off both edges reads as deliberate at any width. The height cap is a
     fraction, not 420px — 420 of an iPhone SE's 667 is most of the screen, and the point of
     moving the controls to the bottom was to leave the block visible above them. */
  /* The strip is centred and ~264px wide, so it clears the left/right safe-area insets on its
     own; only its bottom gap grows, to sit above the home indicator rather than under it. The
     sheet reads `--bar-gap` too, so it rises with it. */
  :root { --bar-gap: 16px; }
  #panel {
    width: min(340px, calc(100% - 24px));
    max-height: min(64%, calc(100% - 12px - var(--above-bar)
                              - env(safe-area-inset-top) - env(safe-area-inset-bottom)));
  }
  #titlebar {
    right: calc(12px + env(safe-area-inset-right));
    max-width: none;
  }
  #region { max-width: 12ch; }

  /* Under the titlebar rather than centred on it: at 390px the two overlap. */
  #status {
    top: calc(60px + env(safe-area-inset-top));
    left: calc(12px + env(safe-area-inset-left)); right: calc(12px + env(safe-area-inset-right));
    transform: none; text-align: center;
  }

  /* Both of these are 340px wide or centred on a `left: 50%`, which overflows a 360px phone. */
  #search-panel {
    width: auto; left: calc(56px + env(safe-area-inset-left)); right: calc(12px + env(safe-area-inset-right));
  }
  #picker-actions {
    left: calc(12px + env(safe-area-inset-left)); right: calc(12px + env(safe-area-inset-right));
    transform: none;
    flex-wrap: wrap; justify-content: center;
  }

  /* A finger is coarser than a cursor: a 96px dial gives the elevation arc barely 60px of
     usable arc, and the azimuth compass a 30px radius. */
  .dials canvas { height: 120px; }
}
