body {
  margin: 0;
  font-family: var(--m-font-ui, system-ui, sans-serif);
}

canvas {
  display: block;
}

/* ── Logo ─────────────────────────────────────────────────────────────── */
/* Water puts the mark in the header's top-right corner, and so do we. It used
 * to sit at the page's top-left, which is what coupled it to the layout:
 * plotMappingToolbar() derived the row's left edge from its measured width, so
 * nothing could move either one independently. Now the card supplies both of
 * its own edges and the mark is decoration inside it. */
#logo {
  position: absolute;
  top: 10px;
  right: 14px;
  height: 30px;
  width: auto;
  z-index: 2;
  pointer-events: none;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.15));
}

/* ── Buttons ─────────────────────────────────────────────────────────── */
button {
  cursor: pointer;
  font-family: var(--m-font-ui, system-ui, sans-serif);
  white-space: nowrap;
  user-select: none;
  transition: filter 0.15s ease, box-shadow 0.15s ease, transform 0.08s ease;
}

button:hover {
  filter: brightness(0.88);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
}

button:active {
  transform: translateY(1px);
  filter: brightness(0.78);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
}

button:focus-visible {
  outline: 2px solid var(--m-blue);
  outline-offset: 2px;
}

/* ── Selects ──────────────────────────────────────────────────────────── */
select {
  cursor: pointer;
  font-family: var(--m-font-ui, system-ui, sans-serif);
  border-radius: 4px;
  transition: box-shadow 0.15s ease, border-color 0.15s ease;
}

select:hover {
  box-shadow: 0 1px 5px rgba(0, 0, 0, 0.25);
}

select:focus {
  outline: 2px solid var(--m-focus);
  outline-offset: 1px;
}

/* ── Text inputs ──────────────────────────────────────────────────────── */
input[type="text"] {
  font-family: var(--m-font-ui, system-ui, sans-serif);
  box-sizing: border-box;
  border-radius: 3px;
  transition: box-shadow 0.15s ease;
}

input[type="text"]:not(#well_data_graph_label):hover {
  box-shadow: 0 1px 5px rgba(0, 0, 0, 0.25);
}

input[type="text"]:not(#well_data_graph_label):focus {
  outline: 2px solid var(--m-focus);
  outline-offset: 1px;
}

input[type="text"]::placeholder {
  color: var(--m-ink-soft);
}

/* Well data display field — read-only display, suppress focus ring */
#well_data_graph_label {
  cursor: default;
  border: 2px solid var(--m-border);
  border-radius: var(--m-radius-sm);
  letter-spacing: 0.025em;
  background-color: var(--m-card);
  color: var(--m-blue);
}

#well_data_graph_label:focus {
  outline: none;
  box-shadow: none;
  border-color: var(--m-blue);
}

/* ── Current mapping skin defaults ─────────────────────────────────────── *
 * These are first-paint fallbacks. mappingSkins.js resolves the active local
 * profile before the page body is parsed and applies the same values here.    */
:root {
  --m-ink: #202c40;
  --m-ink-soft: #5f6f84;
  --m-surface: #f5f8fc;
  --m-blue: #025ad6;
  --m-blue-hover: #034bb0;
  --m-blue-deep: #00379f;
  --m-blue-soft: #edf4ff;
  --m-focus: rgba(2, 90, 214, 0.35);
  --m-success: #00bb7f;
  --m-danger: #d31e22;
  --m-card: #fff;
  --m-border: #e4eaf2;
  --m-shadow: 0 16px 36px -16px rgba(32, 44, 64, 0.22);
  /* Keep the existing card and control geometry; this update is color only. */
  --m-radius: 24px;
  /* One height for every control in the header row, and the reason the labels line
   * up. The row is `align-items: flex-end`, so children with different intrinsic
   * heights BOTTOM-align and their tops disagree -- and because each `.m-field` is a
   * label over a control, a control 2px shorter drags its own label 2px down and the
   * label row goes ragged. Measured at 1920x1080 before this was pinned: buttons and
   * selects 32px, `input[type=text]` 30px (from its font's line box), and `.m-readout`
   * 27px -- or 10px when EMPTY, because a div with no text has no line box and
   * collapses to padding + border, which is what made the Latitude and Longitude
   * boxes look like slivers. */
  --m-control-h: 32px;
  /* The strip of the title line that belongs to the mark, and the reason nothing on
   * that line may be pushed to the right with `justify-content: space-between`: #logo
   * is absolutely positioned in the card's top-right corner (right: 14px, height: 30px,
   * width auto -- 105px for the shipped PNG, so its left edge is 119px in). The mark
   * draws OVER the row (z-index 2 against the row's 1), so a cluster that reaches into
   * this strip puts Play File behind the logo -- measured, at 1024x768, before this
   * existed. The row's right padding keeps its content out of the strip instead: at
   * 1920 the cluster ends ~1200px in and the reserve costs nothing, and below ~1210px
   * it makes the cluster wrap one item sooner, which it was going to do anyway. */
  --m-mark-reserve: 132px;
  --m-radius-sm: 8px;
  --m-font-display: 'Baloo 2', 'Trebuchet MS', ui-rounded, system-ui, sans-serif;
  --m-font-body: 'Nunito', 'Trebuchet MS', system-ui, sans-serif;
  --m-font-ui: system-ui, sans-serif;
  --m-grow-basis: 68px;
  --m-controls-gap: 8px;
  --m-grow-cap: 173px;
}

/* ── Graph-area label (white text over the dark WebGL canvas) ─────────── *
 * Only the graph quadrant's label keeps the absolute white-on-canvas look:
 * it sits over the canvas, not in the toolbar.                            */
#graph_type_select_label {
  position: absolute;
  z-index: 100;
  color: white;
  /* The toolbar's own face, so the graph quadrant's widgets and the header row read as one
   * app. This is only the pre-JS value -- placeMappingGraphQuadrant() replaces the whole
   * shorthand with a fitted size in the same family (mapping.js, mapping_graph_font_family),
   * and the two stacks must be changed together. */
  font: 14px var(--m-font-body);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.75);
  letter-spacing: 0.02em;
  /* Sit against the select, centred on it. The box is positioned in mapping.js at the same top
   * and height as #graph_type_select, so `align-items: center` puts the caption on the select's
   * own text line -- as a plain block the text sat at the TOP of a box that was 5px lower and 5px
   * taller than the select's, which read as a misaligned label ("not centered vertically with the
   * select", 2026-09-21).
   *
   * `justify-content: flex-start` puts the caption hard against the box's left edge, which is the
   * quadrant's own left edge -- the same edge the caption bar below the row starts from. It was
   * `flex-end` until 2026-10-05, hugging the box's right edge to hold the caption 0.02 x
   * graph_viewport.width short of the select; that worked, but it left the empty remainder of the
   * box as dead space in front of the caption (45px at 1920), and moving the box left to close it
   * would have carried the right edge -- and so the gap -- with it. placeMappingGraphQuadrant()
   * now slides the select and the five buttons left by that remainder instead, so the caption
   * lands on the quadrant edge and the 0.02 gap is unchanged (the shift cancels in it). */
  display: flex;
  align-items: center;
  justify-content: flex-start;
  /* Never wrap. Before the row's font was fitted (mapping.js, initializeMapApplication) the
   * label wrapped onto a second line inside its own 43px-tall box at 1440 and below, so the
   * spill was vertical as well as horizontal. The font is now sized to the box, so a wrap
   * cannot happen -- and nowrap means a future miscalculation shows as spill rather than a
   * two-line caption over the graph. Same rule the `button` block above sets. */
  white-space: nowrap;
}

/* ── Top row: the mapping header card ─────────────────────────────────── *
 * Absolute, not static: the canvas is sized to window.innerHeight and lives
 * in the absolutely-positioned #ui_container, so a flow-layout toolbar would
 * push the canvas past the bottom of the window.
 *
 * It uses the Visitech-inspired cool white surface, existing radius and shadow,
 * a subtle blue glow, a mark in the top-right corner, and a Baloo 2 title over
 * the controls. Both of its edges come from CSS.
 *
 * Its vertical cost is map height. mapping.js reads this element's bottom edge as
 * map_viewport_borders.top, and the map quadrant is anchored to the bottom, so
 * every px of title/subtitle padding here comes straight out of the map (the
 * graph quadrant is a half-canvas constant and does not shrink). The title and
 * subtitle make it read as the application header rather than a control
 * strip; the measured cost is in MAPPING_DOCUMENTATION.md §2.
 *
 * The card is three lines: the title line (which carries the record session --
 * see .m-titlerow), the subtitle, and the filters row (which carries the four
 * commands and the well filters). Only the filters row wraps on a desktop window,
 * and it now needs ~1524px of window to stay on one line: measured on
 * mapping/index.html at 2026-09-29, 1920/1600/1440/1366/1360 are all one row and
 * 1358 and below are two -- BUT that is the 2026-09-29 row, with three commands.
 * The 2026-10-16 row needs the 1524 below, so 1440 is now TWO rows. (It needed
 * ~1330px when the row held twelve children; the thirteenth -- Formation -- plus
 * --m-grow-basis 68 / --m-controls-gap 8 moved the fold. The arithmetic is in the
 * BASE/GAP block on #mapping_controls.)
 * **RE-MEASURED 2026-10-16, and the numbers above are the 2026-09-29 row's.** A
 * fixed-width sweep on the shipped markup puts the one-line break at **1335px of
 * card**; with a third 68px name box cloned back in (HEAD's field set, before the
 * Operator pair became one multi-pick field) it is **1410px**. So deleting the
 * Operator 2 box moved the fold **−75px**, and the row **still wraps at 1366**
 * (the card is 1287px there) -- it wrapped before this change too, so "one row at
 * 1366" is not a claim about this row. Full measurement:
 * .scratch/cdp_toolbar_row_geometry.js; the note on #mapping_controls carries it too.
 *
 * **SUPERSEDED 2026-10-16, later the same day, by the Reset Zoom button.** The 1335px
 * above is the row WITHOUT it. A fourth command -- `#fit_displayed_wells_button`, the
 * camera-only sibling of Reset Map -- joined the command group, and the same sweep on
 * the same probe now reads **1445px of card for one line**: +110px, against the button's
 * own measured 101px plus the row's 8px gap = 109 predicted. The card is window-79px
 * (1287 at 1366), so one line now needs a window of ~1524px where it needed ~1414px. In
 * that 110px band -- and ONLY there -- the row takes a second line it did not before,
 * and the map pays the usual 53px. Below 1414px the row wrapped already, 1366 included,
 * so nothing below the band changes; 1920, 1600 and 1536 are all clear of it.
 *
 * That is the honest price of the button, and it was taken deliberately: the command
 * group is where the app's whole-map actions live and the button's neighbour there is
 * Reset Map, which is the control a reader looking for "reset the view" reaches for
 * first. The alternative homes were all worse -- the title line would have meant a map
 * command reading as a session control, and the graph quadrant's own row is full (see
 * the note at mapping.js zoom_well_button). Full measurement: same probe as above;
 * re-run it rather than re-derive the band, which is a 5px-step sweep.
 * The title line needs ~960px of its own, so between roughly 960 and 1360 the
 * card is four lines' worth. Above 1360 nothing wraps and the card measures
 * 151px tall with its bottom edge at 159 and map_viewport_borders.top at 171;
 * wrapped, it is 204px / 212 / 224 -- a wrap costs the map 53px. */
