/* The palette is Taylor Geospatial's, in its dark form: their research
   pages carry the same brown-black surfaces, ivory ink and coral mark
   (research.taylorgeospatial.org). --accent is their periwinkle, which the
   page uses the way it used its old blue
   — links, slider fills, the scrub track — so every rule below that reads
   var(--accent) keeps its meaning. --brand is the coral, reserved for the
   wordmark rule and the one primary button, so it never competes with the
   amber a filtered-out scene is drawn in. */
:root {
  --panel: #1b1512;
  --panel-2: #241d19;
  --ink: #f2ede2;
  --dim: #a39a8e;
  --line: #3a302a;
  --accent: #809fd6;
  --brand: #ff4f2c;
  /* The scrub track's two loaded tones: one hue, two strengths, so the read
     is still coverage and not a new colour. --scrub-loaded (Task 23) is the
     subdued mix for a position whose thumbnail warp is ready; --scrub-hd
     (Task 25) is the full accent, for one that has the mid-resolution
     overview behind it and will flip sharp. */
  --scrub-loaded: color-mix(in srgb, var(--accent) 45%, transparent);
  --scrub-hd: var(--accent);
  /* A .dayrange thumb's rendered box, both engines. A thumb's CENTER travels
     between half this width and (track width − half this width), never the
     track's own edges, so anything drawn to line up with a handle — the
     stretch histogram — must inset by half this value on each side. One
     source of truth for both keeps the two from drifting apart. */
  --thumb: 14px;
  /* Space Grotesk for the headings, JetBrains Mono for the figures and the
     ids. Both are named with a full system fallback stack, so a blocked font
     request costs the page nothing but the voice. --ui stays the system
     face: the panel is dense, and nothing in it reads better in a webfont. */
  --display: "Space Grotesk", system-ui, -apple-system, sans-serif;
  --mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  --ui: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  color-scheme: dark;
}
* { box-sizing: border-box; }
/* The hidden attribute wins over any display rule below (labels are block,
   the legend a grid). */
[hidden] { display: none !important; }
html, body { height: 100%; margin: 0; }
body {
  font: 14px/1.45 var(--ui);
  background: #17120f;
  color: var(--ink);
}
#map { position: absolute; inset: 0 0 0 360px; }
/* The sheet wrapper (index.html) is only a box on a phone. display: contents
   takes it out of the layout here, so #imgpanel and #panel are positioned
   against the viewport exactly as they were before it existed. */
#sheet, #sheetbody { display: contents; }
#grip { display: none; }
#panel {
  position: absolute; inset: 0 auto 0 0; width: 360px;
  overflow-y: auto; padding: 14px 18px 28px;
  background: var(--panel); border-right: 1px solid var(--line);
}
/* The institute's wordmark, over a coral rule: the page's one piece of
   brand furniture, above the product title it belongs to. The <svg> is
   sized by width alone — its viewBox keeps the aspect — and takes its
   colour from the panel's ink, so the one <symbol> serves here and in the
   welcome without a second copy. */
.brand { display: block; margin: 0 0 10px; padding: 0 0 9px;
         border-bottom: 2px solid var(--brand); line-height: 0; }
.brand-mark { display: block; width: 170px; max-width: 100%; height: auto;
              color: var(--ink); }
.brand:hover .brand-mark { color: var(--brand); }
/* The headings carry the institute's voice; everything else stays on the
   system face. They are set tight and small — the panel is 360px wide, and
   a heading that grows takes a control row with it. */
h1 { margin: 0; font: 700 18px/1.2 var(--display); letter-spacing: -.02em; }
/* The title and the About button share one row. */
.head { display: flex; align-items: center; justify-content: space-between;
        gap: 8px; margin: 0 0 8px; }
h2 { margin: 0 0 8px; font: 500 12px/1.3 var(--display); text-transform: uppercase;
     letter-spacing: .1em; color: var(--dim); }
.sub { margin: 0 0 10px; color: var(--dim); font-size: 12px; }
/* The collection switch sits between the title and its one-line blurb, its
   label and select on one row. */
