/* Scalehouse powered by AvlingAI — Avling brand tokens (Crop Gauge set) */
:root {
  --forest: #2e5e1e; --forest-hover: #234a16; --sage: #87a05c; --green-l: #cfe8c4;
  --deep-1: #1c3a12; --deep-2: #12240b;
  --gold: #c89a14; --amber: #a86e10; --amber-bg: #faf1dd; --taupe: #6b5749; --taupe-bg: #efe7de;
  --red: #8b1a1a; --red-bg: #f2dcd6;
  --cream: #f2f0ea; --surf: #faf9f5; --ink: #22301a; --brown2: #52503d; --line: #e4e0d2; --muted: #6f6a5e;
  /* CHART TOKENS — NOT THEMABLE, and that is the whole point.
     app.js overwrites --forest with the organization's own accent_color, so --forest is a
     BRAND token, not a palette value. A data mark painted with it changes colour per
     customer: Demo Seed Company picked #1f4e79 and every plot dot on the season page came
     out blue while the progress bar beside it stayed forest green. Worse, any contrast or
     colour-vision checking done on the real palette is void the moment a customer picks a
     pale accent and the marks vanish against the cream.
     Data marks use these. Brand chrome — buttons, links, active tabs — keeps --forest. */
  --mark: #2e5e1e;          /* a plot */
  --mark-ref: #22301a;      /* a reference the marks are read against */
  --mark-up: #2e5e1e;       /* diverging: the kind side */
  --mark-down: #a86e10;     /* diverging: the hard side (validated against --mark-up:
                               normal-vision dE 19.9, protan 10.4 — both clear) */

  /* CROP IDENTITY — corn is gold, soybeans are green, wheat is amber.
     Mark, 25 Sep: be consistent about crop colour the way the other modules are. These are
     the tokens that convention lives in, and they are NOT themable for the same reason the
     marks above are not — a customer accent of #1f4e79 would turn every soybean mark blue.

     COLOUR IS IDENTITY HERE AND NOTHING ELSE. It never says how a crop DID. The season bar
     stays diverging (--mark-up / --mark-down) because on that bar the colour IS the verdict:
     paint a soybean bar green by crop and a soybean row that lost 7.7 points draws green
     pointing left, which reads as good. Crop colour goes on the LABEL; direction stays on
     the bar. Two meanings, two channels.

     Validated, not eyeballed — corn/soy adjacent dE 24.0 protan, 30.0 normal; all three
     pass the lightness, chroma, CVD and normal-vision checks together. Two caveats that
     the CODE has to honour, because the palette alone does not:
       1. Gold on --surf is 2.53:1, under the 3:1 floor. A gold mark is legal BESIDE a word
          and never as small text on its own, so every crop mark ships with its crop named.
       2. Wheat reuses the --mark-down hue. On an identity mark next to the word "Wheat"
          that is fine; it is the reason a crop dot is never the only thing distinguishing
          two figures. No fourth brand hue clears CVD against these two — brand red against
          forest is dE 1.3 under deuteranopia, invisible to roughly one man in twelve. */
  --crop-corn: #c89a14;
  --crop-soybeans: #2e5e1e;
  --crop-wheat: #a86e10;
  --radius: 14px;
  font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  color: var(--ink); background: var(--cream);
}
* { box-sizing: border-box; }
[hidden] { display: none !important; }
body { margin: 0; background: var(--cream); color: var(--ink); font-size: 15px; line-height: 1.5; }
a { color: var(--forest); }
h1 { font-size: 24px; letter-spacing: -0.015em; margin: 0; }
h2 { font-size: 16px; margin: 0; }

.topbar { background: linear-gradient(180deg, var(--deep-1), var(--deep-2)); color: #e9efdd; padding: 12px 16px;
  display: flex; align-items: center; gap: 12px; position: sticky; top: 0; z-index: 10; }