#mapping_toolbar {
  position: absolute;
  top: 8px;
  /* Keep the current inset; it is part of the toolbar layout. */
  left: 10px;
  right: 10px;
  z-index: 150;
  overflow: hidden;
  padding: clamp(10px, 1.5vh, 16px) clamp(12px, 1.8vw, 22px);
  background: linear-gradient(120deg, var(--m-card) 0%, var(--m-surface) 100%);
  border-radius: var(--m-radius);
  box-shadow: var(--m-shadow);
  font-family: var(--m-font-body);
  color: var(--m-ink);
}

/* Decorative blue glow clipped by the card. It remains behind the title and
 * controls, which carry z-index: 1. */
.m-sun {
  position: absolute;
  right: 6%;
  top: -26px;
  width: 120px;
  height: 120px;
  border-radius: 50%;
  background: radial-gradient(circle at 40% 40%, rgba(2, 90, 214, 0.14), rgba(237, 244, 255, 0.10));
  box-shadow: 0 0 60px 14px rgba(2, 90, 214, 0.08);
}

/* ── Title line: the title, map history controls, then the record session ─ *
 * The row that carries this card's height for free. `.m-title` is 36px of line box at
 * its largest clamp size and the heading measures 337px of a ~1856px row, so the
 * cluster beside it costs nothing at all -- as long as nothing in it is taller than
 * 36px. That is why the cluster is bare buttons (32px, from the pinned --m-control-h)
 * and a single-line toggle group, and why a `.m-field` (45px: label + gap + control)
 * must never be put here: it would push the row to 45px and take 9px off the map,
 * which is anchored to the card's bottom edge. See index.html's note on that.
 *
 * The commands (Refresh Data / Reset Map / Screenshot Map, and Reset Zoom since
 * 2026-10-16) sat here for one pass and moved back into the filters row on
 * 2026-09-21. Both arrangements measure
 * the SAME card height at 1920/1600/1366 -- a row's height is set by the tallest
 * thing in it, not by how full it is, and neither row wrapped at those widths either
 * way. The difference shows up below the filters row's own ~1330px threshold, where
 * the commands' ~360px is enough to cost the map 13px that the empty title line
 * could have absorbed for nothing. */
.m-titlerow {
    position: relative;
    /* Above .m-sun, which `.m-title` used to carry on its own. */
    z-index: 1;
    display: flex;
    /* The controls wrap under the title rather than running off the card. Opening the
     * session cluster adds its Record / Play / Play File controls to the always-visible
     * history pair, so the row can wrap at narrower widths; running off the card would
     * put Play File out of reach because the card is `overflow: hidden`. The history pair
     * itself fits beside the heading on the closed, default row. */
    flex-wrap: wrap;
    align-items: center;
    gap: 10px 12px;
    /* Keeps the cluster out of the mark's corner -- see --m-mark-reserve. */
    padding-right: var(--m-mark-reserve);
}

/* The title-to-session break, and the only real air on the line. Decoration only --
 * the row would lay out identically without it -- but the title line also carries map
 * history controls, and a 12px column gap alone reads as one run of buttons. The margin
 * is the whole of the separation: the row gap is 12px, so this puts 26px either side of
 * the rule and ~53px between the history controls and Record. */
.m-divider {
    width: 1px;
    height: 20px;
    margin: 0 14px;
  background: rgba(32, 44, 64, 0.14);
}

/* The history controls stay visible even while the record/play demonstration group is
 * closed. Keep the pair visually together and give unavailable directions a clear
 * disabled state; mapping.js updates that state from map_history after each change. */
.m-history-controls {
  display: flex;
  align-items: center;
  gap: 6px;
}

.m-history-controls button:disabled {
  cursor: default;
  opacity: 0.48;
  filter: none;
  box-shadow: none;
  transform: none;
}

/* ── The session cluster: hidden until the opener is pressed ─────────── *
 * Record / Play / Play File and the fixed-step group. They are a demonstration and training
 * tool, so they are closed on every load and the opener beside the title is the only thing
 * that reveals them. One flex item rather than four children of the title row, so a title
 * line that wraps moves the whole cluster down together.
 *
 * The margin is derived rather than guessed. The divider contributes 14 + 1 + 14 of margin
 * and the row's own 12px column gap, so the history controls and the opener sit ~53px apart;
 * the row's gap is already applied on this side of the opener, so this is the rest of it.
 * That keeps the opener-to-Record gap equal to the history-to-opener one, and keeps them equal if the
 * divider ever changes. There is deliberately no second rule here: the user asked for space. */
.m-session {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-left: calc(14px + 1px + 14px + 12px);
}

/* LOAD-BEARING. `display: flex` above is an author declaration, and an author declaration
 * beats the UA's `[hidden] { display: none }` on specificity -- so without this rule the
 * cluster is visible on every load and the `hidden` attribute on the element does nothing
 * at all. `.m-session[hidden]` (0,2,0) also beats `.m-session` (0,1,0), so order is not
 * what makes this work. */
.m-session[hidden] {
    display: none;
}

/* The opener's caret. Decoration only: the state lives in the button's aria-expanded, so
 * nothing has to rewrite the button's text, and `rotate` needs no second glyph. */
#session_toggle_button .m-caret {
    display: inline-block;
    margin-left: 6px;
    font-size: 10px;
    transition: transform 0.15s;
}

#session_toggle_button[aria-expanded="true"] .m-caret {
    transform: rotate(180deg);
}

/* The fixed-step toggle and its interval, kept together so a wrapping title line moves
 * them as one unit. */
.m-stepgroup {
    display: flex;
    align-items: center;
    gap: 6px;
}

/* The label-over-a-checkbox pair. The session cluster's "Fixed step" is the only
 * user left: the inactive-wells control was the other one and became a button
 * (#inactive_wells_toggle, "Hide Inactive" / "Show Inactive") so that its label
 * could report the action it performs -- a checkbox's caption cannot. See the
 * note on that button in mapping/index.html. */
.m-toggle {
    display: flex;
    align-items: center;
    gap: 6px;
    font-family: var(--m-font-body);
    font-weight: 800;
    font-size: 13px;
    color: var(--m-ink);
    white-space: nowrap;
    cursor: pointer;
}

.m-toggle input[type="checkbox"] {
    /* Not a --m-control-h control: a checkbox is a square, and stretching it to 32px
     * would make the box read as a button. It is small and vertically centred instead,
     * which is why the cluster's height is still set by the buttons around it. */
    width: 15px;
    height: 15px;
    accent-color: var(--m-blue);
    cursor: pointer;
}

.m-unit {
    font-family: var(--m-font-body);
    font-weight: 800;
    font-size: 13px;
    color: var(--m-ink);
}

/* Header title. */
.m-title {
  position: relative;
  z-index: 1;
  margin: 0;
  font-family: var(--m-font-display);
  font-weight: 800;
  font-size: clamp(20px, 2.8vw, 30px);
  line-height: 1.2;
  letter-spacing: 0.3px;
  color: var(--m-ink);
  text-shadow: 0 2px 0 rgba(255, 255, 255, 0.65);
}

/* This line carries two jobs: the default hint, and the
 * slot the filter path writes its zero-match message into (setMappingSubtitleText
 * in mapping.js). Text with a writer is text no author can keep short, so the
 * line is pinned to ONE -- `nowrap` + ellipsis -- instead of being trusted to
 * stay under a character cap. Every line this wraps to is a line the map loses:
 * the first subtitle here wrapped at 1366 and cost the map 21px, and
 * #mapping_toolbar's ResizeObserver (mapping.js:2199) re-measures the card and
 * raises the map's top edge, so a two-line message would silently shrink the map
 * exactly when the map is already showing nothing.
 *
 * The cap goes 46ch -> 80ch to fit a message that names the filter and often a
 * near miss. Widening is safe here: the card is anchored `left/right`, not
 * shrink-to-fit, so a wider line cannot widen the card, and `overflow: hidden`
 * on it means the line can never escape either. */
