*, *::before, *::after { box-sizing: border-box; } /* `*` alone never matches pseudo-elements */
html, body { margin: 0; height: 100%; }
html { color-scheme: light dark; } /* dark-theme native form controls (number inputs, sliders) with the page */
/* height: 100% above, never 100vh: viewport units ignore scrollbars, so a horizontal one
   leaves the body taller than the room left for it and overflows the other axis */
body {
  display: flex;
  font: 14px/1.5 system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
  color: #1a1a1a;
}

/* left column: brand, map, and controls scroll together as one column */
#left {
  flex: 0 0 400px;
  width: 400px;
  overflow-y: auto;
  border-right: 1px solid #ddd;
  background: #fafafa;
}
/* wordmark + its definition on one baseline row: the definition floats to the
   right of the h1 and wraps within its own column rather than under the wordmark */
#brand { padding: 14px 16px 12px; border-bottom: 1px solid #eee; display: flex; align-items: baseline; gap: 10px; }
#brand h1 { margin: 0; }
#brand .sub { margin: 0; flex: 1; min-width: 0; }
/* Square, because the footprint it frames is square-ish (hex 1:0.87, circle 1:1) and fitBounds
   fits the LIMITING axis: at 42vh the aspect ran 1.20 at a 700px window to 0.52 at 1600px, so
   the same tile either filled the frame or floated in dead space depending on window height. */
#map { aspect-ratio: 1; }
#maphint { margin: 6px 0 0; } /* just under the map; panel padding already aligns it with the section content */
#panel { padding: 16px; }

/* right column: 3D preview, then a results footer */
#right { flex: 1; display: flex; flex-direction: column; min-width: 0; }
/* clips: the canvas must not be able to scroll the document, which resizes the pane sizing it */
#preview { flex: 1; min-height: 0; overflow: hidden; background: #0e0e12; }
#preview canvas { display: block; }
#results { flex: 0 0 auto; padding: 10px 16px; border-top: 1px solid #ddd; background: #fafafa; }
#resultbar { display: flex; align-items: center; gap: 16px; }
/* progress fills the left; attribution sits at right, wrapping below when tight */
#resultbar #progress { order: -1; flex: 1 1 auto; min-width: 0; margin: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
#resultbar #credits { flex: 0 1 auto; min-width: 0; margin: 0; text-align: right; line-height: 1.35; }
#resultbar .primary { flex: 0 0 auto; width: auto; margin: 0; }
#resultbar #ghlink { flex: 0 0 auto; display: inline-flex; align-items: center; color: #666; }
#resultbar #ghlink:hover { color: #1a1a1a; }

/* Below 700px there's no room for two 400px+ columns side by side — the fixed-width #left
   used to just push #right off-screen instead of shrinking. Stack them: #left reverts to
   normal document flow (its own overflow-y:auto only matters when a flex row gives it a
   capped height), and #right gets an explicit height since #preview's flex:1 needs a
   definite parent size to fill rather than collapsing to its content's zero height. */
@media (max-width: 700px) {
  body { flex-direction: column; }
  #left {
    flex: 0 0 auto; width: auto; overflow-y: visible;
    border-right: none; border-bottom: 1px solid #ddd;
  }
  #right { flex: 0 0 60vh; height: 60vh; }
}

/* ONE heading level in the panel: Region, Tile, Water, Trail and Advanced are peers, and
   Advanced is a peer that happens to be collapsed — so its summary IS its heading and wears the
   same style. Uppercase and letterspaced so a heading reads as a heading before it is read as
   words. A second rank would have to earn itself; nothing here is a subsection of anything else. */