.switch { display: flex; align-items: center; gap: 8px; margin: 0 0 6px; }
.switch select { margin-top: 0; flex: 1 1 auto; min-width: 0; }
/* What is picked: the tile and the scene on the map. */
#selbox {
  margin: 0 0 12px; padding: 6px 10px; font-size: 12.5px;
  background: var(--panel-2); border-left: 3px solid var(--accent); border-radius: 3px;
}
#selbox .hint { margin: 0; }
.selrow { display: flex; gap: 8px; align-items: baseline; min-width: 0; }
.selrow .lbl { flex: 0 0 3.4em; color: var(--dim); font-size: 12px; }
.selrow b, .selrow a { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The ids — the tile, the scene on the map, the scene on a card — are the
   one place a fixed advance earns its keep: they are read character by
   character, and they stack into columns. Set a notch below the body size,
   because the mono face runs wide. */
.selrow b, #sel-scene, #cog-id, .scene b { font: 500 11.5px/1.4 var(--mono); }
#sel-scene { color: var(--accent); text-decoration: none; }
#sel-scene:hover { text-decoration: underline; }
#sel-scene::after { content: " ↗"; font-size: 11px; }
/* The shown scene fails the filters: amber, here and on the image panel. */
#selbox.filtered, #imgpanel.filtered #cogbar { border-left-color: #f4a261; }
#sel-why { margin: 4px 0 0; color: #f4c27a; font-size: 12px; }
#sel-why::before { content: "Hidden by your filters: "; color: var(--dim); }
.selacts { display: flex; gap: 6px; margin: 6px 0 2px; }
/* The banner over the map, centered on the map's own width. */
#filterbanner {
  position: absolute; top: 12px; left: 360px; right: 0; z-index: 3;
  display: flex; justify-content: center; pointer-events: none;
}
#filterbanner .inner {
  pointer-events: auto; display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
  max-width: min(640px, calc(100% - 96px)); padding: 7px 10px; font-size: 12.5px;
  background: var(--panel); border: 1px solid #f4a261; border-radius: 6px;
  box-shadow: 0 6px 24px rgba(0, 0, 0, .45);
}
#filterbanner b { color: #f4c27a; }
#filterbanner #fb-why { color: var(--dim); }
/* The pinned card's ✕: clears the scene, which takes the card with it. */
.scene .unpin {
  position: absolute; right: 2px; top: 2px; width: 20px; height: 20px; padding: 0;
  font-size: 11px; line-height: 18px; border-radius: 50%;
}
.scene:not(.filtered) .unpin { display: none; }
/* Its card, pinned at the top of the list while it is on the map. */
.scene.filtered { opacity: .6; position: relative; }
.scene.filtered:hover { opacity: .85; }
.scene.filtered::after {
  content: "filtered out"; position: absolute; left: 4px; top: 4px;
  background: #f4a261; color: #2a1606; font-size: 10px; font-weight: 700;
  letter-spacing: .05em; text-transform: uppercase; padding: 1px 5px; border-radius: 3px;
}
.hint { margin: 6px 0 0; color: var(--dim); font-size: 12px; }
/* The "?" glyph sits inline at the end of its label's text
   instead of in its own grid column, so it is sized to sit on the text
   baseline rather than centered on a row. */
.info { width: 14px; height: 14px; border-radius: 50%; padding: 0;
  font: 600 10px/12px system-ui, sans-serif; font-style: italic;
  background: var(--panel-2); color: var(--dim); border: 1px solid var(--line); cursor: pointer;
  vertical-align: text-bottom; margin-left: 4px; }
/* One filter row, one grid line: label, slider, value. The label carries
   its own "?" button inline now, so the grid needs only three
   tracks — dropping the old fixed icon column gives the slider the rest.
   The first column is a fixed width, not max-content — each .filter is its
   own grid, so max-content would size every row from its own label alone
   and the sliders would not start at the same x. 8.5em clears the longest
   label with its inline glyph ("Min coverage %" plus "?") with room to
   spare, so every row lands on it and the columns line up. */
.filter { display: grid; grid-template-columns: minmax(8.5em, max-content) 1fr 4ch;
  align-items: center; gap: 6px; margin: 4px 0; }
.filter label { white-space: nowrap; }
.filter output { text-align: right; font: 500 12px/1 var(--mono); }
/* The grid's middle track is already the slider's share of the row, so the
   base 74% only left a quarter of it empty before the value column. Filling
   the track is the whole point of giving it one. */
.filter input[type=range] { width: 100%; }
/* The shared info/hover popover: a box fixed to the viewport, so
   it sits on top of the sheet and the image panel instead of pushing them
   open. app.js sets its left/top next to whichever button raised it. */