.m-subtitle {
  position: relative;
  z-index: 1;
  margin: 6px 0 0;
  font-size: clamp(13px, 1.3vw, 15px);
  font-weight: 700;
  color: var(--m-ink-soft);
  max-width: 80ch;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The control row inside the card. `flex-wrap` is what keeps every control
 * reachable on a narrow window: the row grows taller and the map gives up the
 * space instead of the controls running off the right edge (the old
 * hand-advanced x cursor fit 1920px only).
 *
 * Measured 2026-10-04, with the region field in the markup (it is the eleventh
 * .m-field and the reason there are two cases now):
 *
 *   TEXAS           fourteen children, eleven .m-fields -- demand 1425.3px. One line
 *                   (45px) down to 1512; the first width that wraps to a second line
 *                   (98px, so 53px of map) is 1440, where the card has 1361px.
 *   EVERY OTHER     fourteen children, TEN .m-fields: the region field is
 *   STATE           display:none, so the row is what it always was -- demand 1294.3px,
 *                   one line down to 1400, first wrap at 1366.
 *
 * So the region control moves the wrap threshold down by 74px of viewport width, and
 * only on Texas: it costs 53px of map between 1366 and 1440 on that state alone.
 * That is the unavoidable price of an eleventh control, not a tuning failure -- at
 * 1366 the card is 1287px against the other ten fields' 1294.3px, so the row is
 * ALREADY 7.3px over and any additional field wraps there whatever its width. (The
 * card is narrower than this comment used to record: 1361 at 1440, not 1302 at 1366.
 * Re-measure rather than adjust, as ever -- which is how these numbers were taken.)
 *
 * 2026-10-16: Operator 1 + Operator 2 became ONE multi-pick Operator field, so the row
 * carries TWO name boxes now, not three. Measured on the real page (1366x768, cache
 * disabled, .scratch/cdp_toolbar_row_geometry.js -- it sweeps a FIXED width on
 * #mapping_controls and counts rows by clustering tops, because the card's four buttons
 * are 32px and bottom-align with the 45px fields and "distinct tops" would call every
 * one-line layout a wrap):
 *
 *   NOW    one line needs 1335px of card. At a 1366 window the card is 1287px, so the
 *          row WRAPS there -- the well-count field drops to a second line.
 *   HEAD   with a third 68px name box cloned back in: 1410px. So the deleted Operator 2
 *          box moved the threshold by -75px, against 68px of basis + 8px of gap = 76
 *          predicted. -75px is the number to carry: it is a property of THIS change.
 *
 * The threshold was above the 1287px card before this change as well, so "the row does
 * not wrap at 1366" was never true at HEAD either -- do not read the wrap as a
 * regression. What is new is only the -75px. Note also that the constant term below does
 * NOT reproduce 1335 (with B=68, G=8 and the 2026-10-13 saving it predicts ~1183), so
 * treat the arithmetic as the model and these two measured card widths as the fact.
 *
 * 2026-10-13: the inactive-wells control stopped being a label-over-checkbox and
 * became a button reading "Hide Inactive" / "Show Inactive", so the space it wants is now
 * NARROWER. Both numbers below come from ONE context -- the toolbar harness at 2400px,
 * with the shipped old markup swapped back into the same field for the comparison, so
 * the font, the cascade and the row are identical for both:
 *
 *   old    150.8px .m-field  =  the caption "Show inactive wells" plus a 15px box
 *                                 -> 158.8px of row, the figure DOCUMENTATION.md quotes
 *   new    115.0px control   =  the pinned pill (its natural widths are 108.3px for
 *                                 "Hide Inactive" and 114.4px for "Show Inactive")
 *                                 -> 123.0px of row
 *         -35.8px            =  what the row's demand drops by, gap unchanged
 *
 * The control has since moved out of its .m-field wrapper and into the command group at
 * the head of the row (index.html). That move costs the arithmetic NOTHING and the two
 * numbers above still hold: a .m-field carries no width of its own -- it is a column
 * flex box whose base size is its single child's -- so a field around this button and
 * the bare button are the same 115px flex item at the same gap. It is now the fourth
 * child of the row instead of the twelfth; the demand and the wrap point are unmoved.
 *
 * So the row is 35.8px narrower than every figure above. The DEMAND and the WRAP point
 * above are still the 2026-10-04 measurements, taken on a live page -- this harness is a
 * different context and its own demand is not comparable, so re-measure before quoting
 * either. The -35.8px is the font-independent part and is the number to carry forward,
 * exactly the way +158.8px was.
 *
 * The button's width is state-DEPENDENT -- the two labels are both 13 characters but not
 * equal in a proportional font -- and it IS pinned, to the wider of the two (see
 * #inactive_wells_toggle below), because this row is `flex-wrap` and the button is not
 * its last child. The larger slack does not remove that hazard, it only moves it: at any
 * window width inside the 6.1px band between the two natural widths, the row fits one
 * line with "Hide Inactive" and not with "Show Inactive", so pressing the toggle would
 * drop the trailing fields onto a second line and cost the map the 53px of a line. A
 * pinned width deletes the band; the price is 6px of pill in the narrower state, which
 * is why the control reads 115px and not 108.3px above.
 *
 * Before the Formation box it was twelve children wanting 1272px; before that, nine
 * fields and ~880px and a wrap only under 1024px. It did once hold seventeen children
 * (the six session controls and the two readouts as well), which wanted ~1685px and
 * wrapped at 1600 and below. */
/* The TWO name boxes -- Operator, Formation -- grow into the spare, which is why
 * the row's laid-out width is its demand + 210px: each grows from its 68px basis to
 * its 173px cap, and 2 x 105 = 210. (Three boxes before 2026-10-16, when the Operator
 * pair became one multi-pick field: 3 x 105 = 315 then.) The growth is CAPPED, so it
 * is the same 210px in both cases above and stops wherever free space runs out first,
 * which at 1920 it does not: 546.7px free against the Texas row's 415.7px. Past the
 * cap growth cannot add width, and growth cannot add a line. See .m-field-grow below. */
/*  BASE / GAP. The three numbers that decide this row live here and nowhere else;
 *  swap them as a set. `.m-field-grow` reads --m-grow-basis/--m-grow-cap, this rule
 *  reads --m-controls-gap, and no other rule carries a copy.
 *
 *    demand = 994.3 + C + 2B + nG   (n = 11 without the region field, 12 with it)
 *    C = 123 -- the region field, which exists in the markup always but is
 *               `display:none` on every state but Texas, so C and the extra gap
 *               are Texas-only and the other 49 states keep the row they had.
 *
 *  The 2B / n-1 is the 2026-10-16 change: one name box and one child fewer. The
 *  994.3 constant is the 2026-10-04 figure and is NOT reconciled with the measured
 *  thresholds in the note above -- with B=68, G=8 it predicts 1182.5 (C=0) where the
 *  page measures 1335. The arithmetic is the model; the measurement is the fact.
 *
 *  LATER THE SAME DAY, Reset Zoom joined the command group, so the constant absorbs a
 *  fourth command button: **1095.3 = 994.3 + 101** (its measured width), and n gains
 *  the gap in front of it -- **12 without the region field, 13 with it**. Measured on
 *  the same probe: **1445** one line (C=0), i.e. +110 on the 1335 above. The model is
 *  no better reconciled than it was (it predicts 1327.3), so keep reading the sweep,
 *  not the formula.
 *
 *    B=68,  G=8  -> 1182.5 (C=0) / 1313.5 (C=123) by the model. SHIPPED -- and see
 *                    the note above for what it actually measures (**1445** one line
 *                    since the Reset Zoom button; it was 1335 before it). 1366 still
 *                    wraps, as it did before either change.
 *    B=60,  G=10 -> the same demands, but the two name labels land close enough to
 *                    read as one string. (Only FORMATION is 64.4px now, so this is
 *                    no longer the trap it was with two 67.9px OPERATOR labels.)
 *    B=84,  G=10 -> wider still: the row wraps earlier and costs 55px of map.
 *
 *  Why B has a floor: `.m-field` has no width of its own, so the field's width IS
 *  the flex-basis, while `.m-label` is `white-space: nowrap` and set to overflow
 *  (no `overflow: hidden`), so a label wider than its field spills into the gap.
 *  Measured text widths at `bold 10px` + 0.5px tracking (canvas measureText,
 *  cross-checked against TOP WELL SEARCH, whose 102.3px is exactly its field):
 *  FORMATION = 64.4. The two OPERATOR labels of 67.9 are gone: the field is one
 *  OPERATOR, measured 2026-10-16 at **58.2px** rendered (DOM Range, so it carries the
 *  CSS uppercase; the canvas figure agrees at 58.2). So the widest grow-field label is
 *  now FORMATION's 64.4, and the shipped B=68 still holds every label inside its box
 *  with 3.6px to spare; below 64.4 a label borrows the gap, and below B - G it would
 *  touch the next label. */
#mapping_controls {
  position: relative;
  z-index: 1;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--m-controls-gap);
  /* Water's header buttons row sits 14px under the subtitle. */
  margin-top: 14px;
}

/* One label-over-control pair.
 *
 * `position: relative` is the containing block for this field's `.m-clear`
 * button, which is absolutely positioned over the right end of the control.
 * Absolute children are OUT of flex flow, so it costs the field no height: it
 * stays label (~11) + gap 2 + control 32 = 45px, `#mapping_controls` does not
 * grow, and the row's demand arithmetic is untouched. An in-flow third child
 * would have made every field ~79px tall and cost map height to do it. */
.m-field {
  display: flex;
  flex-direction: column;
  gap: 2px;
  position: relative;
}
/* An author `display: flex` beats the UA's `[hidden] { display: none }` at any
 * specificity, so the attribute alone would leave the Formation box sitting in the
 * row -- exactly the trap `.m-session[hidden]` above documents. Written this way
 * because the field is in the markup always and hidden by capability: a state with
 * no pool data shows no box, and one that has it shows one at first paint with no
 * hidden->visible flash. */
.m-field[hidden] { display: none; }

.m-label {
  font-family: var(--m-font-body);
  font-weight: 700;
  font-size: 10px;
  line-height: 1.1;
  letter-spacing: 0.5px;
  text-transform: uppercase;
  /* Small uppercase labels use the darker ink for contrast on the cool card. */
  color: var(--m-ink);
  white-space: nowrap;
}

/* Latitude / Longitude / Count pills. */
.m-readout {
  font-family: var(--m-font-display);
  font-weight: 800;
  font-size: 14px;
  line-height: 1.2;
  color: var(--m-blue);
  background: linear-gradient(135deg, var(--m-card), var(--m-blue-soft));
  border: 2px solid var(--m-border);
  border-radius: var(--m-radius-sm);
  padding: 3px 8px;
  /* One width for all three, and wide enough for the widest value mapping.js can put
   * here. Measured: the glyphs of a 6-significant-figure longitude ("-106.568", what
   * `toPrecision(6)` yields) are 59px, so 59 + 16 padding + 4 border = 79 is the
   * minimum that holds every value -- 82 gives it 3px of slack. `min-width`, not
   * `width`, so a longer value grows the pill instead of being clipped, and `nowrap`
   * has something to hold. Without a floor the three pills would size to their own
   * text (62 empty, 74 and 79 filled) and, worse, would RESIZE the row when a map
   * click fills them in. */
  min-width: 82px;
  /* flex, not text-align: Latitude and Longitude start EMPTY, and a div with no text
   * has no line box, so it collapses to padding + border -- 10px, a sliver next to the
   * 32px controls. Flex gives the box a height that does not depend on having a value
   * and centres the value when a click does fill it. */
  display: flex;
  align-items: center;
  justify-content: center;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* The toolbar actions share the site's blue accent on white pills. The base
 * button rules above are canceled here so hover stays quiet on this card.
 * One accent color for every button in this card, because they are the same kind
 * of action -- the inactive-wells toggle ("Hide Inactive" / "Show Inactive") is
 * a switch rather than a command, but it sits in the same row and should not
 * read as a different species.
 * Padding is 7px 14px rather than Water's 9px 18px: the buttons that share the
 * title line with the title itself have to stay on one line with it at 1366. */
#mapping_toolbar button {
  background: rgba(255, 255, 255, 0.84);
  border: none;
  border-radius: 999px;
  padding: 7px 14px;
  font-family: var(--m-font-body);
  font-size: 13px;
  font-weight: 800;
  color: var(--m-blue);
  box-shadow: 0 4px 12px -4px rgba(32, 44, 64, 0.16);
  transition: background 0.15s, color 0.15s;
}

#mapping_toolbar button:hover {
  background: var(--m-blue-soft);
  color: var(--m-blue-deep);
  filter: none;
  box-shadow: 0 4px 12px -4px rgba(32, 44, 64, 0.16);
}

#mapping_toolbar button:active {
  filter: none;
  box-shadow: 0 1px 3px -1px rgba(32, 44, 64, 0.18);
}

/* The one control in this card whose LABEL changes when it is pressed, and so the only
 * one whose press can change the row's demand. It is pinned to the wider label --
 * "Show Inactive" measured 114.4px at this button's own font against 108.3px for
 * "Hide Inactive" -- so that both states occupy one box and no press can reach the
 * flex-wrap boundary (the row is the card's height, and the card's height is the map's).
 * 115px, not 114.4: a hair of slack for font-hinting differences between the loaded
 * face and its fallback. Re-derive from a canvas measureText of the WIDER label plus the
 * 14px + 14px padding in #mapping_toolbar button if the face ever changes.
 *
 * The dashboard skin overrides this with its rail buttons' width:100%, where the button
 * spans its field instead of hugging its text -- so the pin is inert there. */
#inactive_wells_toggle {
  min-width: 115px;
}

/* The session opener while a recording runs: it cannot be pressed, because the cluster it
 * hides holds the only Stop button there is (see mappingSessionControlsSet()). The hover
 * rule above has to be restated here or a dead control still lights up under the cursor. */
#mapping_toolbar button:disabled,
#mapping_toolbar button:disabled:hover {
  opacity: 0.55;
  cursor: not-allowed;
  background: rgba(255, 255, 255, 0.84);
  color: var(--m-blue);
}

#mapping_toolbar select,
#mapping_toolbar input[type="text"],
#mapping_toolbar input[type="number"] {
  height: auto;
  background: #fff;
  border: 2px solid var(--m-border);
  border-radius: var(--m-radius-sm);
  padding: 4px 8px;
  font-family: var(--m-font-body);
  font-weight: 700;
  font-size: 13px;
  color: var(--m-ink);
  transition: border-color 0.15s;
}

#mapping_toolbar select:hover,
#mapping_toolbar input[type="text"]:hover,
#mapping_toolbar input[type="number"]:hover {
  box-shadow: none;
}