#panel h2, #panel summary {
  font-size: 15px; font-weight: 700;
  letter-spacing: 0.05em; text-transform: uppercase; color: #555;
}
#panel h2 { margin: 0 0 10px; }
#panel summary { margin: 0; } /* the controls below bring their own top margin */
.sub { margin: 4px 0 0; color: #666; }

/* A hairline above every section but the first is the whole separator — peers need no boxes.
   #settings is a bare wrapper, so its sections are matched through it rather than as siblings
   of Region, which is why this is a descendant selector and the exemption is the child one. */
#panel section, #panel details { padding: 14px 0 0; margin: 0 0 18px; border-top: 1px solid #e2e2e2; }
#panel > section:first-child { padding-top: 0; border-top: none; }
label { display: block; font-weight: 600; margin-bottom: 4px; }
select { width: 100%; padding: 6px; }
#preset { margin-bottom: 10px; } /* separate the region picker from the map; native select is styled by `select` + `color-scheme` above */

/* The loaded trail's filename and its two buttons. The filename can be arbitrarily
   long, so it takes the slack and ellipsizes; the buttons never shrink.
   The [hidden] rule is mandatory, not belt-and-braces: an author `display` beats the
   UA's `[hidden] { display: none }`, so setting `display: flex` here silently defeats
   the attribute this row's whole visibility depends on — it stayed on screen with no
   trail loaded, showing "Fit to trail" for a trail that did not exist. #waterWarn
   dodges this by declaring no `display` at all; a flex row cannot, so it re-states the
   UA rule at author level instead. */
#trailRow { display: flex; align-items: center; gap: 8px; margin-top: 8px; font-size: 12px; }
#trailRow[hidden] { display: none; }
#trailName { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
#trailRow button { flex: none; }

.hint { margin: 4px 0 12px; font-size: 12px; color: #888; }

input[type="range"] { width: 100%; }
input.num { width: 72px; padding: 4px; }
#scale { width: 96px; padding: 4px; }
summary { cursor: pointer; }
/* Checkbox rows are the one label that isn't a caption for the control below it — flex so the
   box and its text sit on one baseline with a gap, and lighter than a heading-weight label. */
label.check { display: flex; align-items: center; gap: 8px; font-weight: 500; margin: 10px 0 4px; }
label.check input { margin: 0; }

/* The ? itself. Its bubble is a page-level popover positioned by controls.wireHelp, not a child
   of this button — these rules only style the affordance. :focus-visible matches the styling to
   the JS, which opens on focus as well as hover so keyboard and touch both reach it. */
.help {
  display: inline-flex; align-items: center; justify-content: center;
  width: 17px; height: 17px; padding: 0; margin-left: 6px; vertical-align: middle;
  border-radius: 50%; border: 1px solid #bbb; background: transparent;
  font-size: 11px; line-height: 1; color: #777; cursor: help;
}
.help:hover, .help:focus-visible { background: #2d6cdf; border-color: #2d6cdf; color: #fff; }
/* The row owns the vertical spacing; a label keeping its own asymmetric margin would have its
   MARGIN box centered by align-items, sitting the text off-center from the ? beside it. */
.labelrow { display: flex; align-items: center; margin: 10px 0 4px; }
.labelrow > label { margin: 0; }
/* Outcome cards. Real radios, hidden with opacity rather than display:none so they keep focus
   and the native arrow-key group behavior; :has() carries checked/focus out to the card border,
   so selection needs no JS. */
.cards { display: flex; gap: 8px; margin: 10px 0 4px; }
.card {
  position: relative; flex: 1 1 0; display: flex; flex-direction: column; gap: 2px;
  padding: 8px 6px 6px; border: 1px solid #d5d5d5; border-radius: 6px; background: #fff;
  cursor: pointer; text-align: center; font-weight: 500; font-size: 12px; line-height: 1.25;
}
.card input { position: absolute; opacity: 0; pointer-events: none; }
.card svg { width: 100%; height: auto; display: block; margin-bottom: 2px; }
.card small { font-weight: 400; font-size: 11px; line-height: 1.3; color: #888; }
.card:hover { border-color: #999; }
.card:has(input:checked) { border-color: #2d6cdf; box-shadow: inset 0 0 0 1px #2d6cdf; }
.card:has(input:focus-visible) { outline: 2px solid #2d6cdf; outline-offset: 1px; }
/* The band's slate for printed water, the preview's glacial aqua for the loose part — the same
   two colors those things have on screen, so the cards teach the preview's vocabulary
   (colors.js: the water band and INLAY_COLOR, converted to sRGB hex). */
.card .land { fill: #b5b0a8; }
.card .water { fill: #295c8c; }
.card .part { fill: #3d949e; }
/* Lives on <body>, not in the column — see controls.wireHelp. pointer-events:none so the bubble
   can never take the hover away from the button that opened it. */
#tip {
  /* inset/margin/border reset the UA's [popover] rules, which center it in the viewport. */
  position: fixed; inset: auto; margin: 0; border: 0; z-index: 10;
  max-width: min(300px, calc(100vw - 16px)); /* the JS clamps left/top; this bounds the width */
  padding: 7px 9px; border-radius: 4px; pointer-events: none;
  background: #26262a; color: #ececec; font: 400 12px/1.45 system-ui, sans-serif;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.4);
}

/* Amber banner, not a `.hint`: hints are gray chrome the eye skips, and this one has to be
   noticed from across the panel. No `display` here — that would defeat the `hidden` attribute. */
#waterWarn, #trailWarn {
  margin: 10px 0; padding: 8px 10px; font-size: 12px; line-height: 1.45;
  color: #6b4e00; background: #fff8e1; border-left: 3px solid #e0a800; border-radius: 3px;
}

/* Floats over the canvas rather than taking layout space — the preview pane is the one part of
   the page that should stay as large as it can. `position` is set on #preview by preview.js. */
#viewmodes {
  position: absolute; top: 8px; left: 8px; z-index: 2;
  display: flex; gap: 1px; border-radius: 5px; overflow: hidden;
  background: rgba(0, 0, 0, 0.55); font-size: 12px;
}
#viewmodes button {
  margin: 0; padding: 4px 9px; border: 0; border-radius: 0;
  background: transparent; color: #d8d8d8; cursor: pointer; white-space: nowrap;
}
#viewmodes button:hover { background: rgba(255, 255, 255, 0.12); color: #fff; }
#viewmodes button.on { background: #2d6cdf; color: #fff; font-weight: 600; }

/* Opposite corner from #viewmodes, and clear of the probe at bottom right. Unlike the probe this
   one takes pointer events — it is the Reset view control — so an orbit drag cannot begin inside
   it. A 48px dead zone in a corner is the accepted price of one control instead of two. */
#rose {
  position: absolute; top: 8px; right: 8px; z-index: 2;
  width: 48px; height: 48px; padding: 0; border: 0; border-radius: 50%;
  background: rgba(0, 0, 0, 0.55); color: #d8d8d8; cursor: pointer;
}
#rose:hover { background: rgba(0, 0, 0, 0.75); color: #fff; }
#rose svg { display: block; width: 100%; height: 100%; overflow: visible; }
#rose .disc { fill: none; stroke: currentColor; stroke-width: 1; opacity: 0.45; }
/* Unrotated and unsquashed: preview.js moves these, it never transforms them, because the glyph
   is the part that has to stay readable when the disc flattens. */
#rose text {
  fill: currentColor; opacity: 0.65; font: 600 11px/1 system-ui, sans-serif;
  text-anchor: middle; dominant-baseline: central;
}
#rose .n { opacity: 1; }

/* Only wraps the scale input now; the margin replaces the hint paragraph that used to sit
   under it and carry the gap to the next control. */
.row { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }
button {
  padding: 4px 10px; border: 1px solid #bbb; border-radius: 4px;
  background: #fff; cursor: pointer;
}
button:hover { background: #f0f0f0; }
button.primary {
  padding: 8px 14px; background: #2d6cdf; border-color: #2d6cdf;
  color: #fff; font-weight: 600; white-space: nowrap;
}
button.primary:hover { background: #2a61c6; }
button.primary:disabled { background: #9bb4de; border-color: #9bb4de; cursor: default; }

.readout {
  margin-top: 8px; padding: 8px; font: 12px/1.5 ui-monospace, monospace;
  background: #fff; border: 1px solid #e0e0e0; border-radius: 4px;
  white-space: pre-wrap; word-break: break-word;
}

@media (prefers-color-scheme: dark) {
  body { color: #e8e8e8; }
  #left { border-right-color: #333; background: #1c1c1e; }
  @media (max-width: 700px) { #left { border-bottom-color: #333; } } /* stacked layout swaps the border edge */
  #brand { border-bottom-color: #333; }
  #results { background: #1c1c1e; border-top-color: #333; }
  .sub, .hint { color: #999; }
  button { background: #2c2c2e; border-color: #444; color: #e8e8e8; }
  button:hover { background: #3a3a3c; }
  .readout { background: #111; border-color: #333; }
  .help { border-color: #555; color: #9a9a9a; }
  #panel section, #panel details { border-top-color: #333; }
  #panel h2, #panel summary { color: #c4c4c4; }
  .card { background: #232326; border-color: #444; }
  .card:hover { border-color: #666; }
  .card small { color: #999; }
  .card .land { fill: #55524c; }
  #waterWarn, #trailWarn { color: #f0d68a; background: #2a2418; }
  #credits a, #credits a:visited { color: #7fb0ff; } /* UA default blue is ~1.8:1 on the dark footer — fails WCAG */
  #resultbar #ghlink { color: #999; }
  #resultbar #ghlink:hover { color: #e8e8e8; }
}

#bandHint { margin-bottom: 0; } /* it introduces the band list right below it — keep them tight */
#bandLegend { margin-top: 6px; }
ul.bands { list-style: none; margin: 4px 0 0; padding: 0; }
ul.bands li { display: flex; align-items: center; gap: 6px; padding: 1px 0; }
ul.bands .sw { width: 12px; height: 12px; border-radius: 2px; flex: none; border: 1px solid rgba(128, 128, 128, 0.45); box-sizing: border-box; }