.brand { display: flex; flex-direction: column; line-height: 1.1; text-decoration: none; }
.brand b { color: var(--gold); letter-spacing: 0.1em; font-size: 17px; font-weight: 800; }
.brand span { color: var(--green-l); font-size: 11px; }
.topbar nav { display: flex; gap: 4px; margin-left: 12px; flex-wrap: wrap; }
.topbar nav a { color: #e9efdd; text-decoration: none; padding: 8px 12px; border-radius: 9px; font-size: 14px; }
.topbar nav a[aria-current="page"] { background: rgba(207, 232, 196, 0.16); color: #fff; font-weight: 600; }
.topbar .who { margin-left: auto; font-size: 13px; color: var(--green-l); display: flex; gap: 10px; align-items: center; }

main { max-width: 1280px; margin: 0 auto; padding: 20px 16px 80px; display: flex; flex-direction: column; gap: 16px; }

/* Pages whose job is a wide table get the screen.
   1280px is the right measure for reading — prose, forms, a plot's planting page —
   and the wrong one for a sixteen-column entries grid, which needs about 1600px and
   therefore scrolled sideways on EVERY monitor, including a 27-inch one. Capping by
   viewport as well as by pixels keeps the gutters sane on an ultrawide instead of
   stretching the table to the bezels. */
main.wide { max-width: min(2100px, 96vw); }

/* ...but a paragraph must not inherit that width. A note set 1900px wide is a line
   the eye cannot track back from. */
main.wide p.sub, main.wide .fine { max-width: 105ch; }

/* Nor may an SVG chart. A viewBox scales every unit inside it — bar heights, label
   type, row spacing — so giving a chart the full width of a table page does not
   give it more resolution, it just magnifies it. Capped a little above its natural
   760 so it reads as a chart rather than a poster; the table under it still uses
   the whole page. */
main.wide .chart { max-width: 960px; }

/* ...but the across-plots charts have two real axes rather than a name gutter and
   a bar, so extra width buys actual resolution in x rather than just bigger type.
   A little more room, still nowhere near the width of the table under them. */
main.wide .chart.xy, main.wide .chart.matrix { max-width: 1120px; }

/* Four KPI cards on a 1900px row become four 450px cards holding one number each.
   Let them fill to a sensible size and stop. */
main.wide .grid { grid-template-columns: repeat(auto-fill, minmax(190px, 260px)); justify-content: start; }

/* The entries grid specifically: sixteen columns, so every pixel of cell padding is
   spent sixteen times over. Tightened here rather than globally, so the tables that
   only carry eight columns keep the roomier spacing. */
table.dense td, table.dense th { padding-left: 7px; padding-right: 7px; }
table.dense td input { min-height: 34px; }
/* Six digits at 14px is about 50px of glyphs; 88 was sized for a roomier table. */
table.dense td input.num { width: 78px; }
.head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.head .sub { color: var(--muted); font-size: 14px; }
.head .actions { margin-left: auto; display: flex; gap: 10px; flex-wrap: wrap; }

.card { background: #fff; border: 1px solid var(--line); border-radius: var(--radius); padding: 18px; display: flex; flex-direction: column; gap: 14px; }
.card.flush { padding: 0; overflow: hidden; }
.label { font-size: 12px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); }

.btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; min-height: 44px; padding: 0 18px; border-radius: 11px;
  font-size: 15px; font-weight: 600; border: 1px solid transparent; cursor: pointer; text-decoration: none; background: var(--forest); color: #fff; }
.btn:hover { background: var(--forest-hover); }
.btn.secondary { background: #fff; color: var(--forest); border-color: #c9c4b2; }
.btn.secondary:hover { background: var(--surf); }
.btn.small { min-height: 36px; padding: 0 12px; font-size: 13px; }
.btn[disabled] { opacity: 0.55; cursor: default; }
.icon-btn { width: 40px; height: 40px; padding: 0; }

.grid { display: grid; gap: 12px; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); }
.field { display: flex; flex-direction: column; gap: 5px; font-size: 12px; font-weight: 600; color: #3a2f26; }
.field.wide { grid-column: 1 / -1; }
input, select, textarea { font: inherit; font-size: 15px; min-height: 42px; padding: 0 10px; border: 1px solid var(--line); border-radius: 9px;
  background: var(--surf); color: var(--ink); width: 100%; }
textarea { padding: 8px 10px; min-height: 70px; resize: vertical; }
input:focus, select:focus, textarea:focus { outline: 2px solid rgba(46, 94, 30, 0.35); border-color: var(--forest); }
.req > .t::after { content: " •"; color: var(--amber); font-weight: 800; }

details.section { background: #fff; border: 1px solid var(--line); border-radius: var(--radius); }
details.section > summary { cursor: pointer; padding: 16px 18px; font-weight: 700; display: flex; gap: 10px; align-items: baseline; list-style: none; }
details.section > summary::-webkit-details-marker { display: none; }
details.section > summary .sub { font-weight: 400; color: var(--muted); font-size: 13px; }
details.section > .body { padding: 0 18px 18px; }

.table-wrap { overflow-x: auto; }
table { border-collapse: collapse; width: 100%; font-size: 14px; }
th { text-align: left; font-size: 11px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; color: var(--muted);
  background: var(--surf); padding: 10px; border-bottom: 1px solid var(--line); white-space: nowrap; }
td { padding: 6px 10px; border-bottom: 1px solid var(--line); vertical-align: middle; }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
td input { min-height: 36px; font-size: 14px; }
td input.num { text-align: right; width: 88px; }
tr.check td:first-child { box-shadow: inset 3px 0 0 var(--sage); }

.pill { display: inline-flex; align-items: center; min-height: 26px; padding: 0 10px; border-radius: 999px; font-size: 12px; font-weight: 600; white-space: nowrap; }
.pill.ok { background: var(--forest); color: #fff; }
.pill.warn { background: var(--amber-bg); color: var(--amber); }
.pill.muted { background: #efece2; color: var(--muted); }
.pill.bad { background: var(--red-bg); color: var(--red); }

.empty { text-align: center; padding: 36px 16px; color: var(--muted); }
.toast { position: fixed; left: 50%; bottom: 20px; transform: translateX(-50%); background: var(--ink); color: #fff; padding: 12px 18px;
  border-radius: 12px; font-size: 14px; z-index: 50; max-width: 92vw; box-shadow: 0 6px 24px rgba(0, 0, 0, 0.2); }
.toast.error { background: var(--red); }
.saving { font-size: 12px; color: var(--muted); }

.center-card { max-width: 440px; margin: 8vh auto 0; }
.tabs { display: flex; gap: 4px; padding: 4px; background: #e9e6dc; border-radius: 12px; width: fit-content; }
.tabs button { border: none; background: transparent; min-height: 38px; padding: 0 16px; border-radius: 9px; font: inherit; font-weight: 600; cursor: pointer; color: var(--ink); }
.tabs button[aria-selected="true"] { background: var(--forest); color: #fff; }

/* capture */
.photo-slot { border: 1px dashed #c9c4b2; border-radius: 12px; background: #efece2; min-height: 150px; display: flex; align-items: center;
  justify-content: center; overflow: hidden; color: var(--muted); font-size: 13px; }
.photo-slot img { width: 100%; height: 100%; object-fit: contain; max-height: 360px; }
.checklist { display: flex; flex-direction: column; gap: 8px; padding: 14px; border-radius: 12px; background: var(--amber-bg); }
.checklist div { display: flex; gap: 8px; align-items: center; font-size: 14px; }
.dot { width: 22px; height: 22px; border-radius: 999px; display: inline-flex; align-items: center; justify-content: center; color: #fff; font-size: 12px; font-weight: 800; flex-shrink: 0; }
.dot.ok { background: var(--forest); } .dot.warn { background: var(--amber); }

/* The crop mark. Shared, so the convention is defined once and every module that shows a
   crop picks it up — this is the piece Pedigree and the plot list inherit next.
   It is a MARK, never the identification on its own: the crop is always named beside it
   (see the tokens above — gold is under the contrast floor and one reader in twelve cannot
   separate green from amber). Square rather than round because it sits beside tabular
   figures and a circle reads as a bullet. */
.crop-dot {
  display: inline-block; width: 8px; height: 8px; border-radius: 2px;
  flex-shrink: 0; vertical-align: baseline; background: var(--muted);
}
.crop-dot.corn     { background: var(--crop-corn); }
.crop-dot.soybeans { background: var(--crop-soybeans); }
.crop-dot.wheat    { background: var(--crop-wheat); }
.steps { display: flex; gap: 14px; font-size: 13px; font-weight: 700; color: var(--muted); flex-wrap: wrap; }
.steps .on { color: var(--forest); }
.lowconf input { border-color: var(--amber); background: var(--amber-bg); }
.big-camera { min-height: 64px; font-size: 17px; width: 100%; }

@media (max-width: 720px) {
  .topbar nav, .topbar .who .email { display: none; }
  td a { white-space: nowrap; }
  h1 { font-size: 20px; }
}

/* ---- White label: a dealership's mark in the bar, ours kept small ---------- */
.brand-link { display: flex; align-items: center; gap: 12px; text-decoration: none; min-width: 0; }
.brand-logo { height: 30px; max-width: 132px; object-fit: contain; background: #fff; border-radius: 7px; padding: 3px 6px; }
.brand-name { color: #fff; font-size: 16px; font-weight: 700; letter-spacing: -0.01em; white-space: nowrap; }
.brand-link .powered { color: var(--green-l); font-size: 10.5px; white-space: nowrap; opacity: 0.92; }
.topbar .brand b { color: var(--gold); letter-spacing: 0.1em; font-size: 17px; font-weight: 800; }

/* ---- Tabs that are links (control room) ----------------------------------- */
.steps a { color: var(--muted); text-decoration: none; padding: 7px 12px; border-radius: 9px; border: 1px solid transparent; }
.steps a:hover { background: var(--surf); }
.steps a.on { color: #fff; background: var(--forest); }

/* ---- Brand kit controls ---------------------------------------------------- */
.swatch { width: 36px; height: 36px; border-radius: 10px; border: 1px solid var(--line); cursor: pointer; padding: 0; }
.swatch:focus-visible { outline: 3px solid rgba(46, 94, 30, 0.26); outline-offset: 2px; }
.toggle { width: 46px; height: 26px; border-radius: 999px; border: none; background: #d8d3c4; cursor: pointer;
  display: flex; align-items: center; padding: 0 3px; justify-content: flex-start; transition: background 120ms; }
.toggle.on { background: var(--forest); justify-content: flex-end; }
.toggle::after { content: ""; width: 20px; height: 20px; border-radius: 999px; background: #fff; }

@media (max-width: 720px) {
  .brand-name { display: none; }
  .brand-link .powered { display: none; }
}

/* ---- Field photos ----------------------------------------------------------- */

/* The chips are the whole interaction on the capture screen: one tap, with a
   glove on, in the sun. Big targets, high contrast, no hover-only states. */
.chips { display: flex; flex-wrap: wrap; gap: 8px; }
.chip { font: inherit; font-size: 15px; font-weight: 600; min-height: 44px; padding: 0 16px;
  border-radius: 999px; border: 1px solid #c9c4b2; background: #fff; color: var(--ink); cursor: pointer; }
.chip:hover { border-color: var(--forest); }
.chip.on { background: var(--forest); border-color: var(--forest); color: #fff; }
.chip:focus-visible { outline: 3px solid rgba(46, 94, 30, 0.3); outline-offset: 2px; }

/* A count riding on the photo button, so a rep can see at a glance which rows
   they have already shot. */
.icon-btn .count { position: absolute; top: -6px; right: -6px; min-width: 18px; height: 18px;
  border-radius: 999px; background: var(--forest); color: #fff; font-size: 11px; font-weight: 700;
  display: flex; align-items: center; justify-content: center; padding: 0 4px; }
.icon-btn { position: relative; display: inline-flex; align-items: center; justify-content: center; }
.icon-btn.disabled { opacity: 0.35; cursor: not-allowed; }

.photo-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 14px; }
.photo-card { margin: 0; background: #fff; border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden;
  display: flex; flex-direction: column; }
.photo-card figcaption { padding: 10px 12px 12px; display: flex; flex-direction: column; gap: 6px; }
.photo-open { display: block; width: 100%; aspect-ratio: 4 / 3; padding: 0; border: none; background: var(--surf); cursor: pointer; }
.photo-open img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ---- a map must not paint over the page ------------------------------------
   Leaflet numbers its own insides high — panes at 400, controls at 800, the
   corners at 1000 — and those numbers are only private to the map if the map
   element makes a stacking context. It does not make one on its own: it sits in
   the normal flow with no z-index, so its children were competing in the ROOT
   context against everything else on the page. Against .lightbox at 50, 400 wins,
   and the map drew straight through the dimmed backdrop and over the open chart
   (Mark, 24 Sep, with a picture of it). The same arithmetic beats the sticky
   topbar at 10 and a toast at 50, so this was never about which chart was open —
   any overlay, on any page with a map, and the map came through.

   `isolation: isolate` makes the container a stacking context and nothing more:
   no new box, no clipping, no repaint cost. Leaflet's 400s and 800s become
   relative to the map instead of to the document, which is what they always
   meant. Put on .leaflet-container rather than on one page's map card so a map
   added to a page nobody is thinking about today cannot bring the bug back. */
.leaflet-container { isolation: isolate; }

.lightbox { position: fixed; inset: 0; background: rgba(20, 26, 16, 0.72); display: flex; align-items: flex-start;
  justify-content: center; padding: 24px 16px; overflow: auto; z-index: 50; }
.lightbox-inner { background: #fff; border-radius: var(--radius); padding: 18px; max-width: 900px; width: 100%;
  display: flex; flex-direction: column; gap: 14px; }

.facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px 18px; }
.facts .sub { font-size: 11px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; }

/* What a model saw is kept in its own box, under its own heading, in its own
   color. It is never folded into a note or a chip — see the migration. */
.ai-box { background: var(--surf); border: 1px dashed #c9c4b2; border-radius: 10px; padding: 12px 14px; }
.sub.ai { font-style: italic; }

/* ---- Treatments: the drawer behind the chip -------------------------------- */
#drawer-host .lightbox { z-index: 60; }
#picker-layer .lightbox, #create-layer .lightbox { z-index: 70; }

.comp-list { display: flex; flex-direction: column; gap: 10px; margin-bottom: 10px; }
.comp { border: 1px solid var(--line); border-radius: 10px; padding: 10px 12px;
        display: flex; flex-direction: column; gap: 8px; background: var(--surf); }

.picker-list { display: flex; flex-direction: column; gap: 6px; max-height: 300px; overflow-y: auto; }
.picker-row { font: inherit; text-align: left; width: 100%; background: #fff; border: 1px solid var(--line);
              border-radius: 10px; padding: 9px 12px; cursor: pointer; display: flex;
              flex-direction: column; gap: 2px; color: var(--ink); }
.picker-row:hover { border-color: var(--forest); background: var(--surf); }
.picker-row:focus-visible { outline: 3px solid rgba(46, 94, 30, 0.3); outline-offset: 2px; }

/* The duplicate check. Deliberately styled as information, not as a warning that
   blocks: the create button below it stays enabled the entire time. */
.dupes { border: 1px dashed #c9c4b2; border-radius: 10px; padding: 12px 14px; background: var(--surf);
         display: flex; flex-direction: column; gap: 6px; margin: 10px 0; }

/* A confidential entry in the entries grid. The dealer's own screen still shows the
   real hybrid — the point of the marker is to remind her which rows will print as
   "Competitor" before she hands the sheet over, not to hide anything from her. */
tr.private td:first-child { box-shadow: inset 3px 0 0 var(--amber); }
tr.private td { background: #fdf8ec; }

/* ---- Publishing a harvest sheet ---------------------------------------------
   The bar sits above the tabs because the state is true of the whole plot, not of
   whichever tab is open. Three states, three colors, and the sentence that says
   what the state MEANS rather than just naming it — "Published" tells a rep
   nothing; "counted, and read-only" tells them what to expect when they click. */
.pub-bar { display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  background: #fff; border: 1px solid var(--line); border-left: 4px solid var(--line);
  border-radius: var(--radius); padding: 12px 16px; }
.pub-bar.published { border-left-color: var(--forest); }
.pub-bar.reopened  { border-left-color: var(--amber); background: var(--amber-bg); }
.pub-bar.open      { border-left-color: #c9c4b2; }
.pub-state { display: flex; align-items: center; gap: 10px; }
.pub-blurb { flex-grow: 1; min-width: 220px; font-size: 13px; color: var(--brown2); line-height: 1.5; }
.pub-bar > .btn { margin-left: auto; }

/* The confirmation. Not "are you sure" — four specific claims, each of which a rep
   has actually got wrong, with the Publish button dead until every one is ticked. */
.claim { display: flex; gap: 10px; align-items: flex-start; font-size: 14px;
  line-height: 1.45; cursor: pointer; padding: 8px 10px; border-radius: 10px;
  border: 1px solid var(--line); background: var(--surf); }
.claim:hover { border-color: var(--sage); }
.claim input { width: 20px; height: 20px; min-height: 20px; flex-shrink: 0; margin-top: 1px; padding: 0; }
.claim b { font-weight: 600; display: block; }
.claim-warn { display: block; margin-top: 3px; font-size: 12.5px; color: var(--amber); }

/* A frozen box still shows its number — a published sheet you cannot READ would be
   absurd — but it stops looking like somewhere to type. */
input.locked, select.locked, textarea.locked { background: #f4f2ec; color: var(--brown2);
  border-style: dashed; cursor: not-allowed; }
.plot-locked .icon-btn[disabled] { opacity: 0.3; }

/* ---- Saving, said out loud --------------------------------------------------
   The data was always safe; it was invisibly safe, which to somebody used to a
   paper sheet is the same as not safe. This is the part they can see. */
.saving { display: inline-flex; align-items: center; gap: 7px; font-size: 13px;
  color: var(--muted); white-space: nowrap; }
.saving::before { content: ""; width: 8px; height: 8px; border-radius: 999px;
  background: #c9c4b2; flex-shrink: 0; }
.saving.ok::before   { background: var(--forest); }
.saving.busy::before { background: var(--gold); animation: sh-pulse 1s ease-in-out infinite; }
.saving.bad          { color: var(--red); font-weight: 600; }
.saving.bad::before  { background: var(--red); }
@keyframes sh-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.25; } }
@media (prefers-reduced-motion: reduce) { .saving.busy::before { animation: none; } }

/* ------------------------------------------------------ the sandbox bar
   Under the top bar on every screen of a sandbox account, which is none of
   them today. Amber rather than red: this is not a problem, it is a fact with
   a date on it. */
.sandbar-host { position: sticky; top: 0; z-index: 30; }
.sandbar {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  background: var(--amber-bg); border-bottom: 1px solid #e8d7ab;
  padding: 10px 22px; font-size: 13.5px; line-height: 1.5; color: var(--ink);
}
.sandbar .g { flex: 1; min-width: 240px; }
.sandbar b { color: var(--amber); }
.sandbar-tag {
  font-size: 11px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--amber); background: #fff; border: 1px solid #e8d7ab;
  border-radius: 999px; padding: 3px 10px;
}

.sandbox-form { margin: 14px 22px; display: flex; flex-direction: column; gap: 12px; }
.sandbox-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; }
.sandbox-choice { border: 1px solid var(--line); border-radius: 12px; padding: 11px 13px; margin: 0; }
.sandbox-choice legend { font-size: 12.5px; font-weight: 700; color: var(--muted); padding: 0 5px; }
.sandbox-choice label { display: flex; align-items: center; gap: 9px; padding: 5px 0; font-size: 14px; flex-wrap: wrap; }
.sandbox-choice input[type="email"] { flex: 1; min-width: 200px; }
.sandbox-keep { display: flex; align-items: flex-start; gap: 9px; font-size: 14px; line-height: 1.5; }
.sandbox-keep input { margin-top: 3px; }
.sandbox-actions { display: flex; gap: 9px; flex-wrap: wrap; }
@media (max-width: 700px) {
  .sandbar { padding: 10px 14px; }
  .sandbox-form { margin: 12px 14px; }
}

/* ---- The ground -------------------------------------------------------------
   The computed panel on the plot page: what the app worked out from the plot's
   own boundary. Read-only by nature, so it is styled as a reading rather than a
   form — no boxes, no borders around individual values, nothing that invites a
   click that would do nothing. */
.ground { display: flex; flex-direction: column; gap: 12px; }

.ground-stats {
  display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
}
.ground-stat { line-height: 1.25; }
.ground-val { font-size: 1.35rem; font-weight: 600; }
.ground-lab { font-size: .8rem; }
.ground-note { font-size: .75rem; opacity: .8; }

/* The sentence this panel exists for. A left rule carries the verdict's weight
   without a colored block, which on a form page reads as an alert. */
.ground-verdict {
  margin: 0; padding: 8px 0 8px 12px; border-left: 3px solid var(--line);
  font-size: .92rem; line-height: 1.45;
}
.ground-verdict.even  { border-left-color: var(--forest); }
.ground-verdict.mixed { border-left-color: var(--gold); }
.ground-verdict.split { border-left-color: var(--amber); }
/* The season verdict rides the same rule. A kind season is the good news here, so it
   takes the forest green the even-wetness verdict uses, and a hard one the amber. */
.ground-verdict.kind     { border-left-color: var(--forest); }
.ground-verdict.ordinary { border-left-color: var(--line); }
.ground-verdict.hard     { border-left-color: var(--amber); }

/* OUR numbers, set apart from USDA's on purpose.
   NCCPI is 0 to 1, the AvlingAI pair is 0 to 100 and the season index is 0 to 2 with 1.00
   as normal. Three scales on one panel is exactly how two unrelated measures end up read
   as comparable, so ours sit under their own heading with a rule above them rather than
   continuing the same list of figures. */
.ground-ours-head {
  margin: 4px 0 0; font-size: .75rem; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; color: var(--muted);
  border-top: 1px solid var(--line); padding-top: 10px;
}
.ground-ours { grid-template-columns: repeat(auto-fit, minmax(128px, 1fr)); }

/* Hover an index and it explains itself.
   Mark, 25 Sep. Deliberately NOT the native `title` tooltip: that one cannot be styled,
   takes about a second to appear, truncates a sentence this long, and does nothing at all
   on touch — and this form is used standing in a field on a phone more than at a desk.
   Shown on :hover AND :focus-within, with the stat focusable, so a TAP reaches it too. */
.ground-stat.has-tip {
  position: relative;
  cursor: help;
  border-radius: 6px;
  outline-offset: 3px;
}
.ground-stat.has-tip .ground-lab { border-bottom: 1px dotted var(--muted); display: inline-block; }
.ground-tip {
  position: absolute; z-index: 30; left: 0; top: calc(100% + 8px);
  width: max-content; max-width: min(320px, 78vw);
  padding: 10px 12px; border-radius: 8px;
  background: #16261d; color: #f3f1ea; border: 1px solid rgba(255,255,255,.14);
  box-shadow: 0 8px 24px rgba(0,0,0,.28);
  font-size: .8rem; font-weight: 400; line-height: 1.5; text-transform: none;
  /* visibility rather than display, so it is measurable and transitions; pointer-events
     off so the tip can never sit between the cursor and the thing it describes. */
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity .12s ease, visibility .12s;
}
/* SCOPED TO .has-tip, NOT TO .ground-stat.
   The first version of this rule named .ground-stat, so the season page's distribution and
   season cells — which carry .has-tip and a .ground-tip exactly as the plot panel does —
   rendered the help cursor and nothing else: hovering a plot dot gave you a question mark
   and no explanation, because the tip was there and permanently hidden. Any element that
   carries .has-tip now works, which is the only version of this rule that cannot be
   forgotten by the next cell that needs one. */
.has-tip:hover > .ground-tip,
.has-tip:focus-within > .ground-tip,
.has-tip:focus > .ground-tip { opacity: 1; visibility: visible; }
.has-tip { position: relative; }

/* The last column's tip would otherwise run off the right edge. */
.ground-stats .ground-stat:last-child > .ground-tip,
.ground-stats .ground-stat:nth-last-child(2) > .ground-tip { left: auto; right: 0; }

@media (max-width: 480px) {
  /* On a phone the grid is two columns and a 320px tip would overhang either side, so it
     spans the panel instead of anchoring to the stat. */
  .ground-tip { left: 0; right: auto; max-width: 86vw; }
  .ground-stats .ground-stat:nth-child(even) .ground-tip { left: auto; right: 0; }
}

.ground-soil { margin: 0; font-size: .95rem; }
.ground-lab, .ground-note { color: var(--muted); }
.ground-prov { margin: 0; font-size: .75rem; color: var(--muted); line-height: 1.5; }

@media (max-width: 480px) {
  .ground-stats { grid-template-columns: repeat(2, 1fr); }
  .ground-val { font-size: 1.2rem; }
}