#mapping_toolbar select:focus,
#mapping_toolbar input[type="text"]:focus,
#mapping_toolbar input[type="number"]:focus {
  outline: none;
  border-color: var(--m-blue);
}

/* The graph's floating control row shares the white, blue-accented treatment. */
#graph_type_select {
  color: var(--m-ink);
  background-color: var(--m-card);
  border-color: var(--m-border);
}

#google_earth_button,
#mapping_screenshot_graph_button,
#reset_graph_zoom_button,
#decay_curve_toggle,
#zoom_well_button {
  color: var(--m-blue);
  background-color: var(--m-card);
  border-color: var(--m-border);
}

#google_earth_button:hover,
#mapping_screenshot_graph_button:hover,
#reset_graph_zoom_button:hover,
#decay_curve_toggle:hover,
#zoom_well_button:hover {
  color: var(--m-blue-deep);
  background-color: var(--m-blue-soft);
}

/* Explicit widths: the old code relied on the inputs' default intrinsic size
 * (`size` attribute) plus hand-set inline widths. These are a few px narrower than
 * the old 90px/110px so the whole row fits on one line at 1920 -- see the gap note
 * in #mapping_controls. */
#mapping_toolbar input[type="text"] { width: 84px; }

/* The TWO name boxes -- Operator, Formation -- are the ones whose
 * contents is a NAME rather than a token, and at 68px the box holds 48px of text --
 * 21 of New Mexico's 1,681 operator strings fit whole, and the median needs 155px.
 * They grow into the row's free space rather than taking a fixed width, because a
 * fixed width wide enough for a name costs a second row -- and so 55px of map --
 * below 1366: the row is 1294.3px of the 1302px the card has there. Growth cannot do
 * that. Line breaking uses the flex BASE size, which is pinned to --m-grow-basis and
 * clamped by min-width, and the free space is shared out only afterwards, so the wrap
 * threshold is unchanged at every width.
 *
 * A THIRD grower is most of the reduction on its own: 561.7px of spare split three
 * ways instead of two. What that buys, with B=68 and the 173px cap:
 *     1920 -> 173 (the cap binds; was 260, -33.5%)
 *     1600 -> 148.6   (was 216, -31%)
 *     1440 ->  95.2   (was 136, -30%)
 *     1366 ->  70.6   (was  99, -29%)
 * The 173px cap fits ~90% of the names at 1920 (260px was one third wider to do the
 * same job); the longest is 392px and no cap reaches it. Numbers and the three
 * candidate shapes are in the BASE/GAP block on #mapping_controls -- that is where B
 * and the cap are chosen, not here. */
#mapping_toolbar .m-field-grow {
  flex: 1 1 var(--m-grow-basis);
  min-width: var(--m-grow-basis);
  max-width: var(--m-grow-cap);
}
/* `width: 100%` on the control inside a grow-field was declared HERE until 2026-10-16,
 * as `#mapping_toolbar .m-field-grow input[type="text"]`. It is gone because it matched
 * nothing: BOTH grow-fields (Operator, Formation) hold a `<button class="m-pick">` now,
 * and no grow-field holds an input at all. The declaration did not go away with it --
 * `#mapping_toolbar button.m-pick` below carries `width: 100%`, and this is the note that
 * says why that is safe: a percentage width resolves against the grown field only because
 * the flex-basis above is an explicit --m-grow-basis. Without that, it would fall back to
 * the control's intrinsic size while the line is being broken, making the box's
 * hypothetical width ~180px and moving the wrap threshold. Any future input added to a
 * grow-field needs the same declaration and this same reasoning. */

#mapping_toolbar #well_count_input { width: 100px; }
/* The interval: wide enough for "0.5" and its spinner, narrow enough that the title
 * cluster stays on one line at 1366. */
#mapping_toolbar #playback_step_seconds_input { width: 56px; padding: 4px 2px 4px 6px; }

/* All four kinds of row member agree on one height, so the labels above them line up
 * too. Placed after the per-kind rules so it wins on order as well as on specificity.
 * `border-box` is part of the fix rather than a style choice: on a content-box control
 * `height` ADDS the padding and the border, so setting it without this would make every
 * control taller instead of equal. See --m-control-h for what disagreed before. */
#mapping_toolbar button,
#mapping_toolbar select,
#mapping_toolbar input[type="text"],
#mapping_toolbar input[type="number"],
#mapping_toolbar .m-readout {
  box-sizing: border-box;
  height: var(--m-control-h);
}

#graph_legend_label_1,
#graph_legend_label_2 {
  font-weight: 600;
  letter-spacing: 0.02em;
}

/* ── ChartGL graph (lower-right quadrant) ───────────────────────────────── */
#chartgl_container {
  position: absolute;
  z-index: 50;
  overflow: hidden;
  border-radius: 4px;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.45);
}

#chartgl_container canvas {
  pointer-events: auto;
}

#graph_legend_label_1,
#graph_legend_label_2 {
  display: none;
}

/* ── Google Maps overlay ──────────────────────────────────────────────── */
#google_map {
  border-radius: 6px;
  box-shadow: 0 4px 18px rgba(0, 0, 0, 0.55);
  overflow: hidden;
}

/* Map tooltips and geographic labels are created at runtime. Their coordinates come
 * from Google Maps, while every static visual property stays in the selected skin. */
#google_map_hover_label,
#google_map_grid_hover_label {
  position: absolute;
  pointer-events: none;
  display: none;
  color: #fff;
  padding: 6px 9px;
  border-radius: 5px;
  font: 13px/1.45 system-ui, sans-serif;
  max-width: 340px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
}

#google_map_hover_label {
  z-index: 1000;
  background: rgba(0, 0, 0, 0.78);
}

#google_map_grid_hover_label {
  z-index: 999;
  background: rgba(0, 55, 159, 0.92);
}

.m-map-label-layer {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 1;
}

.m-map-section-label,
.m-map-township-range-label {
  position: absolute;
  text-align: center;
  font-weight: 700;
  pointer-events: none;
  z-index: 1;
  white-space: nowrap;
}

.m-map-section-label {
  width: 64px;
  color: rgba(40, 40, 40, 0.85);
  text-shadow: 0 0 3px rgba(255, 255, 255, 0.7), 0 0 2px rgba(255, 255, 255, 0.7);
}

.m-map-section-label[data-satellite="true"] {
  color: rgba(255, 255, 255, 0.85);
  text-shadow: 0 0 4px rgba(0, 0, 0, 0.9), 0 0 2px rgba(0, 0, 0, 0.9);
}

.m-map-section-label[data-label-size="small"] { font-size: 10px; }
.m-map-section-label[data-label-size="medium"] { font-size: 11px; }
.m-map-section-label[data-label-size="large"] { font-size: 13px; }

.m-map-township-range-label {
  width: 80px;
  color: #00379f;
  font: 700 11px system-ui, sans-serif;
  text-shadow: 0 0 3px rgba(255, 255, 255, 0.95), 0 0 2px rgba(255, 255, 255, 0.95);
}

.m-map-township-range-label[data-label-size="medium"] { font-size: 12px; }
.m-map-township-range-label[data-label-size="large"] { font-size: 14px; }

.m-webgl-well-tooltip {
  position: absolute;
  z-index: 1000;
  box-sizing: border-box;
  width: 450px;
  min-height: 100px;
  padding: 8px;
  color: var(--m-webgl-tooltip-text, rgba(0, 0, 255, 1));
  background: var(--m-webgl-tooltip-surface, rgba(200, 200, 200, 0.5));
  font-size: 16px;
  font-weight: 700;
  text-align: center;
  pointer-events: none;
}

.m-graph-grid-label {
  position: absolute;
  z-index: 1000;
  font-weight: 700;
  text-align: center;
  pointer-events: none;
}

.m-graph-grid-label-x { font-size: 14px; }
.m-graph-grid-label-y { font-size: 16px; }

.m-webgl-plss-label {
  position: absolute;
  z-index: 1000;
  color: var(--m-webgl-plss-label, #202c40);
  font-size: 10px;
  font-weight: 700;
  text-shadow: 0 0 2px rgba(255, 255, 255, 0.95), 0 1px 2px rgba(32, 44, 64, 0.25);
  pointer-events: none;
}

.m-group-clear-button,
.m-group-checkbox {
  position: absolute;
}

html[data-mapping-skin="current"] .m-group-clear-button {
  border: 1px solid var(--m-border, #e4eaf2);
  border-radius: 5px;
}

#playback_file_input {
  position: absolute;
  z-index: 100;
  left: 10px;
  width: 800px;
  height: 200px;
  background: yellow;
  overflow: hidden;
  visibility: hidden;
}

/* Latitude / Longitude, over the top centre of the map. A sibling of #google_map, placed
 * by positionGoogleMapContainer(); `transform` does the centring so the pills' width --
 * which changes as the values fill in -- never has to be measured in JS.
 *
 * No backing panel: the pills carry a solid light gradient of their own, and a third
 * surface over the map would be more chrome than the two numbers are worth. */
#map_latlon_readout {
  position: absolute;
  z-index: 120;              /* over the map (100), under the toolbar card (150) */
  display: flex;
  gap: 10px;
  transform: translateX(-50%);
  /* The readouts follow the cursor, so this sits exactly where the mouse is heading.
   * Without it the pills swallow the map's mousemove and freeze at the last value before
   * the cursor entered them. The cost is that the numbers cannot be selected or copied --
   * the right trade, since a frozen readout looks like a bug and an unselectable one does
   * not. */
  pointer-events: none;
}

/* These labels sit over satellite imagery, so keep them white with a dark shadow. */
#map_latlon_readout .m-label {
  color: #f2f9ff;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.9);
}

/* The height pin, restated. `#mapping_toolbar .m-readout { height: var(--m-control-h) }`
 * no longer reaches these two now that they have left the toolbar, and `.m-readout` has
 * no height of its own -- so an empty Latitude would collapse to padding + border = 10px,
 * the sliver the --m-control-h comment was written about. */
#map_latlon_readout .m-readout {
  box-sizing: border-box;
  height: var(--m-control-h);
}

/* The map key is outside #google_map so the Screenshot Map path can include its current
 * meaning in a composed header. Only the disclosure is interactive; the text body lets
 * map drags and marker clicks pass through. */
#mapping_map_legend {
  position: absolute;
  z-index: 125;
  top: 0;
  right: 0;
  color: #f7fbff;
  font: 12px/1.35 var(--m-font-body);
  pointer-events: none;
}

#mapping_map_legend details {
  width: min(300px, calc(100vw - 24px));
  border: 1px solid rgba(255, 255, 255, 0.52);
  border-radius: 6px;
  background: rgba(20, 31, 47, 0.88);
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.32);
  pointer-events: none;
}

#mapping_map_legend summary {
  padding: 7px 10px;
  color: white;
  font-weight: 700;
  cursor: pointer;
  pointer-events: auto;
  user-select: none;
}

#mapping_map_legend_body {
  padding: 0 10px 9px;
  pointer-events: none;
}

.m-map-key-note {
  margin-bottom: 6px;
  color: #d5e2f1;
}

.m-map-key-entry {
  display: flex;
  align-items: center;
  gap: 7px;
  min-height: 18px;
}