#tip {
  position: fixed; z-index: 20; max-width: 240px;
  background: var(--panel-2); border: 1px solid var(--line); border-radius: 6px;
  padding: 6px 9px; font-size: 12px; color: var(--dim);
  box-shadow: 0 6px 20px rgba(0, 0, 0, .4);
}
.status {
  margin: 0 0 14px; padding: 8px 10px; font-size: 12.5px;
  background: var(--panel-2); border-left: 3px solid var(--accent);
  border-radius: 3px; color: var(--ink);
}
.status.error { border-left-color: #d73027; color: #ffd7d2; }
section { padding: 12px 0; border-top: 1px solid var(--line); }
#controls { border-top: 0; padding-top: 0; }
/* Year and Color by on one row: the year is four digits, so it takes the
   narrow track. */
#controls .pair { display: flex; gap: 8px; }
#controls .pair label { margin-bottom: 0; }
#controls .pair .year { flex: 0 0 5.8em; }
#controls .pair .metric { flex: 1 1 auto; min-width: 0; }
label { display: block; margin: 0 0 9px; color: var(--dim); font-size: 12px; }
input, select, button {
  font: inherit; color: var(--ink); background: var(--panel-2);
  border: 1px solid var(--line); border-radius: 4px; padding: 5px 7px;
}
input[type=month], input[type=date], select { width: 100%; margin-top: 3px; }
/* Customizable select (every #collection/#year/#metric/#sort/#preset/
   #sel-r/g/b/#nodata select matches the bare element selector below, so
   no per-id styling is needed for coverage). appearance: base-select opts
   a <select> and its ::picker(select) popover into a styleable render path;
   Chromium paints the dropdown list to match the panel. Firefox/Safari
   don't parse the base-select value or the ::picker() pseudo-element, so
   they drop these declarations/rules and keep the native OS picker — the
   select element itself, and its closed-button look above, are unchanged
   either way. The opt-in for `select` is its own rule (a plain element
   selector, always valid) separate from the ::picker(select) rule (a
   pseudo-element selector some engines don't know): a comma-joined
   `select, ::picker(select) { appearance: base-select; }` would let an
   unrecognized ::picker(select) invalidate the WHOLE selector list in a
   non-forgiving engine, dropping the `select` opt-in too. Keeping them
   separate means each engine drops only what it doesn't understand. */
select { appearance: base-select; }
::picker(select) {
  appearance: base-select;
  background: var(--panel-2);
  border: 1px solid var(--line);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgb(0 0 0 / 0.45);
  padding: 4px;
}
select option {
  padding: 5px 10px 5px 10px;
  border-radius: 5px;
  color: var(--ink);
  background: transparent;
}
select option:hover, select option:focus { background: var(--line); }
select option:checked { font-weight: 600; }
/* ::checkmark is the spec pseudo-element for the tick base-select draws on
   the selected option; an engine that doesn't know it drops this rule and
   nothing else needs to change. */
select option::checkmark { color: var(--accent); }
input[type=range] { width: 74%; vertical-align: middle; padding: 0; border: 0;
                    background: none; accent-color: var(--accent); }
.dates { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; margin: 0 0 2px; }
.dates input[type=date] { flex: 1 1 0; min-width: 0; width: auto; margin-top: 0; }
.dates .dash { color: var(--dim); }
.dates .info { margin-left: 0; }
/* The month-lock chip: sits beside the From/To labels, shown only
   while a bar click has clamped the slider to one month. */
.chip {
  display: inline-flex; align-items: center; gap: 4px;
  background: var(--panel-2); border: 1px solid var(--line);
  border-radius: 10px; padding: 1px 6px; font-size: 12px; color: var(--dim);
  margin: 2px 0;
}
/* Two native range inputs stacked; each is transparent except its thumb,
   so the pair reads as one two-handle slider. */
.dayrange { position: relative; height: 22px; margin: -2px 0 4px; }
.dayrange .fill { position: absolute; top: 9px; height: 4px; left: 0; width: 100%;
                  background: var(--accent); border-radius: 2px; opacity: .8; }
.dayrange::before { content: ""; position: absolute; top: 9px; left: 0; right: 0;
                    height: 4px; background: var(--line); border-radius: 2px; }
.dayrange input[type=range] {
  position: absolute; left: 0; top: 0; width: 100%; height: 22px; margin: 0;
  background: none; pointer-events: none; -webkit-appearance: none; appearance: none;
}
.dayrange input[type=range]::-webkit-slider-runnable-track { background: none; height: 22px; }
.dayrange input[type=range]::-moz-range-track { background: none; height: 22px; }
.dayrange input[type=range]::-webkit-slider-thumb {
  -webkit-appearance: none; pointer-events: auto; width: var(--thumb); height: var(--thumb);
  margin-top: 4px;
  border-radius: 50%; background: var(--ink); border: 2px solid var(--accent); cursor: ew-resize;
}
.dayrange input[type=range]::-moz-range-thumb {
  pointer-events: auto; width: var(--thumb); height: var(--thumb);
  border-radius: 50%; background: var(--ink); border: 2px solid var(--accent); cursor: ew-resize;
}
.dayrange input[type=range]:focus-visible::-webkit-slider-thumb { outline: 2px solid #fff; }
output { color: var(--ink); font-variant-numeric: tabular-nums; }
button { cursor: pointer; }
button:disabled { opacity: .45; cursor: not-allowed; }

#legend { margin-top: 8px; }
#legend .ramp {
  height: 8px; border-radius: 4px;
  background: linear-gradient(90deg, #1a9850 0%, #fee08b 25%, #d73027 60%, #4d0013 100%);
}
#legend .ticks { display: flex; justify-content: space-between;
                 color: var(--dim); font-size: 11px; margin-top: 3px; }

#timeline { padding: 10px 0; border-top: 1px solid var(--line); }
#timeline summary { cursor: pointer; font: 500 12px/1.3 var(--display);
                    text-transform: uppercase; letter-spacing: .1em; color: var(--dim); }