.m-map-key-swatch {
  flex: 0 0 12px;
  width: 12px;
  height: 12px;
  border: 1px solid rgba(255, 255, 255, 0.85);
  border-radius: 50%;
}

.m-map-key-ramp {
  height: 9px;
  margin: 3px 0 4px;
  border: 1px solid rgba(255, 255, 255, 0.72);
  border-radius: 5px;
}

.m-map-key-range {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  color: #d5e2f1;
  font-variant-numeric: tabular-nums;
}

/* ── Load progress ────────────────────────────────────────────────────── *
 * The two stages of the data load, over the middle of the map. Placed by
 * positionGoogleMapContainer() from map_viewport, and `transform` does the centring so
 * the panel's own width -- which changes as the caption wraps -- is never measured in
 * JS. A sibling of #google_map, never a child: see the markup in index.html.
 *
 * pointer-events:none, like the readout above. The app stays interactive through the
 * load -- that is the entire point of the cooperative-yield pass -- and an overlay that
 * swallowed a drag would undo it.
 *
 * NOT position:fixed and NOT z-index:10000. The graph screenshot collects tooltips with
 * a `body > div` query filtered on exactly those two values, and this would be taken
 * for one. */
#mapping_load_progress {
  position: absolute;
  z-index: 130;              /* over the map (100) and the readout (120), under the toolbar card (150) */
  transform: translate(-50%, -50%);
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 280px;
  max-width: 360px;
  padding: 14px 16px;
  background: linear-gradient(120deg, var(--m-card) 0%, var(--m-surface) 100%);
  border: 1px solid var(--m-border);
  border-radius: var(--m-radius-sm);
  box-shadow: var(--m-shadow);
  font-family: var(--m-font-body);
  color: var(--m-ink);
  pointer-events: none;      /* the map keeps every drag and click */
}

/* Restated because `display: flex` above beats the UA's `[hidden] { display: none }`,
 * which is a bare type-less rule and loses on specificity to an id selector. Without
 * this the overlay would be visible from first paint. */
#mapping_load_progress[hidden] {
  display: none;
}

.m-load-progress-title {
  font-family: var(--m-font-display);
  font-size: 15px;
  font-weight: 600;
}

#mapping_load_progress_verb.is-failed {
  color: var(--m-danger);
}

/* A stage that has not started yet is dimmed rather than hidden, so the panel does not
 * change height when the second one begins. */
.m-load-progress-row {
  display: flex;
  align-items: center;
  gap: 10px;
  opacity: 0.4;
  transition: opacity 0.25s ease;
}

.m-load-progress-row.is-active,
.m-load-progress-row.is-done {
  opacity: 1;
}

.m-load-progress-stage {
  flex: 0 0 auto;
  width: 68px;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--m-ink-soft);
}

.m-load-progress-track {
  flex: 1 1 auto;
  height: 6px;
  border-radius: 3px;
  background: rgba(2, 90, 214, 0.14);
  overflow: hidden;
}

.m-load-progress-fill {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 3px;
  background: var(--m-blue);
  transition: width 0.2s linear;
}

/* The denominator is unknown -- no Content-Length on the response, or a Cache API hit
 * with no XHR at all. The moving stripe is what distinguishes "under way, destination
 * unknown" from "finished and parked at 100%". */
.m-load-progress-fill.is-indeterminate {
  background-image: linear-gradient(115deg,
    rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 0.55) 50%, rgba(255, 255, 255, 0) 100%);
  background-size: 32px 100%;
  background-repeat: repeat-x;
  animation: mLoadStripe 0.9s linear infinite;
}

.m-load-progress-row.is-done .m-load-progress-fill {
  background: var(--m-success); /* done settles; only the active stage moves */
}

@keyframes mLoadStripe {
  from { background-position: 0 0; }
  to   { background-position: 32px 0; }
}

@media (prefers-reduced-motion: reduce) {
  .m-load-progress-fill.is-indeterminate {
    animation: none;
    background-image: none;
  }

  .m-load-progress-fill {
    transition: none;
  }
}

.m-load-progress-detail {
  min-height: 16px;          /* the panel does not resize as the caption fills in */
  font-size: 12px;
  line-height: 1.35;
  color: var(--m-ink-soft);
}

/* Hide the default Google Maps title overlay */
.custom-map-title {
  display: none !important;
}

/* ── Chat window (upper‑right quadrant) ──────────────────────────── */
#mapping_chat_container {
  position: absolute;
  z-index: 200;
  display: flex;
  flex-direction: column;
  /* GPT-6-Luna xhigh: use the reference site's light card colors. The small
   * transparency keeps the floating panel connected to the map underneath. */
  background: rgba(255, 255, 255, 0.96);
  border: 1px solid rgba(228, 234, 242, 0.95);
  border-radius: 10px;
  box-shadow: 0 8px 32px rgba(32, 44, 64, 0.20);
  overflow: hidden;
  font-family: system-ui, -apple-system, sans-serif;
}

#mapping_chat_header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 8px 12px;
  background: var(--m-surface);
  border-bottom: 1px solid var(--m-border);
  flex-shrink: 0;
}

#mapping_chat_header span {
  color: var(--m-ink);
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.02em;
}

#mapping_chat_header .m-chat-status {
  font-size: 10px;
  opacity: 0.5;
}

#mapping_chat_messages {
  flex: 1;
  overflow-y: auto;
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-height: 0;
  scroll-behavior: smooth;
}

#mapping_chat_messages::-webkit-scrollbar {
  width: 5px;
}

#mapping_chat_messages::-webkit-scrollbar-track {
  background: transparent;
}

#mapping_chat_messages::-webkit-scrollbar-thumb {
  background: rgba(32, 44, 64, 0.18);
  border-radius: 3px;
}

/* Sample questions -- a standing shelf of things to ask, above the input row.
 *
 * It does NOT disappear. Pressing a chip asks its question and swaps THAT chip for the next
 * question in the RING -- MAPPING_CHAT_SUGGESTIONS concatenated with
 * MAPPING_CHAT_SUGGESTION_POOL (mappingChatReplaceSuggestion) -- so the panel always offers
 * something to ask next; a message never removes the block.
 *
 * The ring is LONG on purpose: the deck is generated per state at load (37 state-agnostic
 * literals plus the operator and formation names the loaded catalogue admits -- 57 on New
 * Mexico, 13 on a grid-only state, never fewer than the 37), walked one slot per press, so a
 * question comes back only after every entry in the ring has been handed out. The pool used to
 * hold six (a lap of twelve), which is short enough that a user clicking through the shelf met
 * the same question twice in one sitting -- the recycling this shelf was reported for. Nothing here
 * sizes to the ring: the shelf still shows six chips and the CSS below is measured on those.
 *
 * THE BLOCK SITS ABOVE THE INPUT ROW, NOT IN THE TRANSCRIPT (mappingChatBuildSuggestions()
 * inserts it before #mapping_chat_input_row). That placement is what makes a permanent shelf
 * possible: in the transcript the conversation would be pushed down the panel and the chips
 * would be scrolled out of view by the first answer.
 *
 * Three columns is a measurement, not a preference. At 1366x768 the panel is 563px wide, so
 * each column is ~172px and the longer questions take two lines; at 1920x1080 the columns
 * are ~268px and every question is one line. A grid row is as tall as its tallest chip, so
 * the shelf runs 89px wide at 1920x1080 and 120px at 1366x768.
 *
 * The max-height is the other half of that, and it is load-bearing: the shelf is now
 * permanent, so without a cap it would hold the transcript at 22px at 1366x768 for the life
 * of the panel. 157px is the header (32) + the input row (53) + a 72px floor for the
 * transcript, so the transcript is never starved and the shelf scrolls instead -- at
 * 1366x768 that is 227 - 157 = 70px, one row of chips and a scroll to the second. At
 * 1920x1080 the shelf's own 89px is well under the cap and nothing scrolls. */
#mapping_chat_suggestions {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 6px;
  /* 12px to line up with the message bubbles' column; the 10px at the bottom is the gap
   * above the input row's own border-top. */
  padding: 0 12px 10px;
  /* LOAD-BEARING: without it the max-height below caps the CONTENT box and the shelf renders
   * 10px taller than its cap (measured: 80px against a 70px cap at 1366x768), taking those
   * 10px out of the transcript floor the cap exists to protect. */
  box-sizing: border-box;
  max-height: calc(100% - 157px);
  overflow-y: auto;
  /* A flex child of the column-direction #mapping_chat_container. It never grows, and the
   * max-height above is what stops it from eating the transcript. */
  flex-shrink: 0;
}

#mapping_chat_suggestions::-webkit-scrollbar {
  width: 5px;
}

#mapping_chat_suggestions::-webkit-scrollbar-track {
  background: transparent;
}

#mapping_chat_suggestions::-webkit-scrollbar-thumb {
  background: rgba(32, 44, 64, 0.18);
  border-radius: 3px;
}

#mapping_chat_suggestions p {
  grid-column: 1 / -1;
  margin: 0;
  font-size: 10.5px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--m-ink-soft);
}

.chat-suggestion {
  /* LOAD-BEARING: the global `button` rule (index.css:31) is `white-space: nowrap`, which
   * would run a 40-character question straight out of the panel instead of wrapping it. */
  white-space: normal;
  text-align: left;
  font-size: 11.5px;
  line-height: 1.35;
  padding: 5px 9px;
  border-radius: 8px;
  background: var(--m-surface);
  border: 1px solid var(--m-border);
  color: var(--m-ink);
}

/* The send button's blue, so a chip reads as the same kind of thing as the rest of the
 * surface. The global button:hover brightness/shadow still apply on top of this. */
.chat-suggestion:hover {
  background: var(--m-blue-soft);
  border-color: rgba(2, 90, 214, 0.30);
  color: var(--m-blue-deep);
}

.chat-msg {
  max-width: 88%;
  padding: 7px 11px;
  border-radius: 10px;
  font-size: 12.5px;
  line-height: 1.45;
  word-wrap: break-word;
  animation: chatFadeIn 0.2s ease-out;
}

@keyframes chatFadeIn {
  from { opacity: 0; transform: translateY(6px); }
  to   { opacity: 1; transform: translateY(0); }
}

.chat-msg-user {
  align-self: flex-end;
  background: var(--m-blue);
  color: #fff;
  border-bottom-right-radius: 3px;
}

.chat-msg-other {
  align-self: flex-start;
  background: var(--m-surface);
  color: var(--m-ink);
  border-bottom-left-radius: 3px;
}

#mapping_chat_input_row {
  /* THE CONTAINING BLOCK for the row's two overlay buttons (the × and the arrow at
   * the end of this file), which is what it takes to park them inside the input's
   * box. This is the chat row's `.m-field` -- the toolbar's name boxes get the same
   * from `.m-field { position: relative }`, and until 2026-10-06 this row was
   * `static`, which is the whole reason its two buttons used to be in flow.
   *
   * The arithmetic that makes plain `right` offsets land on the input's edge: an
   * absolutely positioned child is placed against the PADDING box, and the input's
   * right edge is exactly 52px inside it -- 10px of this padding + Send's 36px +
   * Send's 6px margin-left. So `right: 1px` is 1px inside the input's edge, the
   * toolbar's own inset. Nothing measures the row at runtime: all three numbers are
   * in this file.
   *
   * The row stays 53px (1 border + 8 + 36 + 8) because both buttons are out of flow
   * -- which matters, because the sample shelf's cap is `calc(100% - 157px)`, and
   * 157 is 32 header + 53 row + a 72px transcript floor. */
  position: relative;
  display: flex;
  gap: 0;
  padding: 8px 10px;
  border-top: 1px solid var(--m-border);
  background: var(--m-surface);
  flex-shrink: 0;
}