#timeline[open] summary { margin-bottom: 8px; }
#bars {
  display: flex; align-items: flex-end; justify-content: flex-start; gap: 1px;
  height: 84px; padding: 0; overflow-x: auto;
  background: var(--panel-2); border-radius: 4px;
}
#bars .hint { align-self: center; padding: 0 10px; }
.bar {
  /* max-width keeps a single published month from filling the whole strip */
  flex: 1 1 4px; min-width: 3px; max-width: 18px;
  padding: 0; border: 0; border-radius: 1px 1px 0 0;
  background: var(--accent); cursor: pointer; opacity: .8;
}
.bar:hover { opacity: 1; }
.bar.on { outline: 1px solid #fff; outline-offset: -1px; opacity: 1; }
.bar:focus-visible { outline: 2px solid var(--accent); }

/* The result count and the sort share the row over the cards. */
#sortrow { display: flex; align-items: center; justify-content: space-between; gap: 8px;
           padding: 8px 0 6px; border-top: 1px solid var(--line); }
#sortrow label { display: flex; align-items: center; gap: 6px; margin: 0; }
#sortrow select { margin-top: 0; width: auto; }
#rescount { color: var(--dim); font-size: 12px; }
#results { display: flex; flex-direction: column; gap: 10px; }
#results:empty { display: none; }
/* A card is one button-like target: a click anywhere shows the scene. */
.scene { display: flex; gap: 10px; align-items: flex-start; cursor: pointer;
         border-radius: 4px; padding: 2px; }
.scene:hover { background: var(--panel-2); }
.scene:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.scene img {
  width: 96px; height: 96px; object-fit: cover; flex: 0 0 96px;
  border-radius: 4px;
}
/* The preview JPEGs' white nodata is keyed to transparent in app.js
   (thumbnail()), so the swath sits straight on the panel. A white
   background under that transparency matches the map's own white
   nodata, so a snowy scene still reads as snow, not as a hole. */