#mapping_chat_input {
  flex: 1;
  padding: 8px 11px;
  border: 1px solid var(--m-border);
  border-radius: 7px;
  background: var(--m-card);
  color: var(--m-ink);
  font-size: 13px;
  font-family: inherit;
  outline: none;
  transition: border-color 0.15s ease;
}

#mapping_chat_input:focus {
  border-color: var(--m-blue);
}

#mapping_chat_input::placeholder {
  color: var(--m-ink-soft);
}

#mapping_chat_send {
  width: 36px;
  height: 36px;
  margin-left: 6px;
  padding: 0;
  border: none;
  border-radius: 7px;
  background: var(--m-blue);
  color: #fff;
  font-size: 16px;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.15s ease;
  flex-shrink: 0;
}

#mapping_chat_send:hover {
  background: var(--m-blue-hover);
}

/* ── Clear (×) buttons ──────────────────────────────────────────────────── *
 * Seven text inputs carry a button that empties them: the five toolbar boxes,
 * the chat question box and the graph caption. It is the pattern graphing.html
 * already ships (#searchInputClear, canvas.css) -- an overlay on the right end
 * of the box with the input's padding reserved for it.
 *
 * THIS SECTION SITS BELOW EVERY RULE IT CAN TIE WITH, ON PURPOSE: several rules
 * here win a specificity TIE against rules above (the toolbar's `padding: 4px
 * 8px`, this row's `padding: 8px 11px`), and source order is the tie-break. It
 * was the LAST section in the file until 2026-10-06, when the chat box's
 * suggestion list was appended after it; that block cannot reach this invariant
 * -- it styles one element of its own and that element's own buttons, which
 * share no selector with anything here. Anything appended later must be checked
 * the same way, not assumed safe.
 *
 * VISIBILITY IS PURE CSS, not a sync function. WellMapState.setState(),
 * playbackWidgetCommand(), undo/redo, updateFormationFieldVisibility() and
 * mappingChatSend() all assign `.value` without dispatching anything, and
 * Chrome autofill on Well Search / Top Well Search fires no event at all -- so
 * a JS "show the button when the box is not empty" needs a hook at every one of
 * those sites and still goes stale on the next writer. `:placeholder-shown`
 * re-derives from the DOM every frame instead. That is also what makes these
 * buttons testable in cdp_toolbar_layout.js, whose harness rebuilds its page
 * from index.html and runs no mapping.js: a JS sync could never light one up
 * there. It is why the inputs carry a whitespace placeholder -- without one the
 * pseudo-class never matches and the button would show permanently. */
.m-clear {
  position: absolute;
  display: none;
  border: none;
  background: transparent;
  padding: 0;
  margin: 0;
  cursor: pointer;
  border-radius: 3px;
  align-items: center;
  justify-content: center;
  font-family: var(--m-font-body);
  font-size: 15px;
  font-weight: 400;
  line-height: 1;
  /* The caption is a light readout; the toolbar and chat row define their own
   * clear-button colors below. */
  color: var(--m-ink-soft);
}
.m-clear:hover {
  color: var(--m-blue);
  background: var(--m-blue-soft);
}

/* The five toolbar boxes. Id-scoped rather than a bare class on purpose:
 * `#mapping_toolbar button` further up is a white pill with border-radius:999px,
 * a drop shadow, 7px 14px padding and the brand blue, and its :hover / :active
 * are (1,1,1) -- all three outrank a plain `.m-clear` or `.m-clear:hover`. Only
 * a rule naming BOTH the id and the class beats them.
 *
 * HEIGHT IS NOT SET HERE. `#mapping_toolbar button` already pins it to
 * var(--m-control-h) = 32px, which is exactly the input row (the field is
 * label ~11 + gap 2 + control 32, so `bottom: 0` spans the last 32px) and
 * exactly what cdp_toolbar_layout.js rounds into its single height bucket. An
 * id-scoped height would outrank that rule and break the 32px assertion. */
#mapping_toolbar button.m-clear {
  right: 1px;
  bottom: 0;
  width: 20px;
  padding: 0;
  background: transparent;
  border: none;
  border-radius: 3px;
  box-shadow: none;
  font-size: 14px;
  font-weight: 400;
  color: rgba(2, 90, 214, 0.45);
}
#mapping_toolbar button.m-clear:hover {
  background: rgba(2, 90, 214, 0.10);
  color: var(--m-blue);
  box-shadow: none;
}

/* ══ The type-ahead arrows ═══════════════════════════════════════════════
 *
 * ONE button, worn by every box that offers a suggestions list: the two
 * toolbar name boxes (Operator, Formation) and the chat's question box. Ours, and
 * not Chrome's, for a reason that decides the whole design: a
 * native `<datalist>` marker is painted by the UA only while the box is hovered
 * or focused, which is exactly the affordance the user asked to stop hiding --
 * and the chat box could never show one at all, because its `list=` was detached
 * at rest by design (the 2026-10-02 fix, MAPPING_DOCUMENTATION.md section 8.7)
 * and re-attaching it at rest would reintroduce the popup that fix removed. So the
 * chat needed a custom arrow no matter what, and having invented it, the three
 * toolbar boxes wear the same one: one mechanism, one look, and "always visible"
 * that does not depend on a UA pseudo-element's hover rules.
 *
 * The four boxes split since 2026-10-06, and the arrow is the whole of what they
 * still share. The three toolbar boxes still open Chrome's native listbox. The chat
 * box does not: it carries no `list=` at ANY point now, and this button opens the
 * page's own list at the end of this file, because the ask was for a larger list and
 * the UA popup's size cannot be styled. One `.m-dropdown`, two destinations.
 *
 * The native marker is hidden on the three toolbar boxes
 * (`::-webkit-calendar-picker-indicator` below) so the box cannot show two arrows
 * once focused. The chat box left that rule when it stopped carrying `list=`. It is
 * a Chromium pseudo-element and unobservable in a headless probe -- if a future
 * Chrome stops matching it, the native marker reappears UNDER ours (both sit at
 * the right edge) rather than breaking anything.
 *
 * Unlike `.m-clear` this is never display:none: it is the always-visible half of
 * the pair. The × still comes and goes with the box's text. */
.m-dropdown {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: transparent;
  padding: 0;
  margin: 0;
  cursor: pointer;
  border-radius: 3px;
  font-family: var(--m-font-body);
  font-size: 11px;
  font-weight: 400;
  line-height: 1;
  /* Toolbar arrows supply their accent color in the scoped rule below. */
  color: var(--m-ink-soft);
}
.m-dropdown:hover {
  color: var(--m-blue);
  background: var(--m-blue-soft);
}

/* The three toolbar arrows. Same id+class scoping as `.m-clear` above and for
 * the same reason (`#mapping_toolbar button` is a white pill at (1,0,1)).
 *
 * `right: 1px` puts the arrow on the outer edge, and the `.m-field-grow` rule
 * further down moves the × in to 21px to make room. The arrow takes the outer
 * edge because it is the permanent one of the pair; the × still comes and goes
 * with the box's text, so it is the one that should be the harder to hit.
 *
 * HEIGHT IS NOT SET HERE, exactly as for the ×: `#mapping_toolbar button` pins
 * it to var(--m-control-h) = 32px, which is what cdp_toolbar_layout.js rounds
 * into its single height bucket. An id-scoped height would outrank that rule. */
#mapping_toolbar button.m-dropdown {
  /* ABSOLUTE, like the × -- and this line is load-bearing twice over. `.m-field` is a
   * flex COLUMN, so a static button is a third flex item: it stacks under the input and
   * makes every name field ~30px taller, which costs map height, and `right`/`bottom`
   * below would do nothing at all. The field is already `position: relative` for the ×,
   * so this needs no other change. */
  position: absolute;
  right: 1px;
  bottom: 0;
  width: 18px;
  padding: 0;
  background: transparent;
  border: none;
  border-radius: 3px;
  box-shadow: none;
  font-size: 11px;
  font-weight: 400;
  color: rgba(2, 90, 214, 0.5);
}
#mapping_toolbar button.m-dropdown:hover {
  background: rgba(2, 90, 214, 0.10);
  color: var(--m-blue);
  box-shadow: none;
}

/* THE REVEAL. Each rule names its own input, so a button can only ever light
 * up from its own box's text. `~` is a general sibling, so the button must
 * stay the immediate next sibling of its input in the markup -- see the
 * comment in index.html. Hidden while the box is empty, which is what
 * cdp_toolbar_layout.js's control-count arithmetic assumes on a fresh load. */
#mapping_toolbar input[type="text"]:not(:placeholder-shown) ~ .m-clear { display: inline-flex; }
#mapping_chat_input:not(:placeholder-shown) ~ .m-clear { display: inline-flex; }
#well_data_graph_label:not(:placeholder-shown) ~ .m-clear { display: inline-flex; }

/* Room for the glyph so characters never run underneath it. Every input here
 * is box-sizing:border-box, so this narrows the CONTENT only: `width: 84px`,
 * `#well_count_input { width: 100px }`, `#mapping_toolbar button.m-pick { width: 100% }`
 * and the caption's inline width are all outer widths and none of them move.
 * The row's demand and its wrap threshold are untouched. These come after both
 * rules they override -- the toolbar's `padding: 4px 8px` and this file's chat
 * row `padding: 8px 11px`.
 *
 * The chat box's 42px is the odd one out in this group, and deliberately: its two
 * neighbours carry ONE button each, and the chat box carries two. 41px is 1px inset
 * + the 18px arrow + 2px + the 20px ×, so 42 leaves the same 1px of slack the
 * toolbar's 40px buys for the same pair -- see the two-button paragraph below. */
#mapping_toolbar input[type="text"] { padding-right: 20px; }
#mapping_chat_input { padding-right: 42px; }
#well_data_graph_label { padding-right: 28px; }

/* NOBODY IS LEFT HERE (2026-10-16). This was
 * `#mapping_toolbar .m-field-grow input[type="text"] { padding-right: 40px }`, the extra
 * room the two-button boxes needed: the × and the ALWAYS-visible arrow, against one button
 * for every other box. It narrowed twice -- the Operator pair became one pick pill, then
 * Formation followed -- and the last input in a grow-field went with the second change, so
 * the rule matched nothing and is deleted.
 *
 * The 40px did not go away, and the reasoning is worth keeping because it is now the pill's:
 * `#mapping_toolbar button.m-pick` below carries `padding: 4px 40px 4px 8px` for exactly this
 * pair. The cost is 20px of LABEL AREA, and it only matters because these fields are the
 * narrow ones: at 1440 they are ~95px wide, so the label area goes from ~67px to ~47px; at
 * 1366 they are ~71px, so it goes from ~43px to ~23px and a long name is cut sooner than it
 * used to be (the pill ellipsises rather than scrolling -- a name list has no caret to move).
 * The alternative -- giving the arrow the ×'s slot and showing whichever is relevant -- was
 * rejected: the user asked for an arrow that is ALWAYS visible, and it would have vanished
 * exactly when a user with a typo in the box wants to browse the list again. */

/* The ×'s new slot, inside the arrow's. Same (1,2,1) trick as the padding rule. */
#mapping_toolbar .m-field-grow button.m-clear { right: 21px; }

/* DELETED (2026-10-16): `#formation_input::-webkit-calendar-picker-indicator
 * { display: none }`.
 *
 * Chrome draws its own datalist marker for any input carrying `list=`, at the right
 * edge -- on top of our arrow once the box is focused. It is the UA's, so it cannot be
 * moved, only hidden. That rule was written for the boxes carrying `list=` AND an arrow,
 * and it emptied out from under itself in three steps: the chat box on 2026-10-06 with
 * its `list=` (it opens the page's own list now), the two operator boxes on 2026-10-16
 * with theirs, and Formation last, when it became a `<button class="m-pick">` opening
 * #m_formation_pick.
 *
 * There is now no native `<datalist>` anywhere in the app and no `list=` attribute in
 * index.html, so the rule had nothing left to match. If a `list=` is ever added back to a
 * toolbar box, this rule has to come back with it -- the marker sits under our arrow and a
 * headless probe cannot confirm it either way (see section 8.7's limitation on the UA
 * popup), so its absence would be invisible to the test suite. */

/* ══ The name pickers: Operator and Formation ════════════════════════════
 *
 * TWO fields, one mechanism. Operator holds up to ten operators, each painted in its own
 * colour from the group palette (mappingOperatorSelectionColor, mapping.js); Formation
 * holds any number and paints nothing (a narrowing predicate, not a series) -- see
 * MAPPING_DOCUMENTATION section 8.7 and section 5.1. Together they replace the pair of
 * Operator type-ahead boxes AND the Formation type-ahead box, so the row carries one fewer
 * field than it did and no box in it is a native datalist any more.
 *
 * The control is a BUTTON where those were inputs, and that is why it needs its own
 * surface rules: `#mapping_toolbar button` is the app's white rounded-PILL button
 * (999px radius, brand-blue ink, 7px 14px padding, a drop shadow) and would otherwise
 * make these the controls in the row that do not line up with the selects and
 * inputs beside them. (1,1,1) against that rule's (1,0,1), and this block comes after
 * the `.m-clear`/`.m-dropdown` pair so it also beats their (1,1,1) hover rules on
 * order.
 *
 * The rule is keyed on `.m-pick` alone, NOT on `#operator_pick`: both pills wear the class,
 * so the second field cost no new CSS here at all. The one place they differ visually is the
 * tint, and that is an inline background the Operator path sets on the element -- Formation
 * has no tint rule and no tint writer. */
#mapping_toolbar button.m-pick {
  display: block;
  width: 100%;
  min-width: 0;
  box-sizing: border-box;
  height: var(--m-control-h);
  /* The 40px on the right is the × + arrow pair's room, and it is exactly the 40px the
   * deleted `#mapping_toolbar .m-field-grow input[type="text"] { padding-right: 40px }`
   * used to buy -- this rule is where that declaration went, for the reason spelled out
   * where it stood. The label ellipsises rather than scrolling: a name list has no caret
   * to move. */
  padding: 4px 40px 4px 8px;
  background: #fff;
  border: 2px solid var(--m-border);
  border-radius: var(--m-radius-sm);
  box-shadow: none;
  font-family: var(--m-font-body);
  font-weight: 700;
  font-size: 13px;
  color: var(--m-ink);
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
}
#mapping_toolbar button.m-pick:hover,
#mapping_toolbar button.m-pick:focus {
  background: #fff;
  box-shadow: none;
  color: var(--m-ink);
  outline: none;
  border-color: var(--m-blue);
}
/* Open, not hovered: the popover stands under the field, so the border stays lit
 * while the pointer is anywhere else on the page. Set from the same attribute the
 * pill carries for the same reason. */
#mapping_toolbar button.m-pick[aria-expanded="true"] {
  border-color: var(--m-blue);
}

/* THE × REVEAL, for the two pick fields. The rules above light a `.m-clear` from its
 * own input's `:placeholder-shown`; a button has no placeholder, so the label refreshers
 * (mappingUpdateOperatorPickLabel and mappingUpdateFormationPickLabel, mapping.js) set
 * `.m-has-pick` on the FIELD instead, and this reads it. Keyed on the field's class and
 * not on an id, so it covered the Formation field the day it became a pill with no change
 * here. `.m-field-grow button.m-clear { right: 21px }` above still places the button --
 * only the visibility comes from here. Like the rules it mirrors, it is one of the things
 * cdp_toolbar_layout.js's control-count arithmetic assumes on a fresh load: nothing picked
 * means nothing displayed. */
#mapping_toolbar .m-field.m-has-pick button.m-clear {
  display: inline-flex;
}

/* ── The picker's popover ────────────────────────────────────────────────────
 *
 * ONE block for BOTH pickers. The Operator popover (#m_operator_pick) and the
 * Formation popover (#m_formation_pick) are the same widget with different policy,
 * so they are skinned by class -- `.m-pick-pop`, which the shared builder
 * (mappingPickListBuild) sets -- rather than by id. The ids remain on the elements
 * for the CDP probes and for a stack trace. This was `#m_operator_pick` throughout
 * until 2026-10-16; the selector changed, no declaration did.
 *
 * The chat's suggestion list is the model, and it is on document.body for the same
 * reason: the toolbar card clips its own overflow, so a list drawn inside the field
 * would be cut off. `position: fixed`, placed from the pill's rect by
 * mappingPickListPlace(), and `z-index: 250` -- NEVER 10000, which is the exact
 * pair the graph screenshot sweep collects `body > div` for.
 *
 * WIDER THAN ITS FIELD, deliberately, and the one place this departs from the chat
 * list: a toolbar name field is 68-173px wide and a name needs ~200px, so the
 * popover takes a floor of 280px, clamped to the window in JS. A list as narrow as
 * the field would ellipsise every row. */
.m-pick-pop {
  position: fixed;
  z-index: 250;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.98);
  border: 1px solid var(--m-border);
  border-radius: var(--m-radius-sm);
  box-shadow: 0 8px 32px rgba(32, 44, 64, 0.20);
}
.m-pick-pop[hidden] {
  display: none;
}

.m-pick-pop .m-pick-search {
  box-sizing: border-box;
  width: 100%;
  height: auto;
  padding: 7px 10px;
  border: none;
  border-bottom: 1px solid var(--m-border);
  border-radius: 0;
  background: #fff;
  font-family: var(--m-font-body);
  font-size: 13px;
  font-weight: 400;
  color: var(--m-ink);
}
.m-pick-pop .m-pick-search:focus {
  outline: none;
  border-bottom-color: var(--m-blue);
}

.m-pick-pop .m-pick-list {
  flex: 1 1 auto;
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* One row: swatch, name, well count, tick. 30px tall against the chat list's 34 -- these are
 * one-line names, not wrapped questions, and a screenful of them has to fit. Only the
 * Operator rows carry a swatch and only the Formation rows a well count, so the middle two
 * are each conditional; the name takes whatever is left either way. */
.m-pick-pop .m-pick-item {
  display: flex;
  align-items: center;
  gap: 7px;
  width: 100%;
  padding: 6px 10px;
  border: none;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  text-align: left;
  font-family: inherit;
  font-size: 13px;
  font-weight: 400;
  line-height: 1.3;
  color: var(--m-ink);
  cursor: pointer;
}
.m-pick-pop .m-pick-item:hover {
  background: var(--m-blue-soft);
  color: var(--m-blue-deep);
}
/* The keyboard cursor, moved by ↑/↓ and spent by Enter. Same fill as :hover on purpose
 * -- the row the mouse is over and the row the keyboard is on are the same row as far as
 * the reader is concerned, and the two never show at once in practice. Placed after the
 * :hover rule so a re-render that puts the cursor back on the hovered row does not
 * flicker between two shades. */
.m-pick-pop .m-pick-item.m-pick-highlighted {
  background: var(--m-blue-soft);
  color: var(--m-blue-deep);
}
/* The leading box, and the one place the two controls' rows differ.
 *
 * OPERATOR's is a colour swatch, transparent until its row is picked -- the colour is the
 * pick's position in the list, so an unpicked row has no colour to show. Its inline
 * background is set by mappingPickListBuildRow from the spec's `swatchColorHex`. */
.m-pick-pop .m-pick-swatch {
  flex: 0 0 auto;
  width: 10px;
  height: 10px;
  border-radius: 2px;
  border: 1px solid rgba(32, 44, 64, 0.25);
  background: transparent;
}
/* FORMATION's is a checkbox -- same box, same geometry (so the two lists' names start at the
 * same x), but it reports SELECTION rather than identity. A formation pick has no palette
 * slot and never repaints a well, so a box that filled with a colour would claim an
 * assignment that never happens; filling with the accent ink says only "this row is picked",
 * which is what the trailing tick says too. It is not a `.m-pick-swatch` for that reason, and
 * it needs no inline style: unlike a palette slot there is nothing per-pick to carry, so the
 * stylesheet owns both states and mappingPickListBuildRow only has to create the element. */
.m-pick-pop .m-pick-box {
  flex: 0 0 auto;
  width: 10px;
  height: 10px;
  border-radius: 2px;
  border: 1px solid rgba(32, 44, 64, 0.25);
  background: transparent;
}
.m-pick-pop .m-pick-item[aria-selected="true"] .m-pick-box {
  background: var(--m-blue);
  border-color: var(--m-blue);
}
.m-pick-pop .m-pick-name {
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The row's right-hand annotation: how many wells the pool holds. It is NOT a
 * `.m-pick-name`'s sibling in the flex sense -- the name takes all the slack and this sits
 * at the end, before the tick -- so it neither grows nor shrinks and a long pool name
 * ellipsises rather than pushing the count off the row. Formation's deck is the only one
 * that has one (Operator's rows are names with nothing to count), which is why the builder
 * only appends the element when the spec's `rowSuffix` returns something. */
.m-pick-pop .m-pick-wells {
  flex: 0 0 auto;
  font-size: 11px;
  color: var(--m-ink-soft);
  font-variant-numeric: tabular-nums;
}
/* THE MATCH ROW -- the search box's own text offered as a pick, above the deck. Deliberately
 * not mistakable for a pool name: italic, in the accent ink, with the name slot carrying a
 * "Match all N pools containing …" sentence rather than a name. It is a row in every
 * mechanical sense (it counts toward the keyboard walk, it ticks, it carries a value in
 * dataset.name), so it must not LOOK like the deck it is listed above. */
.m-pick-pop .m-pick-item.m-pick-match .m-pick-name {
  font-style: italic;
  color: var(--m-blue-deep);
}
.m-pick-pop .m-pick-item.m-pick-match {
  border-bottom: 1px solid var(--m-border);
}
.m-pick-pop .m-pick-check {
  flex: 0 0 auto;
  font-size: 12px;
  line-height: 1;
  color: var(--m-blue);
  visibility: hidden;
}
.m-pick-pop .m-pick-item[aria-selected="true"] .m-pick-check {
  visibility: visible;
}

/* The footer. One element carrying whichever of the three lines applies: "N of M" while
 * the search narrows the deck (the g_mapping_chat_chart_bar_limit idiom), the cap refusal
 * ("10 is the maximum — unpick one first."), or the inert "nothing matches" note. Only one
 * can ever be true at a time. The third line -- "N of 10 picked." against Formation's
 * bare "N picked." -- is the one the two controls word for themselves; see the footer's
 * `footText` in each spec (mapping.js). */
.m-pick-pop .m-pick-foot {
  padding: 6px 10px;
  border-top: 1px solid var(--m-border);
  font-size: 11.5px;
  line-height: 1.3;
  color: var(--m-ink-soft);
}

.m-pick-pop .m-pick-list::-webkit-scrollbar { width: 5px; }
.m-pick-pop .m-pick-list::-webkit-scrollbar-track { background: transparent; }
.m-pick-pop .m-pick-list::-webkit-scrollbar-thumb {
  background: rgba(32, 44, 64, 0.18);
  border-radius: 3px;
}

/* The chat's × is an OVERLAY inside the input's box, like the toolbar's, and since
 * 2026-10-06 it is the row's first overlay rather than a 26px column. It used to be
 * the one `.m-clear` that restated `position: static`; that override is gone and the
 * base rule's `position: absolute` is now what places it, against the row -- see the
 * row's `position: relative` above for the 52px the offsets are measured from.
 *
 * `right: 73px` is the toolbar's `right: 21px` mirrored: 21px inside the input's edge
 * is 52 + 21 = 73px inside the padding box. `top: 8px` is the row's padding-top, so
 * the button is level with the input; the 36px height is the input's own, which is
 * what `bottom: 0` gives the toolbar's buttons in their 32px field.
 *
 * The DOM order is unchanged -- the × is still the input's immediate next sibling and
 * the arrow still follows it -- because `~` in the reveal rule and this button's
 * `previousElementSibling` read both depend on it. Out of flow is a styling change,
 * not a structural one. */
#mapping_chat_input_clear {
  right: 73px;
  top: 8px;
  width: 20px;
  height: 36px;
  color: var(--m-ink-soft);
}
#mapping_chat_input_clear:hover {
  background: var(--m-blue-soft);
  color: var(--m-blue);
}

/* The chat box's arrow, built in initializeMapApplication beside the × above and
 * for the same reason: initMappingChat is read as SOURCE TEXT by
 * test_chat_suggestions.js.
 *
 * `position: absolute` has to be STATED here, unlike the ×'s: `.m-dropdown`'s base
 * rule declares no position, so this rule is what makes it an overlay at all. `right:
 * 53px` is the input's edge (52px) + the toolbar's own 1px inset, and `width: 18px`
 * is the toolbar's width -- the two arrows are the same size in the same slot.
 *
 * It was an in-flow 26px column until 2026-10-06, and the comment here used to say an
 * overlay was impossible -- "the input is the first item, and a button parked at the
 * input's right edge would need a wrapper this row does not have". The wrapper is not
 * needed: `position: relative` on the row IS the containing block. The row now reads
 * [box with × and ▾ inside it][↑], and the input is 60px wider for it. */
#mapping_chat_input_dropdown {
  position: absolute;
  right: 53px;
  top: 8px;
  width: 18px;
  height: 36px;
  color: var(--m-ink-soft);
}
#mapping_chat_input_dropdown:hover {
  background: var(--m-blue-soft);
  color: var(--m-blue);
}