#results .scene img.loaded {
  background: #fff;
}
.scene div { font-size: 12px; color: var(--dim); min-width: 0; word-break: break-word; }
.scene b { color: var(--ink); font-weight: 600; }
.scene a { color: var(--accent); text-decoration: none; margin-right: 6px; }
.scene a:hover { text-decoration: underline; }
.scene .actions { display: inline-flex; gap: 4px; margin: 6px 6px 0 0; }
/* The render icons: one small swatch per preset. */
.scene .actions button {
  width: 24px; height: 24px; padding: 0; border-radius: 4px;
  display: inline-grid; place-items: center; background: var(--panel-2);
}
.scene .actions button:hover { border-color: var(--accent); }
.scene .actions button.on { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.scene .actions svg { width: 16px; height: 16px; display: block; }
.scene .links { display: inline-block; margin-top: 4px; }
button.mini {
  padding: 2px 8px; font-size: 11.5px; background: #342a24; border-color: #4a3c33;
}
button.mini:not(:disabled):hover { background: #43362e; }
/* The image controls (Task 29): a card floating at the map's lower right,
   well under the zoom column at the top right; it scrolls on its own past
   70 vh. No source carries attribution, so MapLibre's compact attribution
   control in that corner is empty and hidden — if one ever appears, the
   panel would cover it. The cogbar and the band mapper keep their own
   sidebar-block look inside it. */
#imgpanel {
  position: absolute; right: 16px; bottom: 16px; z-index: 3;
  width: min(360px, calc(100% - 360px - 32px));
  max-height: 70vh; overflow-y: auto; padding: 10px 12px 12px;
  background: var(--panel); border: 1px solid var(--line); border-radius: 6px;
  box-shadow: 0 6px 24px rgba(0, 0, 0, .45);
}
#cogbar:not([hidden]) {
  margin: 0; padding: 8px 10px; font-size: 12.5px; border-radius: 3px;
  background: var(--panel-2); border-left: 3px solid #1a9850;
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
}
#cogbar b { word-break: break-all; }
/* The tile-loading state: a spinner before the text while the preview
   stands in, a green dot once every tile in view has loaded. */
#cog-state { display: inline-flex; align-items: center; gap: 6px; opacity: .85; }
#cog-state::before {
  content: ""; width: 10px; height: 10px; border-radius: 50%; flex: none;
  background: #1a9850;
}
#cog-state[data-state="loading"]::before {
  background: none; border: 2px solid #4a3c33; border-top-color: var(--accent);
  animation: cog-spin .8s linear infinite;
}
#cog-state[data-state="partial"]::before { background: #f4a261; }
@keyframes cog-spin { to { transform: rotate(360deg); } }
/* The nav strip: prev/next and zoom-to beside the scrub bar, one row. */
#imgnav { display: flex; align-items: center; gap: 6px; }
#imgnav input[type=range] { flex: 1; min-width: 60px; }
/* A dashed-thumb style per engine is not portable; reduced opacity says
   "detached" everywhere: the shown scene sits outside the current filters. */
#imgscrub[data-detached] { opacity: 0.5; }
/* app.js's paintScrubTrack sets --scrub-fill to a hard-stop gradient over
   the view's loaded/unloaded ranges. #imgscrub keeps the plain input rule's
   native thumb (it is not -webkit-appearance:none, so accent-color still
   colors the thumb) — only the track pseudo-element's background changes,
   per engine. The custom property, not a direct background on #imgscrub
   itself, is the mechanism because the general `input[type=range]` rule
   above sets `background: none` on the element and only the track pseudo
   actually paints the fill either engine renders. */
#imgscrub::-webkit-slider-runnable-track { background: var(--scrub-fill, none); }
#imgscrub::-moz-range-track { background: var(--scrub-fill, none); }
/* The band mapper: a compact block under the cogbar. */
#bandbox:not([hidden]) {
  margin: 8px 0 0; padding: 6px 10px 8px; font-size: 12px; border-radius: 3px;
  background: var(--panel-2); border-left: 3px solid #1a9850;
}
#bandbox summary { cursor: pointer; color: var(--dim);
                   font: 500 12px/1.3 var(--display);
                   text-transform: uppercase; letter-spacing: .1em; }
#bandbox label { margin: 6px 0 0; }
#bandbox select { margin-top: 2px; }
#bandbox .bandrow.three { display: flex; gap: 6px; }
#bandbox .bandrow.three label { flex: 1 1 0; min-width: 0; }
#bandbox .bandrow.three select { font-size: 11px; padding: 3px 4px; }
#bandbox .lbl { color: var(--dim); font-size: 12px; margin-right: 6px; }
#bandbox label.inline { display: inline; margin: 0 8px 0 0; }
/* Gamma is the only lone slider in the band box, and it is named here rather
   than matched as `#bandbox input[type=range]`: that selector carries an id,
   so it outranks `.dayrange input[type=range] { width: 100% }` and shrank the
   channel blocks' two stacked handles to 60% of their container. A handle then
   travelled 60% of the track its own histogram was drawn across — the max
   handle of a 0.95 stretch rendered at 55%, which is what the bins disagreed
   with. Naming the one input keeps the rule off every slider built into a
   .dayrange container inside #bandbox. */
#bandbox #gamma { width: 60%; }
#bandbox #bandnote:empty { display: none; }
#bandbox #bandnote { margin: 6px 0 0; }
.chan-r { color: #ff8a8a; font-weight: 700; } .chan-g { color: #8ae28a; font-weight: 700; }
.chan-b { color: #8ab4ff; font-weight: 700; }
.chan { margin-top: 8px; }
.chan-head { display: flex; align-items: baseline; gap: 6px; font-size: 12px; }
.chan-head b { color: var(--ink); }
.chan-head .hint { margin: 0; flex: 1 1 auto; white-space: nowrap; overflow: hidden;
                   text-overflow: ellipsis; }
.chan-head button.mini { padding: 0 5px; font-size: 10.5px; }
/* Inset by half a thumb on each side so the drawn bins share the handles'
   own scale (see --thumb): at value = min the left handle's center sits
   half a thumb in from the container edge, and now so does the histogram's
   first bin. */
.chan canvas.hist { display: block; width: calc(100% - var(--thumb)); height: 36px;
                    margin: 4px calc(var(--thumb) / 2) 0;
                    background: #17120f; border-radius: 3px 3px 0 0; }
.chan .vrange { margin: -4px 0 2px; }
.chan .minmax { display: flex; gap: 6px; align-items: center; color: var(--dim); font-size: 11px; }
.chan .minmax input[type=number] { width: 5.5em; padding: 2px 4px; font-size: 11px;
                                   font-variant-numeric: tabular-nums; }
#scl-legend { display: grid; grid-template-columns: 1fr 1fr; gap: 2px 10px; margin-top: 8px;
              font-size: 11px; color: var(--dim); }
#scl-legend span::before { content: ""; display: inline-block; width: 10px; height: 10px;
                            margin-right: 5px; vertical-align: -1px; border-radius: 2px;
                            background: var(--sw); border: 1px solid #17120f; }

.scene.best { position: relative; }
.scene.best::before {
  content: "best"; position: absolute; left: 4px; top: 4px;
  background: #1a9850; color: #06210f; font-size: 10px; font-weight: 700;
  letter-spacing: .05em; text-transform: uppercase; padding: 1px 5px;
  border-radius: 3px;
}
/* Which card the map shows now (.current) and, from the scrub preview, which
   one a hover or key press previews without changing what is shown (.peek). */
.scene.current { outline: 2px solid var(--accent); outline-offset: -2px; }
.scene.peek { outline: 2px dashed var(--accent); outline-offset: -2px; }

#apibox { margin-top: 14px; border-top: 1px solid var(--line); padding-top: 12px; }
#apibox summary { cursor: pointer; color: var(--dim); font-size: 12px; }
#apibox pre {
  white-space: pre-wrap; word-break: break-word; font: 400 10.5px/1.5 var(--mono);
  background: var(--panel-2); padding: 8px; border-radius: 4px; color: #d8cfc2;
}
#apibox pre:empty { display: none; }
.foot { margin-top: 16px; color: var(--dim); font-size: 11.5px; }
.foot a { color: var(--accent); }

#icon-defs { position: absolute; width: 0; height: 0; overflow: hidden; }
/* The first-visit welcome. */
#welcome {
  max-width: min(460px, calc(100vw - 32px)); padding: 18px 20px 16px;
  background: var(--panel); color: var(--ink);
  border: 1px solid var(--line); border-radius: 8px;
  box-shadow: 0 12px 40px rgba(0, 0, 0, .6); font-size: 13.5px;
}
#welcome::backdrop { background: rgba(10, 7, 5, .66); }
/* The dialog is the ✕'s positioning context, and the wordmark sits above
   the heading with room kept clear of the ✕. */
#welcome { position: relative; }
#welcome .brand-mark { width: 150px; margin: 0 0 12px; color: var(--dim); }
#welcome-x {
  position: absolute; right: 10px; top: 10px; width: 24px; height: 24px;
  padding: 0; line-height: 1; border-radius: 50%; font-size: 12px;
}
#welcome h2 { font-size: 20px; font-weight: 700; text-transform: none;
              letter-spacing: -.02em; color: var(--ink); }