/* The caption's button is placed by placeMappingGraphQuadrant() in mapping.js,
 * against the caption's own inline box: the caption is itself absolutely
 * positioned by that function and lives outside every .m-field, so there is no
 * wrapper to hang an overlay off. Only its looks live here.
 *
 * It sits ABOVE the suggestion-list section below rather than after it, where it
 * was until 2026-10-06: the suggestion list was appended at the end, and leaving
 * the caption's block last put it under a header that is not about it. */
#well_data_graph_label_clear {
  color: var(--m-ink-soft);
  font-size: 16px;
  z-index: 101;
}
#well_data_graph_label_clear:hover {
  background: var(--m-blue-soft);
  color: var(--m-blue);
}

/* ── The chat box's suggestion list ──────────────────────────────────────────
 *
 * Replaces the native `<datalist>` popup the chat box used until 2026-10-06 -- see
 * MAPPING_DOCUMENTATION section 8.7, which records the reversal and why. Chrome paints
 * a datalist's listbox as browser UI outside the page's box model, so neither its font
 * nor its height can be reached by any rule in this file; the ask was a LARGER list, so
 * the only implementation is a list we draw.
 *
 * WHY IT IS NOT A CHILD OF THE PANEL. #mapping_chat_container is `overflow: hidden`,
 * and a list hanging below the input row (the panel's last child) would be clipped at
 * the panel's own bottom edge. So the element lives on `document.body` and is placed
 * with `position: fixed` from the input's rect by mappingChatSuggestionListPlace().
 *
 * `z-index: 250`, NOT 10000. The graph screenshot collects `body > div` elements that
 * are `position: fixed` AND `z-index: 10000` exactly (mapping.js, the tooltip sweep in
 * screenshotMappingGraph) and would take this for one. Any other value is safe; this
 * one is above the panel (200) and below the toolbar's card (which is 150 but is not
 * on the same axis as a fixed element -- the 250 is for the panel and the graph's own
 * controls). `position: fixed` also keeps it out of html2canvas's reach, since the map
 * screenshot runs on the map element and this is not inside it.
 *
 * Placement, size and the flip above the input when there is no room below are all in
 * JS; only the surface is here. */
#mapping_chat_suggestion_list {
  position: fixed;
  z-index: 250;
  box-sizing: border-box;
  overflow-y: auto;
  overscroll-behavior: contain;
  display: flex;
  flex-direction: column;
  /* Elevated white list with a blue-gray edge remains distinct over both the
   * chart and map backgrounds. */
  background: rgba(255, 255, 255, 0.98);
  border: 1px solid var(--m-border);
  border-radius: var(--m-radius-sm);
  box-shadow: 0 8px 32px rgba(32, 44, 64, 0.20);
}
#mapping_chat_suggestion_list[hidden] {
  display: none;
}

/* One row. 34px tall -- 8px padding, 13.5px text at 1.35 -- against Chrome's own
 * ~20px datalist rows, which is the whole point of the change. `white-space: normal`
 * and `width: 100%` so a long question wraps to a second line instead of being cut
 * off the way the UA popup cuts it. */
#mapping_chat_suggestion_list button {
  display: block;
  width: 100%;
  padding: 8px 12px;
  border: none;
  background: transparent;
  border-radius: 0;
  box-shadow: none;
  text-align: left;
  white-space: normal;
  font-family: inherit;
  font-size: 13.5px;
  line-height: 1.35;
  font-weight: 400;
  color: var(--m-ink);
  cursor: pointer;
}
#mapping_chat_suggestion_list button:hover {
  background: var(--m-blue-soft);
  color: var(--m-blue-deep);
}

/* Not a button: the "no question matches" line, which must not invite a click.
 * The muted ink still clears AA on the white list surface because this is prose. */
#mapping_chat_suggestion_list .m-chat-sugg-empty {
  padding: 8px 12px;
  font-size: 13.5px;
  line-height: 1.35;
  color: var(--m-ink-soft);
}

/* The chat's own scrollbar triple, copied from #mapping_chat_messages above. */
#mapping_chat_suggestion_list::-webkit-scrollbar { width: 5px; }
#mapping_chat_suggestion_list::-webkit-scrollbar-track { background: transparent; }
#mapping_chat_suggestion_list::-webkit-scrollbar-thumb {
  background: rgba(32, 44, 64, 0.18);
  border-radius: 3px;
}


/* Only the customer-dashboard skin exposes its live summary strip. */
#dashboard_app_header,
#dashboard_data_coverage_panel,
#dashboard_status_strip,
#dashboard_workspace_toolbar,
#dashboard_filter_panel,
#dashboard_details_panel,
#dashboard_data_footer,
#dashboard_metrics,
#dashboard_map_panel_header {
  display: none;
}

/* These handles are shared by the Current and Customer Dashboard skins. Keep them out of
 * layout-preview; each supported skin provides its own geometry and handle treatment. */
#dashboard_map_splitter,
#dashboard_stack_splitter {
  display: none;
}

html[data-mapping-skin="current"] #dashboard_map_splitter,
html[data-mapping-skin="current"] #dashboard_stack_splitter {
  position: absolute;
  z-index: 205;
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  border: 0;
  border-radius: 8px;
  background: transparent;
  touch-action: none;
  user-select: none;
}

html[data-mapping-skin="current"] #dashboard_map_splitter {
  top: var(--current-map-splitter-top, 72px);
  left: var(--current-map-splitter-left, 50vw);
  width: 18px;
  height: var(--current-map-splitter-height, calc(100vh - 80px));
  transform: translateX(-50%);
  cursor: col-resize;
}

html[data-mapping-skin="current"] #dashboard_stack_splitter {
  top: var(--current-stack-splitter-top, 50vh);
  left: var(--current-stack-splitter-left, 50vw);
  width: var(--current-stack-splitter-width, 50vw);
  height: 18px;
  transform: translateY(-50%);
  cursor: row-resize;
}

html[data-mapping-skin="current"] #dashboard_map_splitter::before,
html[data-mapping-skin="current"] #dashboard_stack_splitter::before {
  position: absolute;
  border-radius: 3px;
  background: #b8c5d6;
  content: "";
  transition: background-color 120ms ease, box-shadow 120ms ease;
}

html[data-mapping-skin="current"] #dashboard_map_splitter::before {
  width: 2px;
  height: calc(100% - 24px);
}

html[data-mapping-skin="current"] #dashboard_stack_splitter::before {
  width: calc(100% - 24px);
  height: 2px;
}

html[data-mapping-skin="current"] #dashboard_map_splitter::after,
html[data-mapping-skin="current"] #dashboard_stack_splitter::after {
  position: absolute;
  border: 1px solid #d2dce8;
  border-radius: 5px;
  background-color: #fff;
  background-image: radial-gradient(#70839c 1px, transparent 1.2px);
  background-position: center;
  background-repeat: repeat;
  box-shadow: 0 1px 4px rgba(32, 44, 64, 0.18);
  content: "";
}

html[data-mapping-skin="current"] #dashboard_map_splitter::after {
  width: 8px;
  height: 34px;
  background-size: 5px 6px;
}

html[data-mapping-skin="current"] #dashboard_stack_splitter::after {
  width: 34px;
  height: 8px;
  background-size: 6px 5px;
}

html[data-mapping-skin="current"] #dashboard_map_splitter:hover::before,
html[data-mapping-skin="current"] #dashboard_map_splitter:focus-visible::before,
html[data-mapping-skin="current"] #dashboard_stack_splitter:hover::before,
html[data-mapping-skin="current"] #dashboard_stack_splitter:focus-visible::before {
  background: #025ad6;
  box-shadow: 0 0 0 2px rgba(2, 90, 214, 0.14);
}

html[data-mapping-skin="current"] #dashboard_map_splitter:focus-visible,
html[data-mapping-skin="current"] #dashboard_stack_splitter:focus-visible {
  outline: 2px solid #025ad6;
  outline-offset: 1px;
}

html[data-mapping-skin="current"] body.m-mapping-resizing {
  user-select: none;
}

html[data-mapping-skin="current"] body.m-mapping-resizing #dashboard_map_splitter {
  cursor: col-resize;
}

html[data-mapping-skin="current"] body.m-mapping-resizing #dashboard_stack_splitter {
  cursor: row-resize;
}