#welcome p { margin: 0 0 10px; color: var(--dim); }
#welcome ol { margin: 0 0 14px; padding-left: 20px; }
#welcome li { margin: 0 0 6px; }
#welcome form { text-align: right; }
/* The one primary button on the page wears the institute's coral. */
#welcome-go { background: var(--brand); border-color: var(--brand); color: #2a0d05;
  font-weight: 600; padding: 6px 14px; }
#welcome-go:hover { background: #ff6648; border-color: #ff6648; }

/* The basemap label switch, under the zoom buttons. MapLibre's own control
   buttons carry a background icon and set no type, so a button with a letter
   in it needs one here. The struck-through grey is the off state, which is
   the same read as a muted icon without a second glyph to draw. */
#labelswitch {
  font: 700 14px/29px system-ui, -apple-system, sans-serif;
  color: #333;
}
#labelswitch[aria-pressed="false"] {
  color: #9aa3b2;
  text-decoration: line-through;
}

/* ---------------------------------------------------------------------- *
 * The phone layout: the map is the page.
 *
 * The map fills the viewport and one bottom sheet holds everything else --
 * the image controls at the top of it, the search controls under them. Two
 * stacked panels used to leave a 9 vh strip of map between them, which is
 * the whole reason this block exists.
 *
 * Heights are in dvh, not vh, because the iOS URL bar changes vh while it
 * slides. CSS owns the three snap heights so they follow that; app.js only
 * names which one is current, in data-snap.
 * ---------------------------------------------------------------------- */
@media (max-width: 760px) {
  /* The sheet and the map are both fixed to the viewport, so nothing is
     left to scroll the page itself. */
  body { overflow: hidden; }
  #filterbanner { left: 0; }
  #map { inset: 0; }
  #sheet {
    display: flex; flex-direction: column;
    position: fixed; inset: auto 0 0 0; z-index: 4;
    height: 50dvh;                      /* half: what a fresh page shows */
    max-height: 88dvh;
    /* The home indicator must not sit on the controls. */
    padding-bottom: env(safe-area-inset-bottom, 0px);
    background: var(--panel); border-top: 1px solid var(--line);
    border-radius: 14px 14px 0 0;
    box-shadow: 0 -6px 24px rgba(0, 0, 0, .5);
    transition: height .18s ease;
  }
  /* peek is the handle, the "Showing …" line, and the image nav strip, so
     both stay readable with the whole map above them. */
  #sheet[data-snap="peek"] { height: calc(150px + env(safe-area-inset-bottom, 0px)); }
  #sheet[data-snap="half"] { height: 50dvh; }
  #sheet[data-snap="full"] { height: 88dvh; }
  /* A drag sets the height in pixels every pointermove; an animation on top
     of that would lag behind the finger. */
  #sheet[data-drag] { transition: none; }
  #grip {
    display: block; flex: none; width: 100%; height: 30px;
    margin: 0; padding: 0; border: 0; border-radius: 14px 14px 0 0;
    background: none; cursor: grab;
    /* The browser must not scroll or zoom on a drag of the handle. */
    touch-action: none;
  }
  /* The browser must not scroll the sheet on a horizontal drag of the
     scrubber; the scrubber owns the drag instead. */
  #imgscrub { touch-action: none; }
  #grip::before {
    content: ""; display: block; width: 44px; height: 4px; margin: 13px auto 0;
    background: var(--dim); border-radius: 2px; opacity: .8;
  }
  #grip:focus-visible { outline: 2px solid var(--accent); outline-offset: -4px; }
  /* Everything in the sheet scrolls together, under the handle. */
  #sheetbody {
    display: block; flex: 1 1 auto; min-height: 0;
    overflow-y: auto; overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
  }
  #panel {
    position: static; width: auto; overflow: visible;
    padding: 2px 14px 22px; border-right: 0;
  }
  /* The image controls are the top block of the same sheet here, not a
     second sheet of their own and not a card over the map. */
  #imgpanel {
    position: static; width: auto; max-height: none; overflow: visible;
    padding: 2px 14px 0;
    background: none; border: 0; border-radius: 0; box-shadow: none;
  }
}
